diff options
Diffstat (limited to 'src/VBox/HostServices')
79 files changed, 32664 insertions, 0 deletions
diff --git a/src/VBox/HostServices/.scm-settings b/src/VBox/HostServices/.scm-settings new file mode 100644 index 00000000..afa97e72 --- /dev/null +++ b/src/VBox/HostServices/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the host HGCM services. +# + +# +# Copyright (C) 2019-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 +# + +/*.h: --guard-relative-to-dir . + diff --git a/src/VBox/HostServices/DragAndDrop/Makefile.kmk b/src/VBox/HostServices/DragAndDrop/Makefile.kmk new file mode 100644 index 00000000..37b88830 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/Makefile.kmk @@ -0,0 +1,65 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Control Host Service. +# + +# +# 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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +# include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The drag and drop service DLL. +# +DLLS += VBoxDragAndDropSvc +VBoxDragAndDropSvc_TEMPLATE = VBoxR3Dll +VBoxDragAndDropSvc_NAME.os2 = VBoxDnD +VBoxDragAndDropSvc_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) +VBoxDragAndDropSvc_INCS = $(PATH_ROOT)/src/VBox/Main/include ./ +VBoxDragAndDropSvc_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxDragAndDropSvc_SOURCES = \ + VBoxDragAndDropSvc.cpp \ + dndmanager.cpp + +VBoxDragAndDropSvc_SOURCES += \ + ../common/client.cpp \ + ../common/message.cpp + +VBoxDragAndDropSvc_SOURCES.win = \ + VBoxDragAndDropSvc.rc + +VBoxDragAndDropSvc_LIBS = \ + $(LIB_RUNTIME) \ + $(PATH_STAGE_LIB)/VBoxDnDHostR3Lib$(VBOX_SUFF_LIB) + +VBoxDragAndDropSvc_LDFLAGS.darwin = \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxDragAndDropSvc.dylib + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp new file mode 100644 index 00000000..c9ac0119 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp @@ -0,0 +1,1352 @@ +/* $Id: VBoxDragAndDropSvc.cpp $ */ +/** @file + * Drag and Drop Service. + */ + +/* + * 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_dnd Drag and drop HGCM Service + * + * @sa See src/VBox/Main/src-client/GuestDnDPrivate.cpp for more information. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/GuestHost/DragAndDropDefs.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/DragAndDropSvc.h> +#include <VBox/AssertGuest.h> + +#include <VBox/err.h> + +#include <algorithm> +#include <list> +#include <map> + +#include "dndmanager.h" + +using namespace DragAndDropSvc; + + +/********************************************************************************************************************************* +* Service class declaration * +*********************************************************************************************************************************/ + +class DragAndDropClient : public HGCM::Client +{ +public: + + DragAndDropClient(uint32_t idClient) + : HGCM::Client(idClient) + , uProtocolVerDeprecated(0) + , fGuestFeatures0(VBOX_DND_GF_NONE) + , fGuestFeatures1(VBOX_DND_GF_NONE) + { + RT_ZERO(m_SvcCtx); + } + + virtual ~DragAndDropClient(void) + { + disconnect(); + } + +public: + + void disconnect(void) RT_NOEXCEPT; + +public: + + /** Protocol version used by this client. + * Deprecated; only used for keeping backwards compatibility. */ + uint32_t uProtocolVerDeprecated; + /** Guest feature flags, VBOX_DND_GF_0_XXX. */ + uint64_t fGuestFeatures0; + /** Guest feature flags, VBOX_DND_GF_1_XXX. */ + uint64_t fGuestFeatures1; +}; + +/** Map holding pointers to drag and drop clients. Key is the (unique) HGCM client ID. */ +typedef std::map<uint32_t, DragAndDropClient*> DnDClientMap; + +/** Simple queue (list) which holds deferred (waiting) clients. */ +typedef std::list<uint32_t> DnDClientQueue; + +/** + * Specialized drag & drop service class. + */ +class DragAndDropService : public HGCM::AbstractService<DragAndDropService> +{ +public: + explicit DragAndDropService(PVBOXHGCMSVCHELPERS pHelpers) + : HGCM::AbstractService<DragAndDropService>(pHelpers) + , m_pManager(NULL) + , m_u32Mode(VBOX_DRAG_AND_DROP_MODE_OFF) + {} + +protected: + int init(VBOXHGCMSVCFNTABLE *pTable) RT_NOEXCEPT RT_OVERRIDE; + int uninit(void) RT_NOEXCEPT RT_OVERRIDE; + int clientConnect(uint32_t idClient, void *pvClient) RT_NOEXCEPT RT_OVERRIDE; + int clientDisconnect(uint32_t idClient, void *pvClient) RT_NOEXCEPT RT_OVERRIDE; + int clientQueryFeatures(uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT; + int clientReportFeatures(DragAndDropClient *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT; + void guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t idClient, void *pvClient, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT RT_OVERRIDE; + int hostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT RT_OVERRIDE; + +private: + int modeSet(uint32_t u32Mode) RT_NOEXCEPT; + inline uint32_t modeGet(void) const RT_NOEXCEPT + { return m_u32Mode; }; + + static DECLCALLBACK(int) progressCallback(uint32_t uStatus, uint32_t uPercentage, int rc, void *pvUser); + +private: + /** Pointer to our DnD manager instance. */ + DnDManager *m_pManager; + /** Map of all connected clients. + * The primary key is the (unique) client ID, the secondary value + * an allocated pointer to the DragAndDropClient class, managed + * by this service class. */ + DnDClientMap m_clientMap; + /** List of all clients which are queued up (deferred return) and ready + * to process new commands. The key is the (unique) client ID. */ + DnDClientQueue m_clientQueue; + /** Current drag and drop mode, VBOX_DRAG_AND_DROP_MODE_XXX. */ + uint32_t m_u32Mode; + /** Host feature mask (VBOX_DND_HF_0_XXX) for DND_GUEST_REPORT_FEATURES + * and DND_GUEST_QUERY_FEATURES. */ + uint64_t m_fHostFeatures0; +}; + + +/********************************************************************************************************************************* +* Client implementation * +*********************************************************************************************************************************/ + +/** + * Called when the HGCM client disconnected on the guest side. + * + * This function takes care of the client's data cleanup and also lets the host + * know that the client has been disconnected. + */ +void DragAndDropClient::disconnect(void) RT_NOEXCEPT +{ + LogFlowThisFunc(("uClient=%RU32, fDeferred=%RTbool\n", m_idClient, IsDeferred())); + + /* + * If the client still is waiting for a message (i.e in deferred mode), + * complete the call with a VERR_CANCELED status so that the client (VBoxTray / VBoxClient) knows + * it should bail out. + */ + if (IsDeferred()) + CompleteDeferred(VERR_CANCELLED); + + /* + * Let the host know. + */ + VBOXDNDCBDISCONNECTMSGDATA data; + RT_ZERO(data); + /** @todo Magic needed? */ + /** @todo Add context ID. */ + + if (m_SvcCtx.pfnHostCallback) + { + int rc2 = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, GUEST_DND_FN_DISCONNECT, &data, sizeof(data)); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Warning: Unable to notify host about client %RU32 disconnect, rc=%Rrc\n", m_idClient, rc2)); + /* Not fatal. */ + } +} + + +/********************************************************************************************************************************* +* Service class implementation * +*********************************************************************************************************************************/ + +int DragAndDropService::init(VBOXHGCMSVCFNTABLE *pTable) RT_NOEXCEPT +{ + /* Legacy clients map to the root category. */ + pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; + + /* Limit to 255 clients (see also DragAndDropService::clientConnect). */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = UINT8_MAX; + + /* Limit the number of concurrent calls to 256 (playing safe). */ + /** @todo Properly determin the max number of pending/concurrent calls for DnD. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxCallsPerClient[i] = 256; + + /* Register functions. */ + pTable->pfnHostCall = svcHostCall; + pTable->pfnSaveState = NULL; /* The service is stateless, so the normal */ + pTable->pfnLoadState = NULL; /* construction done before restoring suffices */ + pTable->pfnRegisterExtension = svcRegisterExtension; + pTable->pfnNotify = NULL; + + /* Drag'n drop mode is disabled by default. */ + modeSet(VBOX_DRAG_AND_DROP_MODE_OFF); + + /* Set host features. */ + m_fHostFeatures0 = VBOX_DND_HF_NONE; + + int rc = VINF_SUCCESS; + + try + { + m_pManager = new DnDManager(&DragAndDropService::progressCallback, this); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int DragAndDropService::uninit(void) RT_NOEXCEPT +{ + LogFlowFuncEnter(); + + if (m_pManager) + { + delete m_pManager; + m_pManager = NULL; + } + + DnDClientMap::iterator itClient = m_clientMap.begin(); + while (itClient != m_clientMap.end()) + { + delete itClient->second; + m_clientMap.erase(itClient); + itClient = m_clientMap.begin(); + } + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +int DragAndDropService::clientConnect(uint32_t idClient, void *pvClient) RT_NOEXCEPT +{ + RT_NOREF1(pvClient); + if (m_clientMap.size() >= UINT8_MAX) /* Don't allow too much clients at the same time. */ + { + AssertMsgFailed(("Maximum number of clients reached\n")); + return VERR_MAX_PROCS_REACHED; + } + + /* + * Add client to our client map. + */ + if (m_clientMap.find(idClient) != m_clientMap.end()) + { + LogFunc(("Client %RU32 is already connected!\n", idClient)); + return VERR_ALREADY_EXISTS; + } + + try + { + DragAndDropClient *pClient = new DragAndDropClient(idClient); + pClient->SetSvcContext(m_SvcCtx); + m_clientMap[idClient] = pClient; + } + catch (std::bad_alloc &) + { + LogFunc(("Client %RU32 - VERR_NO_MEMORY!\n", idClient)); + return VERR_NO_MEMORY; + } + + LogFlowFunc(("Client %RU32 connected (VINF_SUCCESS)\n", idClient)); + return VINF_SUCCESS; +} + +int DragAndDropService::clientDisconnect(uint32_t idClient, void *pvClient) RT_NOEXCEPT +{ + RT_NOREF1(pvClient); + + /* Client not found? Bail out early. */ + DnDClientMap::iterator itClient = m_clientMap.find(idClient); + if (itClient == m_clientMap.end()) + { + LogFunc(("Client %RU32 not found!\n", idClient)); + return VERR_NOT_FOUND; + } + + /* + * Remove from waiters queue. + */ + m_clientQueue.remove(idClient); + + /* + * Remove from client map and deallocate. + */ + AssertPtr(itClient->second); + delete itClient->second; + + m_clientMap.erase(itClient); + + LogFlowFunc(("Client %RU32 disconnected\n", idClient)); + return VINF_SUCCESS; +} + +/** + * Implements GUEST_DND_FN_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 cParms Number of parameters. + * @param paParms Array of parameters. + */ +int DragAndDropService::clientReportFeatures(DragAndDropClient *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT +{ + 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); + 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_DND_GF_1_MUST_BE_ONE, VERR_INVALID_PARAMETER); + + /* + * Report back the host features. + */ + paParms[0].u.uint64 = m_fHostFeatures0; + paParms[1].u.uint64 = 0; + + pClient->fGuestFeatures0 = fFeatures0; + pClient->fGuestFeatures1 = fFeatures1; + + Log(("[Client %RU32] features: %#RX64 %#RX64\n", pClient->GetClientID(), fFeatures0, fFeatures1)); + + return VINF_SUCCESS; +} + +/** + * Implements GUEST_DND_FN_QUERY_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int DragAndDropService::clientQueryFeatures(uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT +{ + /* + * 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)); + + /* + * Report back the host features. + */ + paParms[0].u.uint64 = m_fHostFeatures0; + paParms[1].u.uint64 = 0; + + return VINF_SUCCESS; +} + +int DragAndDropService::modeSet(uint32_t u32Mode) RT_NOEXCEPT +{ +#ifndef VBOX_WITH_DRAG_AND_DROP_GH + if ( u32Mode == VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST + || u32Mode == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL) + { + m_u32Mode = VBOX_DRAG_AND_DROP_MODE_OFF; + return VERR_NOT_SUPPORTED; + } +#endif + + switch (u32Mode) + { + case VBOX_DRAG_AND_DROP_MODE_OFF: + case VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST: + case VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST: + case VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL: + m_u32Mode = u32Mode; + break; + + default: + m_u32Mode = VBOX_DRAG_AND_DROP_MODE_OFF; + break; + } + + return VINF_SUCCESS; +} + +void DragAndDropService::guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t idClient, + void *pvClient, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT +{ + RT_NOREF1(pvClient); + LogFlowFunc(("idClient=%RU32, u32Function=%s (%#x), cParms=%RU32\n", + idClient, DnDGuestMsgToStr(u32Function), u32Function, cParms)); + + /* Check if we've the right mode set. */ + int rc = VERR_ACCESS_DENIED; /* Play safe. */ + switch (u32Function) + { + case GUEST_DND_FN_GET_NEXT_HOST_MSG: + { + if (modeGet() != VBOX_DRAG_AND_DROP_MODE_OFF) + rc = VINF_SUCCESS; + else + { + LogRel(("DnD: Feature is disabled, ignoring request from guest\n")); + rc = VINF_HGCM_ASYNC_EXECUTE; + } + break; + } + + /* New since protocol v2. */ + case GUEST_DND_FN_CONNECT: + RT_FALL_THROUGH(); + /* New since VBox 6.1.x. */ + case GUEST_DND_FN_REPORT_FEATURES: + RT_FALL_THROUGH(); + /* New since VBox 6.1.x. */ + case GUEST_DND_FN_QUERY_FEATURES: + { + /* + * Never block these calls, as the clients issues those when + * initializing and might get stuck if drag and drop is set to "disabled" at + * that time. + */ + rc = VINF_SUCCESS; + break; + } + + /* New since VBOx 7.0.x. See define for details. */ + case GUEST_DND_FN_EVT_ERROR: + { + rc = VINF_SUCCESS; + break; + } + + case GUEST_DND_FN_HG_ACK_OP: + case GUEST_DND_FN_HG_REQ_DATA: + case GUEST_DND_FN_HG_EVT_PROGRESS: + { + if ( modeGet() == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL + || modeGet() == VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST) + rc = VINF_SUCCESS; + else + LogRel(("DnD: Host -> Guest mode disabled, ignoring request from guest\n")); + break; + } + + case GUEST_DND_FN_GH_ACK_PENDING: + case GUEST_DND_FN_GH_SND_DATA_HDR: + case GUEST_DND_FN_GH_SND_DATA: + case GUEST_DND_FN_GH_SND_DIR: + case GUEST_DND_FN_GH_SND_FILE_HDR: + case GUEST_DND_FN_GH_SND_FILE_DATA: + { +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + if ( modeGet() == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL + || modeGet() == VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST) + rc = VINF_SUCCESS; + else +#endif + LogRel(("DnD: Guest -> Host mode disabled, ignoring request from guest\n")); + break; + } + + default: + /* Reach through to DnD manager. */ + rc = VINF_SUCCESS; + break; + } + +#define DO_HOST_CALLBACK(); \ + if ( RT_SUCCESS(rc) \ + && m_SvcCtx.pfnHostCallback) \ + { \ + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); \ + } + + /* + * Lookup client. + */ + DragAndDropClient *pClient = NULL; + + DnDClientMap::iterator itClient = m_clientMap.find(idClient); + if (itClient != m_clientMap.end()) + { + pClient = itClient->second; + AssertPtr(pClient); + } + else + { + LogFunc(("Client %RU32 was not found\n", idClient)); + rc = VERR_NOT_FOUND; + } + +/* Verifies that an uint32 parameter has the expected buffer size set. + * Will set rc to VERR_INVALID_PARAMETER otherwise. See #9777. */ +#define VERIFY_BUFFER_SIZE_UINT32(a_ParmUInt32, a_SizeExpected) \ +do { \ + uint32_t cbTemp = 0; \ + rc = HGCMSvcGetU32(&a_ParmUInt32, &cbTemp); \ + ASSERT_GUEST_BREAK(RT_SUCCESS(rc) && cbTemp == a_SizeExpected); \ +} while (0) + +/* Gets the context ID from the first parameter and store it into the data header. + * Then increments idxParm by one if more than one parameter is available. */ +#define GET_CONTEXT_ID_PARM0() \ + if (fHasCtxID) \ + { \ + ASSERT_GUEST_BREAK(cParms >= 1); \ + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); \ + ASSERT_GUEST_BREAK(RT_SUCCESS(rc)); \ + if (cParms > 1) \ + idxParm++; \ + } + + if (rc == VINF_SUCCESS) /* Note: rc might be VINF_HGCM_ASYNC_EXECUTE! */ + { + rc = VERR_INVALID_PARAMETER; /* Play safe by default. */ + + /* Whether the client's advertised protocol sends context IDs with commands. */ + const bool fHasCtxID = pClient->uProtocolVerDeprecated >= 3; + + /* Current parameter index to process. */ + unsigned idxParm = 0; + + switch (u32Function) + { + /* + * Note: Older VBox versions with enabled DnD guest->host support (< 5.0) + * used the same message ID (300) for GUEST_DND_FN_GET_NEXT_HOST_MSG and + * HOST_DND_FN_GH_REQ_PENDING, which led this service returning + * VERR_INVALID_PARAMETER when the guest wanted to actually + * handle HOST_DND_FN_GH_REQ_PENDING. + */ + case GUEST_DND_FN_GET_NEXT_HOST_MSG: + { + if (cParms == 3) + { + /* Make sure to increase the reference count so that the next message doesn't get removed between + * the guest's GUEST_DND_FN_GET_NEXT_HOST_MSG call and the actual message retrieval call. */ + rc = m_pManager->GetNextMsgInfo(true /* fAddRef */, + &paParms[0].u.uint32 /* uMsg */, &paParms[1].u.uint32 /* cParms */); + if (RT_FAILURE(rc)) /* No queued messages available? */ + { + if (m_SvcCtx.pfnHostCallback) /* Try asking the host. */ + { + VBOXDNDCBHGGETNEXTHOSTMSG data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG; + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); + if (RT_SUCCESS(rc)) + { + paParms[0].u.uint32 = data.uMsg; /* uMsg */ + paParms[1].u.uint32 = data.cParms; /* cParms */ + /* Note: paParms[2] was set by the guest as blocking flag. */ + } + + LogFlowFunc(("Host callback returned %Rrc\n", rc)); + } + else /* No host callback in place, so drag and drop is not supported by the host. */ + rc = VERR_NOT_SUPPORTED; + + if (RT_FAILURE(rc)) + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + + /* Some error occurred or no (new) messages available? */ + if (RT_FAILURE(rc)) + { + uint32_t fFlags = 0; + int rc2 = HGCMSvcGetU32(&paParms[2], &fFlags); + if ( RT_SUCCESS(rc2) + && fFlags) /* Blocking flag set? */ + { + /* Defer client returning. */ + rc = VINF_HGCM_ASYNC_EXECUTE; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFunc(("Message queue is empty, returning %Rrc to guest\n", rc)); + } + } + } + break; + } + case GUEST_DND_FN_CONNECT: + { + ASSERT_GUEST_BREAK(cParms >= 2); + + VBOXDNDCBCONNECTDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_CONNECT; + + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.hdr.uContextID); \ + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uProtocolVersion); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.fFlags); + ASSERT_GUEST_RC_BREAK(rc); + + unsigned uProtocolVer = 3; /* The protocol version we're going to use. */ + + /* Make sure we're only setting a protocl version we're supporting on the host. */ + if (data.uProtocolVersion > uProtocolVer) + data.uProtocolVersion = uProtocolVer; + + pClient->uProtocolVerDeprecated = data.uProtocolVersion; + + /* Return the highest protocol version we're supporting. */ + AssertBreak(idxParm); + ASSERT_GUEST_BREAK(idxParm); + paParms[idxParm - 1].u.uint32 = data.uProtocolVersion; + + LogFlowFunc(("Client %RU32 is now using protocol v%RU32\n", + pClient->GetClientID(), pClient->uProtocolVerDeprecated)); + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_FN_REPORT_FEATURES: + { + rc = clientReportFeatures(pClient, cParms, paParms); + if (RT_SUCCESS(rc)) + { + VBOXDNDCBREPORTFEATURESDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_REPORT_FEATURES; + + data.fGuestFeatures0 = pClient->fGuestFeatures0; + /* fGuestFeatures1 is not used yet. */ + + /* Don't touch initial rc. */ + int rc2 = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); + AssertRC(rc2); + } + break; + } + case GUEST_DND_FN_QUERY_FEATURES: + { + rc = clientQueryFeatures(cParms, paParms); + break; + } + case GUEST_DND_FN_HG_ACK_OP: + { + ASSERT_GUEST_BREAK(cParms >= 2); + + VBOXDNDCBHGACKOPDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_ACK_OP; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.uAction); /* Get drop action. */ + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_FN_HG_REQ_DATA: + { + VBOXDNDCBHGREQDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_REQ_DATA; + + switch (pClient->uProtocolVerDeprecated) + { + case 3: + { + ASSERT_GUEST_BREAK(cParms == 3); + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void **)&data.pszFormat, &data.cbFormat); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.cbFormat); + break; + } + + case 2: + RT_FALL_THROUGH(); + default: + { + ASSERT_GUEST_BREAK(cParms == 1); + rc = HGCMSvcGetPv(&paParms[idxParm], (void**)&data.pszFormat, &data.cbFormat); + ASSERT_GUEST_RC_BREAK(rc); + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_FN_HG_EVT_PROGRESS: + { + ASSERT_GUEST_BREAK(cParms >= 3); + + VBOXDNDCBHGEVTPROGRESSDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_EVT_PROGRESS; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uStatus); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uPercentage); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.rc); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_FN_GH_ACK_PENDING: + { + VBOXDNDCBGHACKPENDINGDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_ACK_PENDING; + + switch (pClient->uProtocolVerDeprecated) + { + case 3: + { + ASSERT_GUEST_BREAK(cParms == 5); + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uDefAction); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uAllActions); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pszFormat, &data.cbFormat); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.cbFormat); + break; + } + + case 2: + default: + { + ASSERT_GUEST_BREAK(cParms == 3); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uDefAction); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.uAllActions); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetPv(&paParms[idxParm], (void**)&data.pszFormat, &data.cbFormat); + ASSERT_GUEST_RC_BREAK(rc); + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + /* New since protocol v3. */ + case GUEST_DND_FN_GH_SND_DATA_HDR: + { + ASSERT_GUEST_BREAK(cParms == 12); + + VBOXDNDCBSNDDATAHDRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA_HDR; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.data.uFlags); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.data.uScreenId); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU64(&paParms[idxParm++], &data.data.cbTotal); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.data.cbMeta); + ASSERT_GUEST_RC_BREAK(rc); + ASSERT_GUEST_BREAK(data.data.cbMeta <= data.data.cbTotal); + rc = HGCMSvcGetPv(&paParms[idxParm++], &data.data.pvMetaFmt, &data.data.cbMetaFmt); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.data.cbMetaFmt); + rc = HGCMSvcGetU64(&paParms[idxParm++], &data.data.cObjects); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.data.enmCompression); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], (uint32_t *)&data.data.enmChecksumType); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetPv(&paParms[idxParm++], &data.data.pvChecksum, &data.data.cbChecksum); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.data.cbChecksum); + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_FN_GH_SND_DATA: + { + switch (pClient->uProtocolVerDeprecated) + { + case 3: + { + ASSERT_GUEST_BREAK(cParms == 5); + + VBOXDNDCBSNDDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.data.u.v3.pvData, &data.data.u.v3.cbData); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.data.u.v3.cbData); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.data.u.v3.pvChecksum, &data.data.u.v3.cbChecksum); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.data.u.v3.cbChecksum); + + DO_HOST_CALLBACK(); + break; + } + + case 2: + RT_FALL_THROUGH(); + default: + { + ASSERT_GUEST_BREAK(cParms == 2); + + VBOXDNDCBSNDDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA; + + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.data.u.v1.pvData, &data.data.u.v1.cbData); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.data.u.v1.cbTotalSize); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } + } + break; + } + case GUEST_DND_FN_GH_SND_DIR: + { + ASSERT_GUEST_BREAK(cParms >= 3); + + VBOXDNDCBSNDDIRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DIR; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pszPath, &data.cbPath); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.cbPath); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.fMode); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } + /* New since protocol v2 (>= VBox 5.0). */ + case GUEST_DND_FN_GH_SND_FILE_HDR: + { + ASSERT_GUEST_BREAK(cParms == 6); + + VBOXDNDCBSNDFILEHDRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_HDR; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pszFilePath, &data.cbFilePath); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.cbFilePath); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.fFlags); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU32(&paParms[idxParm++], &data.fMode); + ASSERT_GUEST_RC_BREAK(rc); + rc = HGCMSvcGetU64(&paParms[idxParm], &data.cbSize); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_FN_GH_SND_FILE_DATA: + { + switch (pClient->uProtocolVerDeprecated) + { + /* Protocol v3 adds (optional) checksums. */ + case 3: + { + ASSERT_GUEST_BREAK(cParms == 5); + + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pvData, &data.cbData); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.cbData); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.u.v3.pvChecksum, &data.u.v3.cbChecksum); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.u.v3.cbChecksum); + + DO_HOST_CALLBACK(); + break; + } + /* Protocol v2 only sends the next data chunks to reduce traffic. */ + case 2: + { + ASSERT_GUEST_BREAK(cParms == 3); + + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pvData, &data.cbData); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm], data.cbData); + + DO_HOST_CALLBACK(); + break; + } + /* Protocol v1 sends the file path and attributes for every file chunk (!). */ + default: + { + ASSERT_GUEST_BREAK(cParms == 5); + + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.u.v1.pszFilePath, &data.u.v1.cbFilePath); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.u.v1.cbFilePath); + rc = HGCMSvcGetPv(&paParms[idxParm++], (void**)&data.pvData, &data.cbData); + ASSERT_GUEST_RC_BREAK(rc); + VERIFY_BUFFER_SIZE_UINT32(paParms[idxParm++], data.cbData); + rc = HGCMSvcGetU32(&paParms[idxParm], &data.u.v1.fMode); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } + } + break; + } + case GUEST_DND_FN_EVT_ERROR: + { + ASSERT_GUEST_BREAK(cParms >= 1); + + VBOXDNDCBEVTERRORDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_EVT_ERROR; + + GET_CONTEXT_ID_PARM0(); + rc = HGCMSvcGetU32(&paParms[idxParm], (uint32_t *)&data.rc); + ASSERT_GUEST_RC_BREAK(rc); + + DO_HOST_CALLBACK(); + break; + } +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + + default: + { + LogFlowFunc(("u32Function=%s (%#x), cParms=%RU32\n", DnDHostMsgToStr(u32Function), u32Function, cParms)); + + /* All other messages are handled by the DnD manager. */ + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + if (rc == VERR_NO_DATA) /* Manager has no new messsages? Try asking the host. */ + { + if (m_SvcCtx.pfnHostCallback) + { + VBOXDNDCBHGGETNEXTHOSTMSGDATA data; + RT_ZERO(data); + + data.hdr.uMagic = VBOX_DND_CB_MAGIC_MAKE(0 /* uFn */, 0 /* uVer */); + + data.uMsg = u32Function; + data.cParms = cParms; + data.paParms = paParms; + + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); + if (RT_SUCCESS(rc)) + { + cParms = data.cParms; + paParms = data.paParms; + } + else + { + if (rc == VERR_CANCELLED) + { + /* Host indicated that the current operation was cancelled. Tell the guest. */ + LogFunc(("Host indicated that operation was cancelled\n")); + } + else + { + /* + * In case the guest is too fast asking for the next message + * and the host did not supply it yet, just defer the client's + * return until a response from the host available. + */ + LogFunc(("No new messages from the host (%Rrc), deferring request\n", rc)); + rc = VINF_HGCM_ASYNC_EXECUTE; + } + } + } + else /* No host callback in place, so drag and drop is not supported by the host. */ + rc = VERR_NOT_SUPPORTED; + } + break; + } + } + } + +#undef VERIFY_BUFFER_SIZE_UINT32 + + /* + * If async execution is requested, we didn't notify the guest yet about + * completion. The client is queued into the waiters list and will be + * notified as soon as a new event is available. + */ + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + LogFunc(("Deferring client %RU32\n", idClient)); + + try + { + AssertPtr(pClient); + pClient->SetDeferred(callHandle, u32Function, cParms, paParms); + m_clientQueue.push_back(idClient); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + /* Don't report to guest. */ + } + } + else if (pClient) + { + /* Complete the call on the guest side. */ + pClient->Complete(callHandle, rc); + } + else + { + AssertMsgFailed(("Guest call failed with %Rrc\n", rc)); + rc = VERR_NOT_IMPLEMENTED; + } + + LogFunc(("Returning %Rrc to guest\n", rc)); +} + +int DragAndDropService::hostCall(uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT +{ + LogFlowFunc(("u32Function=%s (%#x), cParms=%RU32, cClients=%zu, cQueue=%zu\n", + DnDHostMsgToStr(u32Function), u32Function, cParms, m_clientMap.size(), m_clientQueue.size())); + + uint32_t const uMode = modeGet(); + + /* Check if we've the right mode set. */ + int rc = VERR_ACCESS_DENIED; /* Play safe. */ + switch (u32Function) + { + /* + * Host -> Guest mode + */ + case HOST_DND_FN_HG_EVT_ENTER: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_EVT_MOVE: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_EVT_LEAVE: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_EVT_DROPPED: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_DATA_HDR: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_DATA: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_MORE_DATA: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_DIR: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_FILE_DATA: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_FILE_HDR: + { + if ( uMode == VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST + || uMode == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL) + rc = VINF_SUCCESS; + else + { + LogRel2(("DnD: Host to guest mode is disabled, ignoring request from host\n")); + } + break; + } + + /* + * Guest -> Host mode + */ + case HOST_DND_FN_GH_REQ_PENDING: + RT_FALL_THROUGH(); + case HOST_DND_FN_GH_EVT_DROPPED: + { + if ( uMode == VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST + || uMode == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL) + rc = VINF_SUCCESS; + else + { + LogRel2(("DnD: Guest to host mode is disabled, ignoring request from host\n")); + } + break; + } + + /* + * Both modes + */ + case HOST_DND_FN_CANCEL: + if (uMode != VBOX_DRAG_AND_DROP_MODE_OFF) + rc = VINF_SUCCESS; + break; + + /* + * Functions that always work. + */ + case HOST_DND_FN_SET_MODE: + rc = VINF_SUCCESS; + break; + + /* + * Forbid everything else not explicitly allowed. + */ + default: + break; + } + + if (RT_FAILURE(rc)) + return rc; + + bool fSendToGuest = false; /* Whether to send the message down to the guest side or not. */ + + switch (u32Function) + { + case HOST_DND_FN_SET_MODE: + { + if (cParms != 1) + rc = VERR_INVALID_PARAMETER; + else if (paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT) + rc = VERR_INVALID_PARAMETER; + else + rc = modeSet(paParms[0].u.uint32); + break; + } + + case HOST_DND_FN_CANCEL: + { + LogFlowFunc(("Cancelling all waiting clients ...\n")); + + /* Forcefully reset the message queue, as the host has cancelled the current operation. */ + m_pManager->Reset(true /* fForce */); + + /* + * Wake up all deferred clients and tell them to process + * the cancelling message next. + */ + DnDClientQueue::iterator itQueue = m_clientQueue.begin(); + while (itQueue != m_clientQueue.end()) + { + DnDClientMap::iterator itClient = m_clientMap.find(*itQueue); + Assert(itClient != m_clientMap.end()); + + DragAndDropClient *pClient = itClient->second; + AssertPtr(pClient); + + int rc2 = pClient->SetDeferredMsgInfo(HOST_DND_FN_CANCEL, + /* Protocol v3+ also contains the context ID. */ + pClient->uProtocolVerDeprecated >= 3 ? 1 : 0); + AssertRC(rc2); + + /* Return VERR_CANCELLED when waking up the guest side. */ + pClient->CompleteDeferred(VERR_CANCELLED); + + m_clientQueue.erase(itQueue); + itQueue = m_clientQueue.begin(); + } + + Assert(m_clientQueue.empty()); + + /* Tell the host that everything went well. */ + rc = VINF_SUCCESS; + break; + } + + case HOST_DND_FN_HG_EVT_ENTER: + { + /* Reset the message queue as a new DnD operation just began. */ + m_pManager->Reset(false /* fForce */); + + fSendToGuest = true; + rc = VINF_SUCCESS; + break; + } + + default: + { + fSendToGuest = true; + rc = VINF_SUCCESS; + break; + } + } + + do /* goto avoidance break-loop. */ + { + if (fSendToGuest) + { + if (m_clientMap.empty()) /* At least one client on the guest connected? */ + { + /* + * Tell the host that the guest does not support drag'n drop. + * This might happen due to not installed Guest Additions or + * not running VBoxTray/VBoxClient. + */ + rc = VERR_NOT_SUPPORTED; + break; + } + + rc = m_pManager->AddMsg(u32Function, cParms, paParms, true /* fAppend */); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Adding new message of type=%RU32 failed with rc=%Rrc\n", u32Function, rc)); + break; + } + + /* Any clients in our queue ready for processing the next command? */ + if (m_clientQueue.empty()) + { + LogFlowFunc(("All clients (%zu) busy -- delaying execution\n", m_clientMap.size())); + break; + } + + uint32_t uClientNext = m_clientQueue.front(); + DnDClientMap::iterator itClientNext = m_clientMap.find(uClientNext); + Assert(itClientNext != m_clientMap.end()); + + DragAndDropClient *pClient = itClientNext->second; + AssertPtr(pClient); + + /* + * Check if this was a request for getting the next host + * message. If so, return the message ID and the parameter + * count. The message itself has to be queued. + */ + uint32_t uMsgClient = pClient->GetMsgType(); + + uint32_t uMsgNext = 0; + uint32_t cParmsNext = 0; + /* Note: We only want to peek for the next message, hence fAddRef is false. */ + int rcNext = m_pManager->GetNextMsgInfo(false /* fAddRef */, &uMsgNext, &cParmsNext); + + LogFlowFunc(("uMsgClient=%s (%#x), uMsgNext=%s (%#x), cParmsNext=%RU32, rcNext=%Rrc\n", + DnDGuestMsgToStr(uMsgClient), uMsgClient, DnDHostMsgToStr(uMsgNext), uMsgNext, cParmsNext, rcNext)); + + if (RT_SUCCESS(rcNext)) + { + if (uMsgClient == GUEST_DND_FN_GET_NEXT_HOST_MSG) + { + rc = pClient->SetDeferredMsgInfo(uMsgNext, cParmsNext); + + /* Note: Report the current rc back to the guest. */ + pClient->CompleteDeferred(rc); + } + /* + * Does the message the client is waiting for match the message + * next in the queue? Process it right away then. + */ + else if (uMsgClient == uMsgNext) + { + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + + /* Note: Report the current rc back to the guest. */ + pClient->CompleteDeferred(rc); + } + else /* Should not happen; cancel the operation on the guest. */ + { + LogFunc(("Client ID=%RU32 in wrong state with uMsg=%RU32 (next message in queue: %RU32), cancelling\n", + pClient->GetClientID(), uMsgClient, uMsgNext)); + + pClient->CompleteDeferred(VERR_CANCELLED); + } + + m_clientQueue.pop_front(); + } + + } /* fSendToGuest */ + + } while (0); /* To use breaks. */ + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) DragAndDropService::progressCallback(uint32_t uStatus, uint32_t uPercentage, int rc, void *pvUser) +{ + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + + DragAndDropService *pSelf = static_cast<DragAndDropService *>(pvUser); + AssertPtr(pSelf); + + if (pSelf->m_SvcCtx.pfnHostCallback) + { + LogFlowFunc(("GUEST_DND_FN_HG_EVT_PROGRESS: uStatus=%RU32, uPercentage=%RU32, rc=%Rrc\n", + uStatus, uPercentage, rc)); + + VBOXDNDCBHGEVTPROGRESSDATA data; + data.hdr.uMagic = CB_MAGIC_DND_HG_EVT_PROGRESS; + data.uPercentage = RT_MIN(uPercentage, 100); + data.uStatus = uStatus; + data.rc = rc; /** @todo uin32_t vs. int. */ + + return pSelf->m_SvcCtx.pfnHostCallback(pSelf->m_SvcCtx.pvHostData, + GUEST_DND_FN_HG_EVT_PROGRESS, + &data, sizeof(data)); + } + + return VINF_SUCCESS; +} + +/** + * @copydoc FNVBOXHGCMSVCLOAD + */ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + return DragAndDropService::svcLoad(pTable); +} + diff --git a/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc new file mode 100644 index 00000000..27229180 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxDragAndDropSvc.rc $ */ +/** @file + * VBoxDragAndDropSvc - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Drag and Drop Host Service\0" + VALUE "InternalName", "VBoxDragAndDropSvc\0" + VALUE "OriginalFilename", "VBoxDragAndDropSvc.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/DragAndDrop/dndmanager.cpp b/src/VBox/HostServices/DragAndDrop/dndmanager.cpp new file mode 100644 index 00000000..137df6cc --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/dndmanager.cpp @@ -0,0 +1,233 @@ +/* $Id: dndmanager.cpp $ */ +/** @file + * Drag and Drop manager: Handling of DnD messages on the host side. + */ + +/* + * 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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#ifdef LOG_GROUP + #undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_GUEST_DND + +#include "dndmanager.h" + +#include <VBox/log.h> +#include <iprt/file.h> +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/uri.h> + + +/********************************************************************************************************************************* +* DnDManager * +*********************************************************************************************************************************/ + +/** + * Adds a DnD message to the manager's queue. + * + * @returns IPRT status code. + * @param pMsg Pointer to DnD message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + */ +int DnDManager::AddMsg(DnDMessage *pMsg, bool fAppend /* = true */) +{ + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + LogFlowFunc(("uMsg=%s (%#x), cParms=%RU32, fAppend=%RTbool\n", + DnDHostMsgToStr(pMsg->GetType()), pMsg->GetType(), pMsg->GetParamCount(), fAppend)); + + if (fAppend) + m_queueMsg.append(pMsg); + else + m_queueMsg.prepend(pMsg); + +#ifdef DEBUG + DumpQueue(); +#endif + + /** @todo Catch / handle OOM? */ + + return VINF_SUCCESS; +} + +/** + * Adds a DnD message to the manager's queue. + * + * @returns IPRT status code. + * @param uMsg Type (function number) of message to add. + * @param cParms Number of parameters of message to add. + * @param paParms Array of parameters of message to add. + * @param fAppend Whether to append or prepend the message to the queue. + */ +int DnDManager::AddMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fAppend /* = true */) +{ + int rc; + + try + { + DnDMessage *pMsg = new DnDGenericMessage(uMsg, cParms, paParms); + rc = AddMsg(pMsg, fAppend); + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef DEBUG +void DnDManager::DumpQueue(void) +{ + LogFunc(("Current queue (%zu items, FIFO) is: %s", m_queueMsg.size(), m_queueMsg.isEmpty() ? "<Empty>" : "")); + for (size_t i = 0; i < m_queueMsg.size(); ++i) + { + if (i > 0) + Log((" - ")); + DnDMessage const *pMsg = m_queueMsg[i]; + uint32_t const uType = pMsg->GetType(); + Log(("%s (%d / %#x) cRefS=%RU32", DnDHostMsgToStr(uType), uType, uType, pMsg->RefCount())); + } + Log(("\n")); +} +#endif /* DEBUG */ + +/** + * Retrieves information about the next message in the queue. + * + * @returns IPRT status code. VERR_NO_DATA if no next message is available. + * @param fAddRef Set to \c true to increase the message's reference count, or \c false if not. + * @param puType Where to store the message type. + * @param pcParms Where to store the message parameter count. + */ +int DnDManager::GetNextMsgInfo(bool fAddRef, uint32_t *puType, uint32_t *pcParms) +{ + AssertPtrReturn(puType, VERR_INVALID_POINTER); + AssertPtrReturn(pcParms, VERR_INVALID_POINTER); + + int rc; + + if (m_queueMsg.isEmpty()) + { + rc = VERR_NO_DATA; + } + else + { + DnDMessage *pMsg = m_queueMsg.first(); + AssertPtr(pMsg); + + *puType = pMsg->GetType(); + *pcParms = pMsg->GetParamCount(); + + if (fAddRef) + pMsg->AddRef(); + + rc = VINF_SUCCESS; + } + +#ifdef DEBUG + DumpQueue(); +#endif + + LogFlowFunc(("Returning uMsg=%s (%#x), cParms=%RU32, fAddRef=%RTbool, rc=%Rrc\n", + DnDHostMsgToStr(*puType), *puType, *pcParms, fAddRef, rc)); + return rc; +} + +/** + * Retrieves the next queued up message and removes it from the queue on success. + * + * @returns VBox status code. + * @retval VERR_NO_DATA if no next message is available. + * @param uMsg Message type to retrieve. + * @param cParms Number of parameters the \@a paParms array can store. + * @param paParms Where to store the message parameters. + */ +int DnDManager::GetNextMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFunc(("uMsg=%s (%#x), cParms=%RU32\n", DnDHostMsgToStr(uMsg), uMsg, cParms)); + + /* Check for pending messages in our queue. */ + if (m_queueMsg.isEmpty()) + return VERR_NO_DATA; + +#ifdef DEBUG + DumpQueue(); +#endif + + /* Get the current message. */ + DnDMessage *pMsg = m_queueMsg.first(); + AssertPtr(pMsg); + + if (pMsg->Release() == 0) /* Not referenced by any client anymore? */ + m_queueMsg.removeFirst(); /* Remove the current message from the queue. */ + + /* Fetch the current message info. */ + int rc = pMsg->GetData(uMsg, cParms, paParms); + + /* + * If there was an error handling the current message or the user has canceled + * the operation, we need to cleanup all pending events. + */ + if (RT_FAILURE(rc)) + { + /* Clear any pending messages. */ + Reset(true /* fForce */); + } + + LogFlowFunc(("Message processed with rc=%Rrc\n", rc)); + return rc; +} + +/** + * Resets the manager by clearing the message queue and internal state. + * + * @param fForce Set to \c true to forcefully also remove still referenced messages, or \c false to only + * remove non-referenced messages. + */ +void DnDManager::Reset(bool fForce) +{ + LogFlowFuncEnter(); + +#ifdef DEBUG + DumpQueue(); +#endif + + for (size_t i = 0; i < m_queueMsg.size(); i++) + { + if ( fForce + || m_queueMsg[i]->RefCount() == 0) + { + m_queueMsg.removeAt(i); + i = i > 0 ? i - 1 : 0; + } + } +} + diff --git a/src/VBox/HostServices/DragAndDrop/dndmanager.h b/src/VBox/HostServices/DragAndDrop/dndmanager.h new file mode 100644 index 00000000..2abb71b1 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/dndmanager.h @@ -0,0 +1,136 @@ +/** @file + * Drag and Drop manager. + */ + +/* + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h +#define VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/DragAndDropSvc.h> + +#include <iprt/cpp/ministring.h> +#include <iprt/cpp/list.h> + +typedef DECLCALLBACKTYPE(int, FNDNDPROGRESS,(uint32_t uState, uint32_t uPercentage, int rc, void *pvUser)); +typedef FNDNDPROGRESS *PFNDNDPROGRESS; + +/** + * DnD message class. This class forms the base of all other more specialized + * message classes. + */ +class DnDMessage : public HGCM::Message +{ +public: + + DnDMessage(void) + : m_cRefs(0) { } + + DnDMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) + : Message(uMsg, cParms, aParms) + , m_cRefs(0) { } + + virtual ~DnDMessage(void) { } + + uint32_t AddRef(void) { Assert(m_cRefs < 32); return ++m_cRefs; } + uint32_t Release(void) { if (m_cRefs) return --m_cRefs; return m_cRefs; } + uint32_t RefCount(void) const { return m_cRefs; } + +protected: + + /** The message's current reference count. */ + uint32_t m_cRefs; +}; + +/** + * DnD message class for generic messages which didn't need any special + * handling. + */ +class DnDGenericMessage: public DnDMessage +{ +public: + DnDGenericMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) + : DnDMessage(uMsg, cParms, paParms) { } +}; + +/** + * DnD message class for informing the guest to cancel any current (and pending) activities. + */ +class DnDHGCancelMessage: public DnDMessage +{ +public: + + DnDHGCancelMessage(void) + { + int rc2 = initData(DragAndDropSvc::HOST_DND_FN_CANCEL, + 0 /* cParms */, 0 /* aParms */); + AssertRC(rc2); + } +}; + +/** + * DnD manager. Manage creation and queuing of messages for the various DnD + * messages types. + */ +class DnDManager +{ +public: + + DnDManager(PFNDNDPROGRESS pfnProgressCallback, void *pvProgressUser) + : m_pfnProgressCallback(pfnProgressCallback) + , m_pvProgressUser(pvProgressUser) + {} + + virtual ~DnDManager(void) + { + Reset(true /* fForce */); + } + + int AddMsg(DnDMessage *pMessage, bool fAppend = true); + int AddMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fAppend = true); + +#ifdef DEBUG + void DumpQueue(); +#endif + + int GetNextMsgInfo(bool fAddRef, uint32_t *puType, uint32_t *pcParms); + int GetNextMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + + void Reset(bool fForce); + +protected: + + /** DnD message queue (FIFO). */ + RTCList<DnDMessage *> m_queueMsg; + /** Pointer to host progress callback. Optional, can be NULL. */ + PFNDNDPROGRESS m_pfnProgressCallback; + /** Pointer to progress callback user context. Can be NULL if not used. */ + void *m_pvProgressUser; +}; +#endif /* !VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h */ + diff --git a/src/VBox/HostServices/GuestControl/Makefile.kmk b/src/VBox/HostServices/GuestControl/Makefile.kmk new file mode 100644 index 00000000..88a37450 --- /dev/null +++ b/src/VBox/HostServices/GuestControl/Makefile.kmk @@ -0,0 +1,57 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Control Host Service. +# + +# +# 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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The guest control service DLL. +# +DLLS += VBoxGuestControlSvc +VBoxGuestControlSvc_TEMPLATE = VBoxR3Dll +VBoxGuestControlSvc_NAME.os2 = VBoxGCTL +VBoxGuestControlSvc_DEFS = VBOX_WITH_HGCM +VBoxGuestControlSvc_INCS = $(PATH_ROOT)/src/VBox/Main/include +VBoxGuestControlSvc_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxGuestControlSvc_SOURCES = \ + VBoxGuestControlSvc.cpp + +VBoxGuestControlSvc_SOURCES.win = \ + VBoxGuestControlSvc.rc + +VBoxGuestControlSvc_LIBS = \ + $(LIB_RUNTIME) + +VBoxGuestControlSvc_LDFLAGS.darwin = \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxGuestControlSvc.dylib + +include $(FILE_KBUILD_SUB_FOOTER) 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; +} + diff --git a/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.rc b/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.rc new file mode 100644 index 00000000..a10c8803 --- /dev/null +++ b/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxGuestControlSvc.rc $ */ +/** @file + * VBoxGuestControlSvc - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Guest Control Host Service\0" + VALUE "InternalName", "VBoxGuestControl\0" + VALUE "OriginalFilename", "VBoxGuestControl.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/GuestControl/testcase/Makefile.kmk b/src/VBox/HostServices/GuestControl/testcase/Makefile.kmk new file mode 100644 index 00000000..1fa6a759 --- /dev/null +++ b/src/VBox/HostServices/GuestControl/testcase/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Control Host Service testcases. +# + +# +# Copyright (C) 2010-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 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + # Set this in LocalConfig.kmk if you are working on the guest property + # service to automatically run the testcase at build time. + # OTHERS += $(tstGuestControlSvc_0_OUTDIR)/tstGuestControlSvc.run + # + + PROGRAMS += tstGuestControlSvc + TESTING += $(tstGuestControlSvc_0_OUTDIR)/tstGuestControlSvc.run + tstGuestControlSvc_TEMPLATE = VBoxR3TstExe + # The second define here is to ensure that the testcase will run fast, + # without waiting for any thread synchronisation. + tstGuestControlSvc_DEFS = VBOX_WITH_HGCM VBOX_GUEST_CONTROL_TEST_NOTHREAD + tstGuestControlSvc_SOURCES = \ + ../VBoxGuestControlSvc.cpp \ + tstGuestControlSvc.cpp + tstGuestControlSvc_LIBS = $(LIB_RUNTIME) + + $$(tstGuestControlSvc_0_OUTDIR)/tstGuestControlSvc.run: $$(tstGuestControlSvc_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstGuestControlSvc_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/GuestControl/testcase/tstGuestControlSvc.cpp b/src/VBox/HostServices/GuestControl/testcase/tstGuestControlSvc.cpp new file mode 100644 index 00000000..e00d4955 --- /dev/null +++ b/src/VBox/HostServices/GuestControl/testcase/tstGuestControlSvc.cpp @@ -0,0 +1,282 @@ +/* $Id: tstGuestControlSvc.cpp $ */ +/** @file + * Testcase for the guest control service. + */ + +/* + * 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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/HostServices/GuestControlSvc.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + +using namespace guestControl; + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable); + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code. */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +/** + * Initialise the HGCM service table as much as we need to start the + * service. + * + * @return IPRT status code. + * @param pTable the table to initialise + */ +int initTable(VBOXHGCMSVCFNTABLE *pTable, VBOXHGCMSVCHELPERS *pHelpers) +{ + pTable->cbSize = sizeof (VBOXHGCMSVCFNTABLE); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + pHelpers->pfnCallComplete = callComplete; + pTable->pHelpers = pHelpers; + + return VINF_SUCCESS; +} + +typedef struct CMDHOST +{ + /** The HGCM command to execute. */ + int cmd; + /** Number of parameters. */ + int num_parms; + /** The actual parameters. */ + const PVBOXHGCMSVCPARM parms; + /** Flag indicating whether we need a connected client for this command. */ + bool fNeedsClient; + /** The desired return value from the host. */ + int rc; +} CMDHOST, *PCMDHOST; + +typedef struct CMDCLIENT +{ + /** The client's ID. */ + int client_id; + /** The HGCM command to execute. */ + int cmd; + /** Number of parameters. */ + int num_parms; + /** The actual parameters. */ + const PVBOXHGCMSVCPARM parms; + /** The desired return value from the host. */ + int rc; +} CMDCLIENT, *PCMDCLIENT; + +/** + * Tests the HOST_EXEC_CMD function. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static int testHostCmd(const VBOXHGCMSVCFNTABLE *pTable, const PCMDHOST pCmd, uint32_t uNumTests) +{ + int rc = VINF_SUCCESS; + if (!RT_VALID_PTR(pTable->pfnHostCall)) + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid pfnHostCall() pointer\n"); + rc = VERR_INVALID_POINTER; + } + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; (i < uNumTests) && RT_SUCCESS(rc); i++) + { + RTTestPrintf(g_hTest, RTTESTLVL_INFO, "Testing #%u (cmd: %d, num_parms: %d, parms: 0x%p\n", + i, pCmd[i].cmd, pCmd[i].num_parms, pCmd[i].parms); + + if (pCmd[i].fNeedsClient) + { + int client_rc = pTable->pfnConnect(pTable->pvService, 1000 /* Client ID */, NULL /* pvClient */, 0, false); + if (RT_FAILURE(client_rc)) + rc = client_rc; + } + + if (RT_SUCCESS(rc)) + { + int host_rc = pTable->pfnHostCall(pTable->pvService, + pCmd[i].cmd, + pCmd[i].num_parms, + pCmd[i].parms); + if (host_rc != pCmd[i].rc) + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Host call test #%u returned with rc=%Rrc instead of rc=%Rrc\n", + i, host_rc, pCmd[i].rc); + rc = host_rc; + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_PARAMETER; + } + + if (pCmd[i].fNeedsClient) + { + int client_rc = pTable->pfnDisconnect(pTable->pvService, 1000 /* Client ID */, NULL /* pvClient */); + if (RT_SUCCESS(rc)) + rc = client_rc; + } + } + } + } + return rc; +} + +static int testHost(const VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestSub(g_hTest, "Testing host commands ..."); + + VBOXHGCMSVCPARM aParms[1]; + HGCMSvcSetU32(&aParms[0], 1000 /* Context ID */); + + CMDHOST aCmdHostAll[] = + { +#if 0 + /** No client connected. */ + { 1024 /* Not existing command */, 0, 0, false, VERR_NOT_FOUND }, + { -1 /* Invalid command */, 0, 0, false, VERR_NOT_FOUND }, + { HOST_CANCEL_PENDING_WAITS, 1024, 0, false, VERR_NOT_FOUND }, + { HOST_CANCEL_PENDING_WAITS, 0, &aParms[0], false, VERR_NOT_FOUND }, + + /** No client connected, valid command. */ + { HOST_CANCEL_PENDING_WAITS, 0, 0, false, VERR_NOT_FOUND }, + + /** Client connected, no parameters given. */ + { HOST_EXEC_SET_INPUT, 0 /* No parameters given */, 0, true, VERR_INVALID_PARAMETER }, + { 1024 /* Not existing command */, 0 /* No parameters given */, 0, true, VERR_INVALID_PARAMETER }, + { -1 /* Invalid command */, 0 /* No parameters given */, 0, true, VERR_INVALID_PARAMETER }, + + /** Client connected, valid parameters given. */ + { HOST_CANCEL_PENDING_WAITS, 0, 0, true, VINF_SUCCESS }, + { HOST_CANCEL_PENDING_WAITS, 1024, &aParms[0], true, VINF_SUCCESS }, + { HOST_CANCEL_PENDING_WAITS, 0, &aParms[0], true, VINF_SUCCESS}, +#endif + + /** Client connected, invalid parameters given. */ + { HOST_MSG_EXEC_CMD, 1024, 0, true, VERR_INVALID_POINTER }, + { HOST_MSG_EXEC_CMD, 1, 0, true, VERR_INVALID_POINTER }, + { HOST_MSG_EXEC_CMD, -1, 0, true, VERR_INVALID_POINTER }, + + /** Client connected, parameters given. */ + { HOST_MSG_CANCEL_PENDING_WAITS, 1, &aParms[0], true, VINF_SUCCESS }, + { HOST_MSG_EXEC_CMD, 1, &aParms[0], true, VINF_SUCCESS }, + { HOST_MSG_EXEC_SET_INPUT, 1, &aParms[0], true, VINF_SUCCESS }, + { HOST_MSG_EXEC_GET_OUTPUT, 1, &aParms[0], true, VINF_SUCCESS }, + + /** Client connected, unknown command + valid parameters given. */ + { -1, 1, &aParms[0], true, VINF_SUCCESS } + }; + + int rc = testHostCmd(pTable, &aCmdHostAll[0], RT_ELEMENTS(aCmdHostAll)); + RTTestSubDone(g_hTest); + return rc; +} + +static int testClient(const VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestSub(g_hTest, "Testing client commands ..."); + + int rc = pTable->pfnConnect(pTable->pvService, 1 /* Client ID */, NULL /* pvClient */, 0, false); + if (RT_SUCCESS(rc)) + { + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + /* No commands from host yet. */ + VBOXHGCMSVCPARM aParmsGuest[8]; + HGCMSvcSetU32(&aParmsGuest[0], 0 /* Msg type */); + HGCMSvcSetU32(&aParmsGuest[1], 0 /* Parameters */); + pTable->pfnCall(pTable->pvService, &callHandle, 1 /* Client ID */, NULL /* pvClient */, + GUEST_MSG_WAIT, 2, &aParmsGuest[0], 0); + RTTEST_CHECK_RC_RET(g_hTest, callHandle.rc, VINF_SUCCESS, callHandle.rc); + + /* Host: Add a dummy command. */ + VBOXHGCMSVCPARM aParmsHost[8]; + HGCMSvcSetU32(&aParmsHost[0], 1000 /* Context ID */); + HGCMSvcSetStr(&aParmsHost[1], "foo.bar"); + HGCMSvcSetStr(&aParmsHost[2], "baz"); + + rc = pTable->pfnHostCall(pTable->pvService, HOST_MSG_EXEC_CMD, 3, &aParmsHost[0]); + RTTEST_CHECK_RC_RET(g_hTest, rc, VINF_SUCCESS, rc); + + /* Client: Disconnect again. */ + int rc2 = pTable->pfnDisconnect(pTable->pvService, 1000 /* Client ID */, NULL /* pvClient */); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + RTTestSubDone(g_hTest); + return rc; +} + +/* + * Set environment variable "IPRT_TEST_MAX_LEVEL=all" to get more debug output! + */ +int main() +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstGuestControlSvc", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + /* Some host info. */ + RTTestIPrintf(RTTESTLVL_ALWAYS, "sizeof(void*)=%d\n", sizeof(void*)); + + /* Do the tests. */ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + RTTEST_CHECK_RC_RET(g_hTest, initTable(&svcTable, &svcHelpers), VINF_SUCCESS, 1); + + do + { + RTTESTI_CHECK_RC_BREAK(VBoxHGCMSvcLoad(&svcTable), VINF_SUCCESS); + + RTTESTI_CHECK_RC_BREAK(testHost(&svcTable), VINF_SUCCESS); + + RTTESTI_CHECK_RC_BREAK(svcTable.pfnUnload(svcTable.pvService), VINF_SUCCESS); + + RTTESTI_CHECK_RC_BREAK(VBoxHGCMSvcLoad(&svcTable), VINF_SUCCESS); + + RTTESTI_CHECK_RC_BREAK(testClient(&svcTable), VINF_SUCCESS); + + RTTESTI_CHECK_RC_BREAK(svcTable.pfnUnload(svcTable.pvService), VINF_SUCCESS); + + } while (0); + + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/HostServices/GuestProperties/Makefile.kmk b/src/VBox/HostServices/GuestProperties/Makefile.kmk new file mode 100644 index 00000000..80e5c63b --- /dev/null +++ b/src/VBox/HostServices/GuestProperties/Makefile.kmk @@ -0,0 +1,58 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Info Services Host Service. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The shared folder service DLL. +# +DLLS += VBoxGuestPropSvc +VBoxGuestPropSvc_TEMPLATE = VBoxR3Dll +VBoxGuestPropSvc_NAME.os2 = VBoxSIS +VBoxGuestPropSvc_DEFS = VBOX_WITH_HGCM +VBoxGuestPropSvc_INCS = $(PATH_ROOT)/src/VBox/Main/include +VBoxGuestPropSvc_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxGuestPropSvc_SOURCES = \ + VBoxGuestPropSvc.cpp + +VBoxGuestPropSvc_SOURCES.win = \ + VBoxGuestPropSvc.rc + +VBoxGuestPropSvc_LIBS = \ + $(LIB_RUNTIME) + +VBoxGuestPropSvc_LDFLAGS.darwin = \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxGuestPropSvc.dylib + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.cpp b/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.cpp new file mode 100644 index 00000000..0336999b --- /dev/null +++ b/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.cpp @@ -0,0 +1,1885 @@ +/* $Id: VBoxGuestPropSvc.cpp $ */ +/** @file + * Guest Property Service: Host service entry points. + */ + +/* + * Copyright (C) 2008-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_properties Guest Property HGCM Service + * + * This HGCM service allows the guest to set and query values in a property + * store on the host. The service proxies the guest requests to the service + * owner on the host using a request callback provided by the owner, and is + * notified of changes to properties made by the host. It forwards these + * notifications to clients in the guest which have expressed interest and + * are waiting for notification. + * + * The service currently consists of two threads. One of these is the main + * HGCM service thread which deals with requests from the guest and from the + * host. The second thread sends the host asynchronous notifications of + * changes made by the guest and deals with notification timeouts. + * + * Guest requests to wait for notification are added to a list of open + * notification requests and completed when a corresponding guest property + * is changed or when the request times out. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_HGCM +#include <VBox/HostServices/GuestPropertySvc.h> + +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/cpp/autores.h> +#include <iprt/cpp/utils.h> +#include <iprt/cpp/ministring.h> +#include <VBox/err.h> +#include <VBox/hgcmsvc.h> +#include <iprt/mem.h> +#include <iprt/req.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/vmm/dbgf.h> +#include <VBox/version.h> +#include <VBox/AssertGuest.h> + +#include <list> + + +namespace guestProp { + +/** + * Structure for holding a property + */ +struct Property +{ + /** The string space core record. */ + RTSTRSPACECORE mStrCore; + /** The name of the property */ + RTCString mName; + /** The property value */ + RTCString mValue; + /** The timestamp of the property */ + uint64_t mTimestamp; + /** The property flags */ + uint32_t mFlags; + + /** Default constructor */ + Property() : mTimestamp(0), mFlags(GUEST_PROP_F_NILFLAG) + { + RT_ZERO(mStrCore); + } + /** Constructor with const char * */ + Property(const char *pcszName, const char *pcszValue, uint64_t nsTimestamp, uint32_t u32Flags) + : mName(pcszName) + , mValue(pcszValue) + , mTimestamp(nsTimestamp) + , mFlags(u32Flags) + { + RT_ZERO(mStrCore); + mStrCore.pszString = mName.c_str(); + } + /** Constructor with std::string */ + Property(RTCString const &rName, RTCString const &rValue, uint64_t nsTimestamp, uint32_t fFlags) + : mName(rName) + , mValue(rValue) + , mTimestamp(nsTimestamp) + , mFlags(fFlags) + {} + + /** Does the property name match one of a set of patterns? */ + bool Matches(const char *pszPatterns) const + { + return ( pszPatterns[0] == '\0' /* match all */ + || RTStrSimplePatternMultiMatch(pszPatterns, RTSTR_MAX, + mName.c_str(), RTSTR_MAX, + NULL) + ); + } + + /** Are two properties equal? */ + bool operator==(const Property &prop) + { + if (mTimestamp != prop.mTimestamp) + return false; + if (mFlags != prop.mFlags) + return false; + if (mName != prop.mName) + return false; + if (mValue != prop.mValue) + return false; + return true; + } + + /* Is the property nil? */ + bool isNull() + { + return mName.isEmpty(); + } +}; +/** The properties list type */ +typedef std::list <Property> PropertyList; + +/** + * Structure for holding an uncompleted guest call + */ +struct GuestCall +{ + uint32_t u32ClientId; + /** The call handle */ + VBOXHGCMCALLHANDLE mHandle; + /** The function that was requested */ + uint32_t mFunction; + /** Number of call parameters. */ + uint32_t mParmsCnt; + /** The call parameters */ + VBOXHGCMSVCPARM *mParms; + /** The default return value, used for passing warnings */ + int mRc; + + /** The standard constructor */ + GuestCall(void) : u32ClientId(0), mFunction(0), mParmsCnt(0) {} + /** The normal constructor */ + GuestCall(uint32_t aClientId, VBOXHGCMCALLHANDLE aHandle, uint32_t aFunction, + uint32_t aParmsCnt, VBOXHGCMSVCPARM aParms[], int aRc) + : u32ClientId(aClientId), mHandle(aHandle), mFunction(aFunction), + mParmsCnt(aParmsCnt), mParms(aParms), mRc(aRc) {} +}; +/** The guest call list type */ +typedef std::list <GuestCall> CallList; + +/** + * Class containing the shared information service functionality. + */ +class Service : public RTCNonCopyable +{ +private: + /** Type definition for use in callback functions */ + typedef Service SELF; + /** HGCM helper functions. */ + PVBOXHGCMSVCHELPERS mpHelpers; + /** Global flags for the service */ + uint32_t mfGlobalFlags; + /** The property string space handle. */ + RTSTRSPACE mhProperties; + /** The number of properties. */ + unsigned mcProperties; + /** The list of property changes for guest notifications; + * only used for timestamp tracking in notifications at the moment */ + PropertyList mGuestNotifications; + /** The list of outstanding guest notification calls */ + CallList mGuestWaiters; + /** @todo we should have classes for thread and request handler thread */ + /** 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; + /** The previous timestamp. + * This is used by getCurrentTimestamp() to decrease the chance of + * generating duplicate timestamps. */ + uint64_t mPrevTimestamp; + /** The number of consecutive timestamp adjustments that we've made. + * Together with mPrevTimestamp, this defines a set of obsolete timestamp + * values: {(mPrevTimestamp - mcTimestampAdjustments), ..., mPrevTimestamp} */ + uint64_t mcTimestampAdjustments; + /** For helping setting host version properties _after_ restoring VMs. */ + bool m_fSetHostVersionProps; + + /** + * Get the next property change notification from the queue of saved + * notification based on the timestamp of the last notification seen. + * Notifications will only be reported if the property name matches the + * pattern given. + * + * @returns iprt status value + * @returns VWRN_NOT_FOUND if the last notification was not found in the queue + * @param pszPatterns the patterns to match the property name against + * @param nsTimestamp the timestamp of the last notification + * @param pProp where to return the property found. If none is + * found this will be set to nil. + * @throws nothing + * @thread HGCM + */ + int getOldNotification(const char *pszPatterns, uint64_t nsTimestamp, Property *pProp) + { + AssertPtrReturn(pszPatterns, VERR_INVALID_POINTER); + /* Zero means wait for a new notification. */ + AssertReturn(nsTimestamp != 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pProp, VERR_INVALID_POINTER); + int rc = getOldNotificationInternal(pszPatterns, nsTimestamp, pProp); + +#ifdef VBOX_STRICT + /* + * ENSURE that pProp is the first event in the notification queue that: + * - Appears later than nsTimestamp + * - Matches the pszPatterns + */ + /** @todo r=bird: This incorrectly ASSUMES that mTimestamp is unique. + * The timestamp resolution can be very coarse on windows for instance. */ + PropertyList::const_iterator it = mGuestNotifications.begin(); + for (; it != mGuestNotifications.end() + && it->mTimestamp != nsTimestamp; ++it) + { /*nothing*/ } + if (it == mGuestNotifications.end()) /* Not found */ + it = mGuestNotifications.begin(); + else + ++it; /* Next event */ + for (; it != mGuestNotifications.end() + && it->mTimestamp != pProp->mTimestamp; ++it) + Assert(!it->Matches(pszPatterns)); + if (pProp->mTimestamp != 0) + { + Assert(*pProp == *it); + Assert(pProp->Matches(pszPatterns)); + } +#endif /* VBOX_STRICT */ + return rc; + } + + /** + * Check whether we have permission to change a property. + * + * @returns Strict VBox status code. + * @retval VINF_SUCCESS if we do. + * @retval VERR_PERMISSION_DENIED if the value is read-only for the requesting + * side. + * @retval VINF_PERMISSION_DENIED if the side is globally marked read-only. + * + * @param fFlags the flags on the property in question + * @param isGuest is the guest or the host trying to make the change? + */ + int checkPermission(uint32_t fFlags, bool isGuest) + { + if (fFlags & (isGuest ? GUEST_PROP_F_RDONLYGUEST : GUEST_PROP_F_RDONLYHOST)) + return VERR_PERMISSION_DENIED; + if (isGuest && (mfGlobalFlags & GUEST_PROP_F_RDONLYGUEST)) + return VINF_PERMISSION_DENIED; + return VINF_SUCCESS; + } + + /** + * Check whether the property name is reserved for host changes only. + * + * @returns Boolean true (host reserved) or false (available to guest). + * + * @param pszName The property name to check. + */ + bool checkHostReserved(const char *pszName) + { + if (RTStrStartsWith(pszName, "/VirtualBox/GuestAdd/VBoxService/")) + return true; + if (RTStrStartsWith(pszName, "/VirtualBox/GuestAdd/PAM/")) + return true; + if (RTStrStartsWith(pszName, "/VirtualBox/GuestAdd/Greeter/")) + return true; + if (RTStrStartsWith(pszName, "/VirtualBox/GuestAdd/SharedFolders/")) + return true; + if (RTStrStartsWith(pszName, "/VirtualBox/HostInfo/")) + return true; + if (RTStrStartsWith(pszName, "/VirtualBox/VMInfo/")) + return true; + return false; + } + + /** + * Gets a property. + * + * @returns Pointer to the property if found, NULL if not. + * + * @param pszName The name of the property to get. + */ + Property *getPropertyInternal(const char *pszName) + { + return (Property *)RTStrSpaceGet(&mhProperties, pszName); + } + +public: + explicit Service(PVBOXHGCMSVCHELPERS pHelpers) + : mpHelpers(pHelpers) + , mfGlobalFlags(GUEST_PROP_F_NILFLAG) + , mhProperties(NULL) + , mcProperties(0) + , mpfnHostCallback(NULL) + , mpvHostData(NULL) + , mPrevTimestamp(0) + , mcTimestampAdjustments(0) + , m_fSetHostVersionProps(false) + , mhThreadNotifyHost(NIL_RTTHREAD) + , mhReqQNotifyHost(NIL_RTREQQUEUE) + { } + + /** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnUnload} + * Simply deletes the service object + */ + static DECLCALLBACK(int) svcUnload(void *pvService) + { + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + SELF *pSelf = reinterpret_cast<SELF *>(pvService); + int rc = pSelf->uninit(); + AssertRC(rc); + if (RT_SUCCESS(rc)) + delete pSelf; + return rc; + } + + /** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnConnect} + * Stub implementation of pfnConnect. + */ + static DECLCALLBACK(int) svcConnect(void * /* pvService */, + uint32_t /* u32ClientID */, + void * /* pvClient */, + uint32_t /*fRequestor*/, + bool /*fRestoring*/) + { + return VINF_SUCCESS; + } + + static DECLCALLBACK(int) svcDisconnect(void *pvService, uint32_t idClient, void *pvClient); + + /** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnCall} + * Wraps to the call member function + */ + static DECLCALLBACK(void) svcCall(void * pvService, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32ClientID, + void *pvClient, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[], + uint64_t tsArrival) + { + AssertLogRelReturnVoid(RT_VALID_PTR(pvService)); + LogFlowFunc(("pvService=%p, callHandle=%p, u32ClientID=%u, pvClient=%p, u32Function=%u, cParms=%u, paParms=%p\n", pvService, callHandle, u32ClientID, pvClient, u32Function, cParms, paParms)); + SELF *pSelf = reinterpret_cast<SELF *>(pvService); + pSelf->call(callHandle, u32ClientID, pvClient, u32Function, cParms, paParms); + LogFlowFunc(("returning\n")); + RT_NOREF_PV(tsArrival); + } + + /** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnHostCall} + * Wraps to the hostCall member function + */ + static DECLCALLBACK(int) svcHostCall(void *pvService, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[]) + { + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + LogFlowFunc(("pvService=%p, u32Function=%u, cParms=%u, paParms=%p\n", pvService, u32Function, cParms, paParms)); + SELF *pSelf = reinterpret_cast<SELF *>(pvService); + int rc = pSelf->hostCall(u32Function, cParms, paParms); + LogFlowFunc(("rc=%Rrc\n", rc)); + return rc; + } + + /** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnRegisterExtension} + * Installs a host callback for notifications of property changes. + */ + static DECLCALLBACK(int) svcRegisterExtension(void *pvService, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) + { + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + SELF *pSelf = reinterpret_cast<SELF *>(pvService); + pSelf->mpfnHostCallback = pfnExtension; + pSelf->mpvHostData = pvExtension; + return VINF_SUCCESS; + } + + int setHostVersionProps(); + void incrementCounterProp(const char *pszName); + static DECLCALLBACK(void) svcNotify(void *pvService, HGCMNOTIFYEVENT enmEvent); + + int initialize(); + +private: + static DECLCALLBACK(int) reqThreadFn(RTTHREAD ThreadSelf, void *pvUser); + uint64_t getCurrentTimestamp(void); + int setPropertyBlock(uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int getProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int setProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool isGuest); + int setPropertyInternal(const char *pcszName, const char *pcszValue, uint32_t fFlags, uint64_t nsTimestamp, + bool fIsGuest = false); + int delProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool isGuest); + int enumProps(uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int getNotification(uint32_t u32ClientId, VBOXHGCMCALLHANDLE callHandle, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int getOldNotificationInternal(const char *pszPattern, uint64_t nsTimestamp, Property *pProp); + int getNotificationWriteOut(uint32_t cParms, VBOXHGCMSVCPARM paParms[], Property const &prop, bool fWasDeleted); + int doNotifications(const char *pszProperty, uint64_t nsTimestamp); + int notifyHost(const char *pszName, const char *pszValue, uint64_t nsTimestamp, const char *pszFlags); + + void call(VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, + void *pvClient, uint32_t eFunction, uint32_t cParms, + VBOXHGCMSVCPARM paParms[]); + int hostCall(uint32_t eFunction, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int uninit(); + static DECLCALLBACK(void) dbgInfo(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs); + + /* Thread for handling host notifications. */ + RTTHREAD mhThreadNotifyHost; + /* Queue for handling requests for notifications. */ + RTREQQUEUE mhReqQNotifyHost; + static DECLCALLBACK(int) threadNotifyHost(RTTHREAD self, void *pvUser); + + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(Service); +}; + + +/** + * Gets the current timestamp. + * + * Since the RTTimeNow resolution can be very coarse, this method takes some + * simple steps to try avoid returning the same timestamp for two consecutive + * calls. Code like getOldNotification() more or less assumes unique + * timestamps. + * + * @returns Nanosecond timestamp. + */ +uint64_t Service::getCurrentTimestamp(void) +{ + RTTIMESPEC time; + uint64_t u64NanoTS = RTTimeSpecGetNano(RTTimeNow(&time)); + if (mPrevTimestamp - u64NanoTS > mcTimestampAdjustments) + mcTimestampAdjustments = 0; + else + { + mcTimestampAdjustments++; + u64NanoTS = mPrevTimestamp + 1; + } + this->mPrevTimestamp = u64NanoTS; + return u64NanoTS; +} + +/** + * Set a block of properties in the property registry, checking the validity + * of the arguments passed. + * + * @returns iprt status value + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @thread HGCM + */ +int Service::setPropertyBlock(uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + const char **papszNames; + const char **papszValues; + const char **papszFlags; + uint64_t *paNsTimestamps; + uint32_t cbDummy; + int rc = VINF_SUCCESS; + + /* + * Get and validate the parameters + */ + if ( cParms != 4 + || RT_FAILURE(HGCMSvcGetPv(&paParms[0], (void **)&papszNames, &cbDummy)) + || RT_FAILURE(HGCMSvcGetPv(&paParms[1], (void **)&papszValues, &cbDummy)) + || RT_FAILURE(HGCMSvcGetPv(&paParms[2], (void **)&paNsTimestamps, &cbDummy)) + || RT_FAILURE(HGCMSvcGetPv(&paParms[3], (void **)&papszFlags, &cbDummy)) + ) + rc = VERR_INVALID_PARAMETER; + /** @todo validate the array sizes... */ + else + { + for (unsigned i = 0; RT_SUCCESS(rc) && papszNames[i] != NULL; ++i) + { + if ( !RT_VALID_PTR(papszNames[i]) + || !RT_VALID_PTR(papszValues[i]) + || !RT_VALID_PTR(papszFlags[i]) + ) + rc = VERR_INVALID_POINTER; + else + { + uint32_t fFlagsIgn; + rc = GuestPropValidateFlags(papszFlags[i], &fFlagsIgn); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Add the properties. No way to roll back here. + */ + for (unsigned i = 0; papszNames[i] != NULL; ++i) + { + uint32_t fFlags; + rc = GuestPropValidateFlags(papszFlags[i], &fFlags); + AssertRCBreak(rc); + /* + * Handle names which are read-only for the guest. + */ + if (checkHostReserved(papszNames[i])) + fFlags |= GUEST_PROP_F_RDONLYGUEST; + + Property *pProp = getPropertyInternal(papszNames[i]); + if (pProp) + { + /* Update existing property. */ + rc = pProp->mValue.assignNoThrow(papszValues[i]); + AssertRCBreak(rc); + pProp->mTimestamp = paNsTimestamps[i]; + pProp->mFlags = fFlags; + } + else + { + /* Create a new property */ + try + { + pProp = new Property(papszNames[i], papszValues[i], paNsTimestamps[i], fFlags); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + if (RTStrSpaceInsert(&mhProperties, &pProp->mStrCore)) + mcProperties++; + else + { + delete pProp; + rc = VERR_INTERNAL_ERROR_3; + AssertFailedBreak(); + } + } + } + } + } + + return rc; +} + +/** + * Retrieve a value from the property registry by name, checking the validity + * of the arguments passed. If the guest has not allocated enough buffer + * space for the value then we return VERR_OVERFLOW and set the size of the + * buffer needed in the "size" HGCM parameter. If the name was not found at + * all, we return VERR_NOT_FOUND. + * + * @returns iprt status value + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @thread HGCM + */ +int Service::getProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc; + const char *pcszName = NULL; /* shut up gcc */ + char *pchBuf = NULL; /* shut up MSC */ + uint32_t cbName; + uint32_t cbBuf = 0; /* shut up MSC */ + + /* + * Get and validate the parameters + */ + LogFlowThisFunc(("\n")); + if ( cParms != 4 /* Hardcoded value as the next lines depend on it. */ + || RT_FAILURE(HGCMSvcGetCStr(&paParms[0], &pcszName, &cbName)) /* name */ + || RT_FAILURE(HGCMSvcGetBuf(&paParms[1], (void **)&pchBuf, &cbBuf)) /* buffer */ + ) + rc = VERR_INVALID_PARAMETER; + else + rc = GuestPropValidateName(pcszName, cbName); + if (RT_FAILURE(rc)) + { + LogFlowThisFunc(("rc = %Rrc\n", rc)); + return rc; + } + + /* + * Read and set the values we will return + */ + + /* Get the property. */ + Property *pProp = getPropertyInternal(pcszName); + if (pProp) + { + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + rc = GuestPropWriteFlags(pProp->mFlags, szFlags); + if (RT_SUCCESS(rc)) + { + /* Check that the buffer is big enough */ + size_t const cbFlags = strlen(szFlags) + 1; + size_t const cbValue = pProp->mValue.length() + 1; + size_t const cbNeeded = cbValue + cbFlags; + HGCMSvcSetU32(&paParms[3], (uint32_t)cbNeeded); + if (cbBuf >= cbNeeded) + { + /* Write the value, flags and timestamp */ + memcpy(pchBuf, pProp->mValue.c_str(), cbValue); + memcpy(pchBuf + cbValue, szFlags, cbFlags); + + HGCMSvcSetU64(&paParms[2], pProp->mTimestamp); + + /* + * Done! Do exit logging and return. + */ + Log2(("Queried string %s, value=%s, timestamp=%lld, flags=%s\n", + pcszName, pProp->mValue.c_str(), pProp->mTimestamp, szFlags)); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + else + rc = VERR_NOT_FOUND; + + LogFlowThisFunc(("rc = %Rrc (%s)\n", rc, pcszName)); + return rc; +} + +/** + * Set a value in the property registry by name, checking the validity + * of the arguments passed. + * + * @returns iprt status value + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @param isGuest is this call coming from the guest (or the host)? + * @throws std::bad_alloc if an out of memory condition occurs + * @thread HGCM + */ +int Service::setProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool isGuest) +{ + const char *pcszName = NULL; /* shut up gcc */ + const char *pcszValue = NULL; /* ditto */ + const char *pcszFlags = NULL; + uint32_t cbName = 0; /* ditto */ + uint32_t cbValue = 0; /* ditto */ + uint32_t cbFlags = 0; + uint32_t fFlags = GUEST_PROP_F_NILFLAG; + uint64_t u64TimeNano = getCurrentTimestamp(); + + LogFlowThisFunc(("\n")); + + /* + * General parameter correctness checking. + */ + int rc = VINF_SUCCESS; + if ( cParms < 2 /* Hardcoded value as the next lines depend on it these range checks. */ + || cParms > 3 + || RT_FAILURE(HGCMSvcGetCStr(&paParms[0], &pcszName, &cbName)) /* name */ + || RT_FAILURE(HGCMSvcGetCStr(&paParms[1], &pcszValue, &cbValue)) /* value */ + || ( cParms == 3 + && RT_FAILURE(HGCMSvcGetCStr(&paParms[2], &pcszFlags, &cbFlags)) /* flags */ + ) + ) + rc = VERR_INVALID_PARAMETER; + + /* + * Check the values passed in the parameters for correctness. + */ + if (RT_SUCCESS(rc)) + rc = GuestPropValidateName(pcszName, cbName); + if (RT_SUCCESS(rc)) + rc = GuestPropValidateValue(pcszValue, cbValue); + if (cParms == 3 && RT_SUCCESS(rc)) + rc = GuestPropValidateFlags(pcszFlags, &fFlags); + if (RT_FAILURE(rc)) + { + LogFlowThisFunc(("rc = %Rrc\n", rc)); + return rc; + } + + /* + * Hand it over to the internal setter method. + */ + rc = setPropertyInternal(pcszName, pcszValue, fFlags, u64TimeNano, isGuest); + + LogFlowThisFunc(("%s=%s, rc=%Rrc\n", pcszName, pcszValue, rc)); + return rc; +} + +/** + * Internal property setter. + * + * @returns VBox status code. + * @param pcszName The property name. + * @param pcszValue The new value. + * @param fFlags The flags. + * @param nsTimestamp The timestamp. + * @param fIsGuest Is it the guest calling. + * @throws std::bad_alloc if an out of memory condition occurs + * @thread HGCM + */ +int Service::setPropertyInternal(const char *pcszName, const char *pcszValue, uint32_t fFlags, uint64_t nsTimestamp, + bool fIsGuest /*= false*/) +{ + /* + * If the property already exists, check its flags to see if we are allowed + * to change it. + */ + Property *pProp = getPropertyInternal(pcszName); + int rc = checkPermission(pProp ? pProp->mFlags : GUEST_PROP_F_NILFLAG, fIsGuest); + /* + * Handle names which are read-only for the guest. + */ + if (rc == VINF_SUCCESS && checkHostReserved(pcszName)) + { + if (fIsGuest) + rc = VERR_PERMISSION_DENIED; + else + fFlags |= GUEST_PROP_F_RDONLYGUEST; + } + if (rc == VINF_SUCCESS) + { + /* + * Set the actual value + */ + if (pProp) + { + rc = pProp->mValue.assignNoThrow(pcszValue); + if (RT_SUCCESS(rc)) + { + pProp->mTimestamp = nsTimestamp; + pProp->mFlags = fFlags; + } + } + else if (mcProperties < GUEST_PROP_MAX_PROPS) + { + try + { + /* Create a new string space record. */ + pProp = new Property(pcszName, pcszValue, nsTimestamp, fFlags); + AssertPtr(pProp); + + if (RTStrSpaceInsert(&mhProperties, &pProp->mStrCore)) + mcProperties++; + else + { + AssertFailed(); + delete pProp; + + rc = VERR_ALREADY_EXISTS; + } + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_TOO_MUCH_DATA; + + /* + * Send a notification to the guest and host and return. + */ + // if (fIsGuest) /* Notify the host even for properties that the host + // * changed. Less efficient, but ensures consistency. */ + int rc2 = doNotifications(pcszName, nsTimestamp); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowThisFunc(("%s=%s, rc=%Rrc\n", pcszName, pcszValue, rc)); + return rc; +} + + +/** + * Remove a value in the property registry by name, checking the validity + * of the arguments passed. + * + * @returns iprt status value + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @param isGuest is this call coming from the guest (or the host)? + * @thread HGCM + */ +int Service::delProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool isGuest) +{ + int rc; + const char *pcszName = NULL; /* shut up gcc */ + uint32_t cbName; + + LogFlowThisFunc(("\n")); + + /* + * Check the user-supplied parameters. + */ + if ( (cParms == 1) /* Hardcoded value as the next lines depend on it. */ + && RT_SUCCESS(HGCMSvcGetCStr(&paParms[0], &pcszName, &cbName)) /* name */ + ) + rc = GuestPropValidateName(pcszName, cbName); + else + rc = VERR_INVALID_PARAMETER; + if (RT_FAILURE(rc)) + { + LogFlowThisFunc(("rc=%Rrc\n", rc)); + return rc; + } + + /* + * If the property exists, check its flags to see if we are allowed + * to change it. + */ + Property *pProp = getPropertyInternal(pcszName); + if (pProp) + rc = checkPermission(pProp->mFlags, isGuest); + + /* + * And delete the property if all is well. + */ + if (rc == VINF_SUCCESS && pProp) + { + uint64_t nsTimestamp = getCurrentTimestamp(); + PRTSTRSPACECORE pStrCore = RTStrSpaceRemove(&mhProperties, pProp->mStrCore.pszString); + AssertPtr(pStrCore); NOREF(pStrCore); + mcProperties--; + delete pProp; + // if (isGuest) /* Notify the host even for properties that the host + // * changed. Less efficient, but ensures consistency. */ + int rc2 = doNotifications(pcszName, nsTimestamp); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowThisFunc(("%s: rc=%Rrc\n", pcszName, rc)); + return rc; +} + +/** + * Enumeration data shared between enumPropsCallback and Service::enumProps. + */ +typedef struct ENUMDATA +{ + const char *pszPattern; /**< The pattern to match properties against. */ + char *pchCur; /**< The current buffer postion. */ + size_t cbLeft; /**< The amount of available buffer space. */ + size_t cbNeeded; /**< The amount of needed buffer space. */ +} ENUMDATA; + +/** + * @callback_method_impl{FNRTSTRSPACECALLBACK} + */ +static DECLCALLBACK(int) enumPropsCallback(PRTSTRSPACECORE pStr, void *pvUser) +{ + Property *pProp = (Property *)pStr; + ENUMDATA *pEnum = (ENUMDATA *)pvUser; + + /* Included in the enumeration? */ + if (!pProp->Matches(pEnum->pszPattern)) + return 0; + + /* Convert the non-string members into strings. */ + char szTimestamp[256]; + size_t const cbTimestamp = RTStrFormatNumber(szTimestamp, pProp->mTimestamp, 10, 0, 0, 0) + 1; + + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + int rc = GuestPropWriteFlags(pProp->mFlags, szFlags); + if (RT_FAILURE(rc)) + return rc; + size_t const cbFlags = strlen(szFlags) + 1; + + /* Calculate the buffer space requirements. */ + size_t const cbName = pProp->mName.length() + 1; + size_t const cbValue = pProp->mValue.length() + 1; + size_t const cbRequired = cbName + cbValue + cbTimestamp + cbFlags; + pEnum->cbNeeded += cbRequired; + + /* Sufficient buffer space? */ + if (cbRequired > pEnum->cbLeft) + { + pEnum->cbLeft = 0; + return 0; /* don't quit */ + } + pEnum->cbLeft -= cbRequired; + + /* Append the property to the buffer. */ + char *pchCur = pEnum->pchCur; + pEnum->pchCur += cbRequired; + + memcpy(pchCur, pProp->mName.c_str(), cbName); + pchCur += cbName; + + memcpy(pchCur, pProp->mValue.c_str(), cbValue); + pchCur += cbValue; + + memcpy(pchCur, szTimestamp, cbTimestamp); + pchCur += cbTimestamp; + + memcpy(pchCur, szFlags, cbFlags); + pchCur += cbFlags; + + Assert(pchCur == pEnum->pchCur); + return 0; +} + +/** + * Enumerate guest properties by mask, checking the validity + * of the arguments passed. + * + * @returns iprt status value + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @thread HGCM + */ +int Service::enumProps(uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + + /* + * Get the HGCM function arguments. + */ + char const *pchPatterns = NULL; + char *pchBuf = NULL; + uint32_t cbPatterns = 0; + uint32_t cbBuf = 0; + LogFlowThisFunc(("\n")); + if ( (cParms != 3) /* Hardcoded value as the next lines depend on it. */ + || RT_FAILURE(HGCMSvcGetCStr(&paParms[0], &pchPatterns, &cbPatterns)) /* patterns */ + || RT_FAILURE(HGCMSvcGetBuf(&paParms[1], (void **)&pchBuf, &cbBuf)) /* return buffer */ + ) + rc = VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc) && cbPatterns > GUEST_PROP_MAX_PATTERN_LEN) + rc = VERR_TOO_MUCH_DATA; + + /* + * First repack the patterns into the format expected by RTStrSimplePatternMatch() + */ + char szPatterns[GUEST_PROP_MAX_PATTERN_LEN]; + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < cbPatterns - 1; ++i) + { + char ch = pchPatterns[i]; + if (pchPatterns[i] != '\0') + { /* likely*/ } + else + { + /* Since the RTStrValidateEncodingEx call in HGCMSvcGetCStr stops at the + first terminator, we have to validate all subsequent pattern strings. */ + rc = RTStrValidateEncodingEx(&pchPatterns[i + 1], cbPatterns - i -1, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); + ASSERT_GUEST_RC_BREAK(rc); + ch = '|'; + } + szPatterns[i] = ch; + } + szPatterns[cbPatterns - 1] = '\0'; + } + + /* + * Next enumerate into the buffer. + */ + if (RT_SUCCESS(rc)) + { + ENUMDATA EnumData; + EnumData.pszPattern = szPatterns; + EnumData.pchCur = pchBuf; + EnumData.cbLeft = cbBuf; + EnumData.cbNeeded = 0; + rc = RTStrSpaceEnumerate(&mhProperties, enumPropsCallback, &EnumData); + AssertRCSuccess(rc); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU32(&paParms[2], (uint32_t)(EnumData.cbNeeded + 4)); + if (EnumData.cbLeft >= 4) + { + /* The final terminators. */ + EnumData.pchCur[0] = '\0'; + EnumData.pchCur[1] = '\0'; + EnumData.pchCur[2] = '\0'; + EnumData.pchCur[3] = '\0'; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + + return rc; +} + + +/** Helper query used by getOldNotification + * @throws nothing + */ +int Service::getOldNotificationInternal(const char *pszPatterns, uint64_t nsTimestamp, Property *pProp) +{ + /* We count backwards, as the guest should normally be querying the + * most recent events. */ + int rc = VWRN_NOT_FOUND; + PropertyList::reverse_iterator it = mGuestNotifications.rbegin(); + for (; it != mGuestNotifications.rend(); ++it) + if (it->mTimestamp == nsTimestamp) + { + rc = VINF_SUCCESS; + break; + } + + /* Now look for an event matching the patterns supplied. The base() + * member conveniently points to the following element. */ + PropertyList::iterator base = it.base(); + for (; base != mGuestNotifications.end(); ++base) + if (base->Matches(pszPatterns)) + { + try + { + *pProp = *base; + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + return rc; + } + *pProp = Property(); + return rc; +} + + +/** Helper query used by getNotification */ +int Service::getNotificationWriteOut(uint32_t cParms, VBOXHGCMSVCPARM paParms[], Property const &rProp, bool fWasDeleted) +{ + AssertReturn(cParms == 4, VERR_INVALID_PARAMETER); /* Basic sanity checking. */ + + /* Format the data to write to the buffer. */ + char *pchBuf; + uint32_t cbBuf; + int rc = HGCMSvcGetBuf(&paParms[2], (void **)&pchBuf, &cbBuf); + if (RT_SUCCESS(rc)) + { + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + rc = GuestPropWriteFlags(rProp.mFlags, szFlags); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&paParms[1], rProp.mTimestamp); + + size_t const cbFlags = strlen(szFlags) + 1; + size_t const cbName = rProp.mName.length() + 1; + size_t const cbValue = rProp.mValue.length() + 1; + size_t const cbWasDeleted = 2; + size_t const cbNeeded = cbName + cbValue + cbFlags + cbWasDeleted; + HGCMSvcSetU32(&paParms[3], (uint32_t)cbNeeded); + if (cbNeeded <= cbBuf) + { + /* Buffer layout: Name\0Value\0Flags\0fWasDeleted\0. */ + memcpy(pchBuf, rProp.mName.c_str(), cbName); + pchBuf += cbName; + memcpy(pchBuf, rProp.mValue.c_str(), cbValue); + pchBuf += cbValue; + memcpy(pchBuf, szFlags, cbFlags); + pchBuf += cbFlags; + *pchBuf++ = fWasDeleted ? '1' : '0'; + *pchBuf++ = '\0'; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + return rc; +} + + +/** + * Get the next guest notification. + * + * @returns iprt status value + * @param u32ClientId the client ID + * @param callHandle handle + * @param cParms the number of HGCM parameters supplied + * @param paParms the array of HGCM parameters + * @thread HGCM + * @throws nothing + */ +int Service::getNotification(uint32_t u32ClientId, VBOXHGCMCALLHANDLE callHandle, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + char *pszPatterns = NULL; /* shut up gcc */ + char *pchBuf; + uint32_t cchPatterns = 0; + uint32_t cbBuf = 0; + uint64_t nsTimestamp; + + /* + * Get the HGCM function arguments and perform basic verification. + */ + LogFlowThisFunc(("\n")); + if ( cParms != 4 /* Hardcoded value as the next lines depend on it. */ + || RT_FAILURE(HGCMSvcGetStr(&paParms[0], &pszPatterns, &cchPatterns)) /* patterns */ + || RT_FAILURE(HGCMSvcGetU64(&paParms[1], &nsTimestamp)) /* timestamp */ + || RT_FAILURE(HGCMSvcGetBuf(&paParms[2], (void **)&pchBuf, &cbBuf)) /* return buffer */ + ) + rc = VERR_INVALID_PARAMETER; + else + { + LogFlow(("pszPatterns=%s, nsTimestamp=%llu\n", pszPatterns, nsTimestamp)); + + /* + * If no timestamp was supplied or no notification was found in the queue + * of old notifications, enqueue the request in the waiting queue. + */ + Property prop; + if (RT_SUCCESS(rc) && nsTimestamp != 0) + rc = getOldNotification(pszPatterns, nsTimestamp, &prop); + if (RT_SUCCESS(rc)) + { + if (prop.isNull()) + { + /* + * Check if the client already had the same request. + * Complete the old request with an error in this case. + * Protection against clients, which cancel and resubmits requests. + */ + uint32_t cPendingWaits = 0; + CallList::iterator it = mGuestWaiters.begin(); + while (it != mGuestWaiters.end()) + { + if (u32ClientId == it->u32ClientId) + { + const char *pszPatternsExisting; + uint32_t cchPatternsExisting; + int rc3 = HGCMSvcGetCStr(&it->mParms[0], &pszPatternsExisting, &cchPatternsExisting); + if ( RT_SUCCESS(rc3) + && RTStrCmp(pszPatterns, pszPatternsExisting) == 0) + { + /* Complete the old request. */ + mpHelpers->pfnCallComplete(it->mHandle, VERR_INTERRUPTED); + it = mGuestWaiters.erase(it); + } + else if (mpHelpers->pfnIsCallCancelled(it->mHandle)) + { + /* Cleanup cancelled request. */ + mpHelpers->pfnCallComplete(it->mHandle, VERR_INTERRUPTED); + it = mGuestWaiters.erase(it); + } + else + { + /** @todo check if cancelled. */ + cPendingWaits++; + ++it; + } + } + else + ++it; + } + + if (cPendingWaits < GUEST_PROP_MAX_GUEST_CONCURRENT_WAITS) + { + try + { + mGuestWaiters.push_back(GuestCall(u32ClientId, callHandle, GUEST_PROP_FN_GET_NOTIFICATION, + cParms, paParms, rc)); + rc = VINF_HGCM_ASYNC_EXECUTE; + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + } + else + { + LogFunc(("Too many pending waits already!\n")); + rc = VERR_OUT_OF_RESOURCES; + } + } + /* + * Otherwise reply at once with the enqueued notification we found. + */ + else + { + int rc2 = getNotificationWriteOut(cParms, paParms, prop, !getPropertyInternal(prop.mName.c_str())); + if (RT_FAILURE(rc2)) + rc = rc2; + } + } + } + + LogFlowThisFunc(("returning rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Notify the service owner and the guest that a property has been + * added/deleted/changed + * + * @param pszProperty The name of the property which has changed. + * @param nsTimestamp The time at which the change took place. + * @throws nothing. + * @thread HGCM service + */ +int Service::doNotifications(const char *pszProperty, uint64_t nsTimestamp) +{ + AssertPtrReturn(pszProperty, VERR_INVALID_POINTER); + LogFlowThisFunc(("pszProperty=%s, nsTimestamp=%llu\n", pszProperty, nsTimestamp)); + /* Ensure that our timestamp is different to the last one. */ + if ( !mGuestNotifications.empty() + && nsTimestamp == mGuestNotifications.back().mTimestamp) + ++nsTimestamp; + + /* + * Don't keep too many changes around. + */ + if (mGuestNotifications.size() >= GUEST_PROP_MAX_GUEST_NOTIFICATIONS) + mGuestNotifications.pop_front(); + + /* + * Try to find the property. Create a change event if we find it and a + * delete event if we do not. + */ + Property prop; + int rc = prop.mName.assignNoThrow(pszProperty); + AssertRCReturn(rc, rc); + prop.mTimestamp = nsTimestamp; + /* prop is currently a delete event for pszProperty */ + Property const * const pProp = getPropertyInternal(pszProperty); + if (pProp) + { + /* Make prop into a change event. */ + rc = prop.mValue.assignNoThrow(pProp->mValue); + AssertRCReturn(rc, rc); + prop.mFlags = pProp->mFlags; + } + + /* Release guest waiters if applicable and add the event + * to the queue for guest notifications */ + CallList::iterator it = mGuestWaiters.begin(); + while (it != mGuestWaiters.end()) + { + const char *pszPatterns = NULL; + uint32_t cchPatterns; + int rc2; + + rc2 = HGCMSvcGetCStr(&it->mParms[0], &pszPatterns, &cchPatterns); + if (RT_FAILURE(rc2)) + { + LogRel(("doNotifications: failed to get match pattern for guest property notification request, rc=%Rrc\n", rc2)); + mpHelpers->pfnCallComplete(it->mHandle, VERR_INVALID_PARAMETER); + it = mGuestWaiters.erase(it); + } + else if (prop.Matches(pszPatterns)) + { + rc2 = getNotificationWriteOut(it->mParmsCnt, it->mParms, prop, !pProp); + if (RT_SUCCESS(rc2)) + rc2 = it->mRc; + mpHelpers->pfnCallComplete(it->mHandle, rc2); + it = mGuestWaiters.erase(it); + } + else + ++it; + } + + try + { + mGuestNotifications.push_back(prop); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if ( RT_SUCCESS(rc) + && mpfnHostCallback) + { + /* + * Host notifications - first case: if the property exists then send its + * current value + */ + if (pProp) + { + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + /* Send out a host notification */ + const char *pszValue = prop.mValue.c_str(); + rc = GuestPropWriteFlags(prop.mFlags, szFlags); + if (RT_SUCCESS(rc)) + rc = notifyHost(pszProperty, pszValue, nsTimestamp, szFlags); + } + /* + * Host notifications - second case: if the property does not exist then + * send the host an empty value + */ + else + { + /* Send out a host notification */ + rc = notifyHost(pszProperty, NULL, nsTimestamp, ""); + } + } + + LogFlowThisFunc(("returning rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(void) +notifyHostAsyncWorker(PFNHGCMSVCEXT pfnHostCallback, void *pvHostData, PGUESTPROPHOSTCALLBACKDATA pHostCallbackData) +{ + pfnHostCallback(pvHostData, 0 /*u32Function*/, (void *)pHostCallbackData, sizeof(GUESTPROPHOSTCALLBACKDATA)); + RTMemFree(pHostCallbackData); +} + +/** + * Notify the service owner that a property has been added/deleted/changed. + * @returns IPRT status value + * @param pszName the property name + * @param pszValue the new value, or NULL if the property was deleted + * @param nsTimestamp the time of the change + * @param pszFlags the new flags string + */ +int Service::notifyHost(const char *pszName, const char *pszValue, uint64_t nsTimestamp, const char *pszFlags) +{ + LogFlowFunc(("pszName=%s, pszValue=%s, nsTimestamp=%llu, pszFlags=%s\n", pszName, pszValue, nsTimestamp, pszFlags)); + int rc; + + /* Allocate buffer for the callback data and strings. */ + size_t cbName = pszName? strlen(pszName): 0; + size_t cbValue = pszValue? strlen(pszValue): 0; + size_t cbFlags = pszFlags? strlen(pszFlags): 0; + size_t cbAlloc = sizeof(GUESTPROPHOSTCALLBACKDATA) + cbName + cbValue + cbFlags + 3; + PGUESTPROPHOSTCALLBACKDATA pHostCallbackData = (PGUESTPROPHOSTCALLBACKDATA)RTMemAlloc(cbAlloc); + if (pHostCallbackData) + { + uint8_t *pu8 = (uint8_t *)pHostCallbackData; + pu8 += sizeof(GUESTPROPHOSTCALLBACKDATA); + + pHostCallbackData->u32Magic = GUESTPROPHOSTCALLBACKDATA_MAGIC; + + pHostCallbackData->pcszName = (const char *)pu8; + memcpy(pu8, pszName, cbName); + pu8 += cbName; + *pu8++ = 0; + + /* NULL value means property was deleted. */ + pHostCallbackData->pcszValue = pszValue ? (const char *)pu8 : NULL; + memcpy(pu8, pszValue, cbValue); + pu8 += cbValue; + *pu8++ = 0; + + pHostCallbackData->u64Timestamp = nsTimestamp; + + pHostCallbackData->pcszFlags = (const char *)pu8; + memcpy(pu8, pszFlags, cbFlags); + pu8 += cbFlags; + *pu8++ = 0; + + rc = RTReqQueueCallEx(mhReqQNotifyHost, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)notifyHostAsyncWorker, 3, + mpfnHostCallback, mpvHostData, pHostCallbackData); + if (RT_FAILURE(rc)) + { + RTMemFree(pHostCallbackData); + } + } + else + { + rc = VERR_NO_MEMORY; + } + LogFlowFunc(("returning rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Handle an HGCM service call. + * @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 + */ +void Service::call (VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, + void * /* pvClient */, uint32_t eFunction, uint32_t cParms, + VBOXHGCMSVCPARM paParms[]) +{ + int rc; + LogFlowFunc(("u32ClientID = %d, fn = %d, cParms = %d, pparms = %p\n", + u32ClientID, eFunction, cParms, paParms)); + + switch (eFunction) + { + /* The guest wishes to read a property */ + case GUEST_PROP_FN_GET_PROP: + LogFlowFunc(("GET_PROP\n")); + rc = getProperty(cParms, paParms); + break; + + /* The guest wishes to set a property */ + case GUEST_PROP_FN_SET_PROP: + LogFlowFunc(("SET_PROP\n")); + rc = setProperty(cParms, paParms, true); + break; + + /* The guest wishes to set a property value */ + case GUEST_PROP_FN_SET_PROP_VALUE: + LogFlowFunc(("SET_PROP_VALUE\n")); + rc = setProperty(cParms, paParms, true); + break; + + /* The guest wishes to remove a configuration value */ + case GUEST_PROP_FN_DEL_PROP: + LogFlowFunc(("DEL_PROP\n")); + rc = delProperty(cParms, paParms, true); + break; + + /* The guest wishes to enumerate all properties */ + case GUEST_PROP_FN_ENUM_PROPS: + LogFlowFunc(("ENUM_PROPS\n")); + rc = enumProps(cParms, paParms); + break; + + /* The guest wishes to get the next property notification */ + case GUEST_PROP_FN_GET_NOTIFICATION: + LogFlowFunc(("GET_NOTIFICATION\n")); + rc = getNotification(u32ClientID, callHandle, cParms, paParms); + break; + + default: + rc = VERR_NOT_IMPLEMENTED; + } + LogFlowFunc(("rc = %Rrc\n", rc)); + if (rc != VINF_HGCM_ASYNC_EXECUTE) + mpHelpers->pfnCallComplete(callHandle, rc); +} + +/** + * Enumeration data shared between dbgInfoCallback and Service::dbgInfoShow. + */ +typedef struct ENUMDBGINFO +{ + PCDBGFINFOHLP pHlp; +} ENUMDBGINFO; + +static DECLCALLBACK(int) dbgInfoCallback(PRTSTRSPACECORE pStr, void *pvUser) +{ + Property *pProp = (Property *)pStr; + PCDBGFINFOHLP pHlp = ((ENUMDBGINFO *)pvUser)->pHlp; + + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + int rc = GuestPropWriteFlags(pProp->mFlags, szFlags); + if (RT_FAILURE(rc)) + RTStrPrintf(szFlags, sizeof(szFlags), "???"); + + pHlp->pfnPrintf(pHlp, "%s: '%s', %RU64", pProp->mName.c_str(), pProp->mValue.c_str(), pProp->mTimestamp); + if (strlen(szFlags)) + pHlp->pfnPrintf(pHlp, " (%s)", szFlags); + pHlp->pfnPrintf(pHlp, "\n"); + return 0; +} + + +/** + * Handler for debug info. + * + * @param pvUser user pointer. + * @param pHlp The info helper functions. + * @param pszArgs Arguments, ignored. + */ +DECLCALLBACK(void) Service::dbgInfo(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF1(pszArgs); + SELF *pSelf = reinterpret_cast<SELF *>(pvUser); + + ENUMDBGINFO EnumData = { pHlp }; + RTStrSpaceEnumerate(&pSelf->mhProperties, dbgInfoCallback, &EnumData); +} + + +/** + * Service call handler for the host. + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnHostCall} + * @thread hgcm + */ +int Service::hostCall (uint32_t eFunction, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc; + LogFlowFunc(("fn = %d, cParms = %d, pparms = %p\n", eFunction, cParms, paParms)); + + switch (eFunction) + { + /* The host wishes to set a block of properties */ + case GUEST_PROP_FN_HOST_SET_PROPS: + LogFlowFunc(("SET_PROPS_HOST\n")); + rc = setPropertyBlock(cParms, paParms); + break; + + /* The host wishes to read a configuration value */ + case GUEST_PROP_FN_HOST_GET_PROP: + LogFlowFunc(("GET_PROP_HOST\n")); + rc = getProperty(cParms, paParms); + break; + + /* The host wishes to set a configuration value */ + case GUEST_PROP_FN_HOST_SET_PROP: + LogFlowFunc(("SET_PROP_HOST\n")); + rc = setProperty(cParms, paParms, false); + break; + + /* The host wishes to set a configuration value */ + case GUEST_PROP_FN_HOST_SET_PROP_VALUE: + LogFlowFunc(("SET_PROP_VALUE_HOST\n")); + rc = setProperty(cParms, paParms, false); + break; + + /* The host wishes to remove a configuration value */ + case GUEST_PROP_FN_HOST_DEL_PROP: + LogFlowFunc(("DEL_PROP_HOST\n")); + rc = delProperty(cParms, paParms, false); + break; + + /* The host wishes to enumerate all properties */ + case GUEST_PROP_FN_HOST_ENUM_PROPS: + LogFlowFunc(("ENUM_PROPS\n")); + rc = enumProps(cParms, paParms); + break; + + /* The host wishes to set global flags for the service */ + case GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS: + LogFlowFunc(("SET_GLOBAL_FLAGS_HOST\n")); + if (cParms == 1) + { + uint32_t fFlags; + rc = HGCMSvcGetU32(&paParms[0], &fFlags); + if (RT_SUCCESS(rc)) + mfGlobalFlags = fFlags; + } + else + rc = VERR_INVALID_PARAMETER; + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnDisconnect} + */ +/*static*/ DECLCALLBACK(int) Service::svcDisconnect(void *pvService, uint32_t idClient, void *pvClient) +{ + RT_NOREF(pvClient); + LogFlowFunc(("idClient=%u\n", idClient)); + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertLogRelReturn(pThis, VERR_INVALID_POINTER); + + /* + * Complete all pending requests for this client. + */ + for (CallList::iterator It = pThis->mGuestWaiters.begin(); It != pThis->mGuestWaiters.end();) + { + GuestCall &rCurCall = *It; + if (rCurCall.u32ClientId != idClient) + ++It; + else + { + LogFlowFunc(("Completing call %u (%p)...\n", rCurCall.mFunction, rCurCall.mHandle)); + pThis->mpHelpers->pfnCallComplete(rCurCall.mHandle, VERR_INTERRUPTED); + It = pThis->mGuestWaiters.erase(It); + } + } + + return VINF_SUCCESS; +} + +/** + * Increments a counter property. + * + * It is assumed that this a transient property that is read-only to the guest. + * + * @param pszName The property name. + * @throws std::bad_alloc if an out of memory condition occurs + */ +void Service::incrementCounterProp(const char *pszName) +{ + /* Format the incremented value. */ + char szValue[64]; + Property *pProp = getPropertyInternal(pszName); + if (pProp) + { + uint64_t uValue = RTStrToUInt64(pProp->mValue.c_str()); + RTStrFormatU64(szValue, sizeof(szValue), uValue + 1, 10, 0, 0, 0); + } + else + { + szValue[0] = '1'; + szValue[1] = '\0'; + } + + /* Set it. */ + setPropertyInternal(pszName, szValue, GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, getCurrentTimestamp()); +} + +/** + * Sets the VBoxVer, VBoxVerExt and VBoxRev properties. + */ +int Service::setHostVersionProps() +{ + uint64_t nsTimestamp = getCurrentTimestamp(); + + /* Set the raw VBox version string as a guest property. Used for host/guest + * version comparison. */ + int rc = setPropertyInternal("/VirtualBox/HostInfo/VBoxVer", VBOX_VERSION_STRING_RAW, + GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, nsTimestamp); + AssertRCReturn(rc, rc); + + /* Set the full VBox version string as a guest property. Can contain vendor-specific + * information/branding and/or pre-release tags. */ + rc = setPropertyInternal("/VirtualBox/HostInfo/VBoxVerExt", VBOX_VERSION_STRING, + GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, nsTimestamp + 1); + AssertRCReturn(rc, rc); + + /* Set the VBox SVN revision as a guest property */ + rc = setPropertyInternal("/VirtualBox/HostInfo/VBoxRev", RTBldCfgRevisionStr(), + GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, nsTimestamp + 2); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnNotify} + */ +/*static*/ DECLCALLBACK(void) Service::svcNotify(void *pvService, HGCMNOTIFYEVENT enmEvent) +{ + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturnVoid(pThis); + + /* Make sure the host version properties have been touched and are + up-to-date after a restore: */ + if ( !pThis->m_fSetHostVersionProps + && (enmEvent == HGCMNOTIFYEVENT_RESUME || enmEvent == HGCMNOTIFYEVENT_POWER_ON)) + { + pThis->setHostVersionProps(); + pThis->m_fSetHostVersionProps = true; + } + + if (enmEvent == HGCMNOTIFYEVENT_RESUME) + pThis->incrementCounterProp("/VirtualBox/VMInfo/ResumeCounter"); + + if (enmEvent == HGCMNOTIFYEVENT_RESET) + pThis->incrementCounterProp("/VirtualBox/VMInfo/ResetCounter"); +} + + +/* static */ +DECLCALLBACK(int) Service::threadNotifyHost(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + Service *pThis = (Service *)pvUser; + int rc = VINF_SUCCESS; + + LogFlowFunc(("ENTER: %p\n", pThis)); + + for (;;) + { + rc = RTReqQueueProcess(pThis->mhReqQNotifyHost, RT_INDEFINITE_WAIT); + + AssertMsg(rc == VWRN_STATE_CHANGED, + ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", + rc)); + if (rc == VWRN_STATE_CHANGED) + { + break; + } + } + + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) wakeupNotifyHost(void) +{ + /* Returning a VWRN_* will cause RTReqQueueProcess return. */ + return VWRN_STATE_CHANGED; +} + + +int Service::initialize() +{ + /* + * Insert standard host properties. + */ + /* The host version will but updated again on power on or resume + (after restore), however we need the properties now for restored + guest notification/wait calls. */ + int rc = setHostVersionProps(); + AssertRCReturn(rc, rc); + + uint64_t nsNow = getCurrentTimestamp(); /* Must increment this for each property to avoid asserting in getOldNotification. */ + + /* Resume and reset counters. */ + rc = setPropertyInternal("/VirtualBox/VMInfo/ResetCounter", "0", GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, nsNow); + AssertRCReturn(rc, rc); + rc = setPropertyInternal("/VirtualBox/VMInfo/ResumeCounter", "0", GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, ++nsNow); + AssertRCReturn(rc, rc); + + /* Sysprep execution by VBoxService (host is allowed to change these). */ + rc = setPropertyInternal("/VirtualBox/HostGuest/SysprepExec", "", GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, ++nsNow); + AssertRCReturn(rc, rc); + rc = setPropertyInternal("/VirtualBox/HostGuest/SysprepArgs", "", GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_RDONLYGUEST, ++nsNow); + AssertRCReturn(rc, rc); + + + /* The host notification thread and queue. */ + rc = RTReqQueueCreate(&mhReqQNotifyHost); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&mhThreadNotifyHost, + threadNotifyHost, + this, + 0 /* default stack size */, + RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, + "GstPropNtfy"); + if (RT_SUCCESS(rc)) + { + /* Finally debug stuff (ignore failures): */ + HGCMSvcHlpInfoRegister(mpHelpers, "guestprops", "Display the guest properties", Service::dbgInfo, this); + return rc; + } + + RTReqQueueDestroy(mhReqQNotifyHost); + mhReqQNotifyHost = NIL_RTREQQUEUE; + } + return rc; +} + +/** + * @callback_method_impl{FNRTSTRSPACECALLBACK, Destroys Property.} + */ +static DECLCALLBACK(int) destroyProperty(PRTSTRSPACECORE pStr, void *pvUser) +{ + RT_NOREF(pvUser); + Property *pProp = RT_FROM_CPP_MEMBER(pStr, struct Property, mStrCore); /* clang objects to offsetof on non-POD.*/ + delete pProp; + return 0; +} + + +int Service::uninit() +{ + if (mpHelpers) + HGCMSvcHlpInfoDeregister(mpHelpers, "guestprops"); + + if (mhReqQNotifyHost != NIL_RTREQQUEUE) + { + /* Stop the thread */ + PRTREQ pReq; + int rc = RTReqQueueCall(mhReqQNotifyHost, &pReq, 10000, (PFNRT)wakeupNotifyHost, 0); + if (RT_SUCCESS(rc)) + RTReqRelease(pReq); + rc = RTThreadWait(mhThreadNotifyHost, 10000, NULL); + AssertRC(rc); + rc = RTReqQueueDestroy(mhReqQNotifyHost); + AssertRC(rc); + mhReqQNotifyHost = NIL_RTREQQUEUE; + mhThreadNotifyHost = NIL_RTTHREAD; + RTStrSpaceDestroy(&mhProperties, destroyProperty, NULL); + mhProperties = NULL; + } + return VINF_SUCCESS; +} + +} /* namespace guestProp */ + +using guestProp::Service; + +/** + * @copydoc FNVBOXHGCMSVCLOAD + */ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable) +{ + int rc = VERR_IPE_UNINITIALIZED_STATUS; + + 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 + { + Service *pService = NULL; + /* No exceptions may propagate outside. */ + try + { + pService = new Service(ptable->pHelpers); + rc = VINF_SUCCESS; + } + catch (int rcThrown) + { + rc = rcThrown; + } + catch (...) + { + rc = VERR_UNEXPECTED_EXCEPTION; + } + + if (RT_SUCCESS(rc)) + { + /* We do not maintain connections, so no client data is needed. */ + ptable->cbClient = 0; + + /* Legacy clients map to the kernel category. */ + ptable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL; + + /* Go with default client limits, but we won't ever need more than + 16 pending calls per client I would think (1 should be enough). */ + for (uintptr_t i = 0; i < RT_ELEMENTS(ptable->acMaxClients); i++) + ptable->acMaxCallsPerClient[i] = 16; + + ptable->pfnUnload = Service::svcUnload; + ptable->pfnConnect = Service::svcConnect; + ptable->pfnDisconnect = Service::svcDisconnect; + ptable->pfnCall = Service::svcCall; + ptable->pfnHostCall = Service::svcHostCall; + ptable->pfnSaveState = NULL; /* The service is stateless, so the normal */ + ptable->pfnLoadState = NULL; /* construction done before restoring suffices */ + ptable->pfnRegisterExtension = Service::svcRegisterExtension; + ptable->pfnNotify = Service::svcNotify; + ptable->pvService = pService; + + /* Service specific initialization. */ + rc = pService->initialize(); + if (RT_FAILURE(rc)) + { + delete pService; + pService = NULL; + } + } + else + Assert(!pService); + } + } + + LogFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + diff --git a/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.rc b/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.rc new file mode 100644 index 00000000..da45d2e2 --- /dev/null +++ b/src/VBox/HostServices/GuestProperties/VBoxGuestPropSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxGuestPropSvc.rc $ */ +/** @file + * VBoxGuestPropSvc - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Guest Properties Service\0" + VALUE "InternalName", "VBoxGuestPropSvc\0" + VALUE "OriginalFilename", "VBoxGuestPropSvc.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/GuestProperties/testcase/Makefile.kmk b/src/VBox/HostServices/GuestProperties/testcase/Makefile.kmk new file mode 100644 index 00000000..aac7a8c6 --- /dev/null +++ b/src/VBox/HostServices/GuestProperties/testcase/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Properties Host Service testcases. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + # Set this in LocalConfig.kmk if you are working on the guest property + # service to automatically run the testcase at build time. + # OTHERS += $(tstGuestPropSvc_0_OUTDIR)/tstGuestPropSvc.run + # + + PROGRAMS += tstGuestPropSvc + TESTING += $(tstGuestPropSvc_0_OUTDIR)/tstGuestPropSvc.run + tstGuestPropSvc_TEMPLATE = VBoxR3TstExe + # The second define here is to ensure that the testcase will run fast, + # without waiting for any thread synchronisation. + tstGuestPropSvc_DEFS = VBOX_WITH_HGCM VBOX_GUEST_PROP_TEST_NOTHREAD + tstGuestPropSvc_SOURCES = \ + tstGuestPropSvc.cpp \ + ../VBoxGuestPropSvc.cpp + tstGuestPropSvc_LIBS = $(LIB_RUNTIME) + + $$(tstGuestPropSvc_0_OUTDIR)/tstGuestPropSvc.run: $$(tstGuestPropSvc_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstGuestPropSvc_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/GuestProperties/testcase/tstGuestPropSvc.cpp b/src/VBox/HostServices/GuestProperties/testcase/tstGuestPropSvc.cpp new file mode 100644 index 00000000..987cff43 --- /dev/null +++ b/src/VBox/HostServices/GuestProperties/testcase/tstGuestPropSvc.cpp @@ -0,0 +1,1206 @@ +/* $Id: tstGuestPropSvc.cpp $ */ +/** @file + * + * Testcase for the guest property service. + */ + +/* + * Copyright (C) 2008-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/HostServices/GuestPropertySvc.h> +#include <VBox/err.h> +#include <VBox/hgcmsvc.h> +#include <iprt/test.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); + + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Dummy helper callback. */ +static DECLCALLBACK(int) tstHlpInfoDeregister(void *pvInstance, const char *pszName) +{ + RT_NOREF(pvInstance, pszName); + return VINF_SUCCESS; +} + +/** Dummy helper callback. */ +static DECLCALLBACK(int) tstHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser) +{ + RT_NOREF(pvInstance, pszName, pszDesc, pfnHandler, pvUser); + return VINF_SUCCESS; +} + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +/** + * Initialise the HGCM service table as much as we need to start the + * service + * @param pTable the table to initialise + */ +void initTable(VBOXHGCMSVCFNTABLE *pTable, VBOXHGCMSVCHELPERS *pHelpers) +{ + RT_ZERO(*pHelpers); + pHelpers->pfnCallComplete = callComplete; + pHelpers->pfnInfoRegister = tstHlpInfoRegister; + pHelpers->pfnInfoDeregister = tstHlpInfoDeregister; + + RT_ZERO(*pTable); + pTable->cbSize = sizeof(VBOXHGCMSVCFNTABLE); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + pTable->pHelpers = pHelpers; +} + +/** + * A list of valid flag strings for testConvertFlags. The flag conversion + * functions should accept these and convert them from string to a flag type + * and back without errors. + */ +struct flagStrings +{ + /** Flag string in a format the functions should recognise */ + const char *pcszIn; + /** How the functions should output the string again */ + const char *pcszOut; +} +g_aValidFlagStrings[] = +{ + /* pcszIn, pcszOut */ + { " ", "" }, + { "transient, ", "TRANSIENT" }, + { " rdOnLyHOST, transIENT , READONLY ", "TRANSIENT, READONLY" }, + { " rdonlyguest", "RDONLYGUEST" }, + { "rdonlyhost ", "RDONLYHOST" }, + { "transient, transreset, rdonlyhost", "TRANSIENT, RDONLYHOST, TRANSRESET" }, + { "transient, transreset, rdonlyguest", "TRANSIENT, RDONLYGUEST, TRANSRESET" }, /* max length */ + { "rdonlyguest, rdonlyhost", "READONLY" }, + { "transient, transreset, ", "TRANSIENT, TRANSRESET" }, /* Don't combine them ... */ + { "transreset, ", "TRANSIENT, TRANSRESET" }, /* ... instead expand transreset for old adds. */ +}; + +/** + * A list of invalid flag strings for testConvertFlags. The flag conversion + * functions should reject these. + */ +const char *g_apszInvalidFlagStrings[] = +{ + "RDONLYHOST,,", + " TRANSIENT READONLY" +}; + +/** + * Test the flag conversion functions. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testConvertFlags(void) +{ + int rc = VINF_SUCCESS; + char *pszFlagBuffer = (char *)RTTestGuardedAllocTail(g_hTest, GUEST_PROP_MAX_FLAGS_LEN); + + RTTestISub("Conversion of valid flags strings"); + for (unsigned i = 0; i < RT_ELEMENTS(g_aValidFlagStrings) && RT_SUCCESS(rc); ++i) + { + uint32_t fFlags; + rc = GuestPropValidateFlags(g_aValidFlagStrings[i].pcszIn, &fFlags); + if (RT_FAILURE(rc)) + RTTestIFailed("Failed to validate flag string '%s'", g_aValidFlagStrings[i].pcszIn); + if (RT_SUCCESS(rc)) + { + rc = GuestPropWriteFlags(fFlags, pszFlagBuffer); + if (RT_FAILURE(rc)) + RTTestIFailed("Failed to convert flag string '%s' back to a string.", + g_aValidFlagStrings[i].pcszIn); + } + if (RT_SUCCESS(rc) && (strlen(pszFlagBuffer) > GUEST_PROP_MAX_FLAGS_LEN - 1)) + { + RTTestIFailed("String '%s' converts back to a flag string which is too long.\n", + g_aValidFlagStrings[i].pcszIn); + rc = VERR_TOO_MUCH_DATA; + } + if (RT_SUCCESS(rc) && (strcmp(pszFlagBuffer, g_aValidFlagStrings[i].pcszOut) != 0)) + { + RTTestIFailed("String '%s' converts back to '%s' instead of to '%s'\n", + g_aValidFlagStrings[i].pcszIn, pszFlagBuffer, + g_aValidFlagStrings[i].pcszOut); + rc = VERR_PARSE_ERROR; + } + } + if (RT_SUCCESS(rc)) + { + RTTestISub("Rejection of invalid flags strings"); + for (unsigned i = 0; i < RT_ELEMENTS(g_apszInvalidFlagStrings) && RT_SUCCESS(rc); ++i) + { + uint32_t fFlags; + /* This is required to fail. */ + if (RT_SUCCESS(GuestPropValidateFlags(g_apszInvalidFlagStrings[i], &fFlags))) + { + RTTestIFailed("String '%s' was incorrectly accepted as a valid flag string.\n", + g_apszInvalidFlagStrings[i]); + rc = VERR_PARSE_ERROR; + } + } + } + if (RT_SUCCESS(rc)) + { + uint32_t u32BadFlags = GUEST_PROP_F_ALLFLAGS << 1; + RTTestISub("Rejection of an invalid flags field"); + /* This is required to fail. */ + if (RT_SUCCESS(GuestPropWriteFlags(u32BadFlags, pszFlagBuffer))) + { + RTTestIFailed("Flags 0x%x were incorrectly written out as '%.*s'\n", + u32BadFlags, GUEST_PROP_MAX_FLAGS_LEN, pszFlagBuffer); + rc = VERR_PARSE_ERROR; + } + } + + RTTestGuardedFree(g_hTest, pszFlagBuffer); +} + +/** + * List of property names for testSetPropsHost. + */ +const char *g_apcszNameBlock[] = +{ + "test/name/", + "test name", + "TEST NAME", + "/test/name", + NULL +}; + +/** + * List of property values for testSetPropsHost. + */ +const char *g_apcszValueBlock[] = +{ + "test/value/", + "test value", + "TEST VALUE", + "/test/value", + NULL +}; + +/** + * List of property timestamps for testSetPropsHost. + */ +uint64_t g_au64TimestampBlock[] = +{ + 0, 999, 999999, UINT64_C(999999999999), 0 +}; + +/** + * List of property flags for testSetPropsHost. + */ +const char *g_apcszFlagsBlock[] = +{ + "", + "readonly, transient", + "RDONLYHOST", + "RdOnlyGuest", + NULL +}; + +/** + * Test the SET_PROPS_HOST function. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testSetPropsHost(VBOXHGCMSVCFNTABLE *ptable) +{ + RTTestISub("SET_PROPS_HOST"); + RTTESTI_CHECK_RETV(RT_VALID_PTR(ptable->pfnHostCall)); + + VBOXHGCMSVCPARM aParms[4]; + HGCMSvcSetPv(&aParms[0], (void *)g_apcszNameBlock, 0); + HGCMSvcSetPv(&aParms[1], (void *)g_apcszValueBlock, 0); + HGCMSvcSetPv(&aParms[2], (void *)g_au64TimestampBlock, 0); + HGCMSvcSetPv(&aParms[3], (void *)g_apcszFlagsBlock, 0); + RTTESTI_CHECK_RC(ptable->pfnHostCall(ptable->pvService, GUEST_PROP_FN_HOST_SET_PROPS, 4, &aParms[0]), VINF_SUCCESS); +} + +#if 0 +/** Result strings for zeroth enumeration test */ +static const char *g_apchEnumResult0[] = +{ + "test/name/\0test/value/\0""0\0", + "test name\0test value\0""999\0TRANSIENT, READONLY", + "TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST", + "/test/name\0/test/value\0""999999999999\0RDONLYGUEST", + NULL +}; + +/** Result string sizes for zeroth enumeration test */ +static const uint32_t g_acbEnumResult0[] = +{ + sizeof("test/name/\0test/value/\0""0\0"), + sizeof("test name\0test value\0""999\0TRANSIENT, READONLY"), + sizeof("TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST"), + sizeof("/test/name\0/test/value\0""999999999999\0RDONLYGUEST"), + 0 +}; + +/** + * The size of the buffer returned by the zeroth enumeration test - + * the - 1 at the end is because of the hidden zero terminator + */ +static const uint32_t g_cbEnumBuffer0 = + sizeof("test/name/\0test/value/\0""0\0\0" + "test name\0test value\0""999\0TRANSIENT, READONLY\0" + "TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST\0" + "/test/name\0/test/value\0""999999999999\0RDONLYGUEST\0\0\0\0\0") - 1; +#endif + +/** Result strings for first and second enumeration test */ +static const char *g_apchEnumResult1[] = +{ + "TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST", + "/test/name\0/test/value\0""999999999999\0RDONLYGUEST", + NULL +}; + +/** Result string sizes for first and second enumeration test */ +static const uint32_t g_acbEnumResult1[] = +{ + sizeof("TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST"), + sizeof("/test/name\0/test/value\0""999999999999\0RDONLYGUEST"), + 0 +}; + +/** + * The size of the buffer returned by the first enumeration test - + * the - 1 at the end is because of the hidden zero terminator + */ +static const uint32_t g_cbEnumBuffer1 = + sizeof("TEST NAME\0TEST VALUE\0""999999\0RDONLYHOST\0" + "/test/name\0/test/value\0""999999999999\0RDONLYGUEST\0\0\0\0\0") - 1; + +static const struct enumStringStruct +{ + /** The enumeration pattern to test */ + const char *pszPatterns; + /** The size of the pattern string (including terminator) */ + const uint32_t cbPatterns; + /** The expected enumeration output strings */ + const char **papchResult; + /** The size of the output strings */ + const uint32_t *pacchResult; + /** The size of the buffer needed for the enumeration */ + const uint32_t cbBuffer; +} g_aEnumStrings[] = +{ +#if 0 /* unpredictable automatic variables set by the service now */ + { + "", sizeof(""), + g_apchEnumResult0, + g_acbEnumResult0, + g_cbEnumBuffer0 + }, +#endif + { + "/t*\0?E*", sizeof("/t*\0?E*"), + g_apchEnumResult1, + g_acbEnumResult1, + g_cbEnumBuffer1 + }, + { + "/t*|?E*", sizeof("/t*|?E*"), + g_apchEnumResult1, + g_acbEnumResult1, + g_cbEnumBuffer1 + } +}; + +/** + * Test the ENUM_PROPS_HOST function. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testEnumPropsHost(VBOXHGCMSVCFNTABLE *ptable) +{ + RTTestISub("ENUM_PROPS_HOST"); + RTTESTI_CHECK_RETV(RT_VALID_PTR(ptable->pfnHostCall)); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aEnumStrings); ++i) + { + VBOXHGCMSVCPARM aParms[3]; + char abBuffer[2048]; + RTTESTI_CHECK_RETV(g_aEnumStrings[i].cbBuffer < sizeof(abBuffer)); + + /* Check that we get buffer overflow with a too small buffer. */ + HGCMSvcSetPv(&aParms[0], (void *)g_aEnumStrings[i].pszPatterns, g_aEnumStrings[i].cbPatterns); + HGCMSvcSetPv(&aParms[1], (void *)abBuffer, g_aEnumStrings[i].cbBuffer - 1); + memset(abBuffer, 0x55, sizeof(abBuffer)); + int rc2 = ptable->pfnHostCall(ptable->pvService, GUEST_PROP_FN_HOST_ENUM_PROPS, 3, aParms); + if (rc2 == VERR_BUFFER_OVERFLOW) + { + uint32_t cbNeeded; + RTTESTI_CHECK_RC(rc2 = HGCMSvcGetU32(&aParms[2], &cbNeeded), VINF_SUCCESS); + if (RT_SUCCESS(rc2)) + RTTESTI_CHECK_MSG(cbNeeded == g_aEnumStrings[i].cbBuffer, + ("expected %#x, got %#x, pattern %d\n", g_aEnumStrings[i].cbBuffer, cbNeeded, i)); + } + else + RTTestIFailed("ENUM_PROPS_HOST returned %Rrc instead of VERR_BUFFER_OVERFLOW on too small buffer, pattern number %d.", rc2, i); + + /* Make a successfull call. */ + HGCMSvcSetPv(&aParms[0], (void *)g_aEnumStrings[i].pszPatterns, g_aEnumStrings[i].cbPatterns); + HGCMSvcSetPv(&aParms[1], (void *)abBuffer, g_aEnumStrings[i].cbBuffer); + memset(abBuffer, 0x55, sizeof(abBuffer)); + rc2 = ptable->pfnHostCall(ptable->pvService, GUEST_PROP_FN_HOST_ENUM_PROPS, 3, aParms); + if (rc2 == VINF_SUCCESS) + { + /* Look for each of the result strings in the buffer which was returned */ + for (unsigned j = 0; g_aEnumStrings[i].papchResult[j] != NULL; ++j) + { + bool found = false; + for (unsigned k = 0; !found && k < g_aEnumStrings[i].cbBuffer - g_aEnumStrings[i].pacchResult[j]; ++k) + if (memcmp(abBuffer + k, g_aEnumStrings[i].papchResult[j], g_aEnumStrings[i].pacchResult[j]) == 0) + found = true; + if (!found) + RTTestIFailed("ENUM_PROPS_HOST did not produce the expected output for pattern %d.", i); + } + } + else + RTTestIFailed("ENUM_PROPS_HOST returned %Rrc instead of VINF_SUCCESS, pattern number %d.", rc2, i); + } +} + +/** + * Set a property by calling the service + * @returns the status returned by the call to the service + * + * @param pTable the service instance handle + * @param pcszName the name of the property to set + * @param pcszValue the value to set the property to + * @param pcszFlags the flag string to set if one of the SET_PROP[_HOST] + * commands is used + * @param isHost whether the SET_PROP[_VALUE]_HOST commands should be + * used, rather than the guest ones + * @param useSetProp whether SET_PROP[_HOST] should be used rather than + * SET_PROP_VALUE[_HOST] + */ +int doSetProperty(VBOXHGCMSVCFNTABLE *pTable, const char *pcszName, + const char *pcszValue, const char *pcszFlags, bool isHost, + bool useSetProp) +{ + RTThreadSleep(1); /* stupid, stupid timestamp fudge to avoid asserting in getOldNotification() */ + + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + int command = GUEST_PROP_FN_SET_PROP_VALUE; + if (isHost) + { + if (useSetProp) + command = GUEST_PROP_FN_HOST_SET_PROP; + else + command = GUEST_PROP_FN_HOST_SET_PROP_VALUE; + } + else if (useSetProp) + command = GUEST_PROP_FN_SET_PROP; + VBOXHGCMSVCPARM aParms[3]; + /* Work around silly constant issues - we ought to allow passing + * constant strings in the hgcm parameters. */ + char szName[GUEST_PROP_MAX_NAME_LEN]; + char szValue[GUEST_PROP_MAX_VALUE_LEN]; + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + RTStrPrintf(szName, sizeof(szName), "%s", pcszName); + RTStrPrintf(szValue, sizeof(szValue), "%s", pcszValue); + RTStrPrintf(szFlags, sizeof(szFlags), "%s", pcszFlags); + HGCMSvcSetStr(&aParms[0], szName); + HGCMSvcSetStr(&aParms[1], szValue); + HGCMSvcSetStr(&aParms[2], szFlags); + if (isHost) + callHandle.rc = pTable->pfnHostCall(pTable->pvService, command, + useSetProp ? 3 : 2, aParms); + else + pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, command, + useSetProp ? 3 : 2, aParms, 0); + return callHandle.rc; +} + +/** + * Test the SET_PROP, SET_PROP_VALUE, SET_PROP_HOST and SET_PROP_VALUE_HOST + * functions. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testSetProp(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("SET_PROP, _VALUE, _HOST, _VALUE_HOST"); + + /** Array of properties for testing SET_PROP_HOST and _GUEST. */ + static const struct + { + /** Property name */ + const char *pcszName; + /** Property value */ + const char *pcszValue; + /** Property flags */ + const char *pcszFlags; + /** Should this be set as the host or the guest? */ + bool isHost; + /** Should we use SET_PROP or SET_PROP_VALUE? */ + bool useSetProp; + /** Should this succeed or be rejected with VERR_PERMISSION_DENIED? */ + bool isAllowed; + } + s_aSetProperties[] = + { + { "Red", "Stop!", "transient", false, true, true }, + { "Amber", "Caution!", "", false, false, true }, + { "Green", "Go!", "readonly", true, true, true }, + { "Blue", "What on earth...?", "", true, false, true }, + { "/test/name", "test", "", false, true, false }, + { "TEST NAME", "test", "", true, true, false }, + { "Green", "gone out...", "", false, false, false }, + { "Green", "gone out...", "", true, false, false }, + { "/VirtualBox/GuestAdd/SharedFolders/MountDir", "test", "", false, true, false }, + { "/VirtualBox/GuestAdd/SomethingElse", "test", "", false, true, true }, + { "/VirtualBox/HostInfo/VRDP/Client/1/Name", "test", "", false, false, false }, + { "/VirtualBox/GuestAdd/SharedFolders/MountDir", "test", "", true, true, true }, + { "/VirtualBox/HostInfo/VRDP/Client/1/Name", "test", "TRANSRESET", true, true, true }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_aSetProperties); ++i) + { + int rc = doSetProperty(pTable, + s_aSetProperties[i].pcszName, + s_aSetProperties[i].pcszValue, + s_aSetProperties[i].pcszFlags, + s_aSetProperties[i].isHost, + s_aSetProperties[i].useSetProp); + if (s_aSetProperties[i].isAllowed && RT_FAILURE(rc)) + RTTestIFailed("Setting property '%s' failed with rc=%Rrc.", + s_aSetProperties[i].pcszName, rc); + else if ( !s_aSetProperties[i].isAllowed + && rc != VERR_PERMISSION_DENIED) + RTTestIFailed("Setting property '%s' returned %Rrc instead of VERR_PERMISSION_DENIED.", + s_aSetProperties[i].pcszName, rc); + } +} + +/** + * Delete a property by calling the service + * @returns the status returned by the call to the service + * + * @param pTable the service instance handle + * @param pcszName the name of the property to delete + * @param isHost whether the DEL_PROP_HOST command should be used, rather + * than the guest one + */ +static int doDelProp(VBOXHGCMSVCFNTABLE *pTable, const char *pcszName, bool isHost) +{ + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + int command = GUEST_PROP_FN_DEL_PROP; + if (isHost) + command = GUEST_PROP_FN_HOST_DEL_PROP; + VBOXHGCMSVCPARM aParms[1]; + HGCMSvcSetStr(&aParms[0], pcszName); + if (isHost) + callHandle.rc = pTable->pfnHostCall(pTable->pvService, command, 1, aParms); + else + pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, command, 1, aParms, 0); + return callHandle.rc; +} + +/** + * Test the DEL_PROP, and DEL_PROP_HOST functions. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testDelProp(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("DEL_PROP, DEL_PROP_HOST"); + + /** Array of properties for testing DEL_PROP_HOST and _GUEST. */ + static const struct + { + /** Property name */ + const char *pcszName; + /** Should this be set as the host or the guest? */ + bool isHost; + /** Should this succeed or be rejected with VERR_PERMISSION_DENIED? */ + bool isAllowed; + } s_aDelProperties[] = + { + { "Red", false, true }, + { "Amber", true, true }, + { "Red2", false, true }, + { "Amber2", true, true }, + { "Green", false, false }, + { "Green", true, false }, + { "/test/name", false, false }, + { "TEST NAME", true, false }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_aDelProperties); ++i) + { + int rc = doDelProp(pTable, s_aDelProperties[i].pcszName, s_aDelProperties[i].isHost); + if (s_aDelProperties[i].isAllowed && RT_FAILURE(rc)) + RTTestIFailed("Deleting property '%s' failed with rc=%Rrc.", + s_aDelProperties[i].pcszName, rc); + else if ( !s_aDelProperties[i].isAllowed + && rc != VERR_PERMISSION_DENIED ) + RTTestIFailed("Deleting property '%s' returned %Rrc instead of VERR_PERMISSION_DENIED.", + s_aDelProperties[i].pcszName, rc); + } +} + +/** + * Test the GET_PROP_HOST function. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testGetProp(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("GET_PROP_HOST"); + + /** Array of properties for testing GET_PROP_HOST. */ + static const struct + { + /** Property name */ + const char *pcszName; + /** What value/flags pattern do we expect back? */ + const char *pchValue; + /** What size should the value/flags array be? */ + uint32_t cchValue; + /** Should this property exist? */ + bool exists; + /** Do we expect a particular timestamp? */ + bool hasTimestamp; + /** What timestamp if any do ex expect? */ + uint64_t u64Timestamp; + } + s_aGetProperties[] = + { + { "test/name/", "test/value/\0", sizeof("test/value/\0"), true, true, 0 }, + { "test name", "test value\0TRANSIENT, READONLY", + sizeof("test value\0TRANSIENT, READONLY"), true, true, 999 }, + { "TEST NAME", "TEST VALUE\0RDONLYHOST", sizeof("TEST VALUE\0RDONLYHOST"), + true, true, 999999 }, + { "/test/name", "/test/value\0RDONLYGUEST", + sizeof("/test/value\0RDONLYGUEST"), true, true, UINT64_C(999999999999) }, + { "Green", "Go!\0READONLY", sizeof("Go!\0READONLY"), true, false, 0 }, + { "Blue", "What on earth...?\0", sizeof("What on earth...?\0"), true, + false, 0 }, + { "Red", "", 0, false, false, 0 }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_aGetProperties); ++i) + { + VBOXHGCMSVCPARM aParms[4]; + /* Work around silly constant issues - we ought to allow passing + * constant strings in the hgcm parameters. */ + char szBuffer[GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN]; + RTTESTI_CHECK_RETV(s_aGetProperties[i].cchValue < sizeof(szBuffer)); + + HGCMSvcSetStr(&aParms[0], s_aGetProperties[i].pcszName); + memset(szBuffer, 0x55, sizeof(szBuffer)); + HGCMSvcSetPv(&aParms[1], szBuffer, sizeof(szBuffer)); + int rc2 = pTable->pfnHostCall(pTable->pvService, GUEST_PROP_FN_HOST_GET_PROP, 4, aParms); + + if (s_aGetProperties[i].exists && RT_FAILURE(rc2)) + { + RTTestIFailed("Getting property '%s' failed with rc=%Rrc.", + s_aGetProperties[i].pcszName, rc2); + continue; + } + + if (!s_aGetProperties[i].exists && rc2 != VERR_NOT_FOUND) + { + RTTestIFailed("Getting property '%s' returned %Rrc instead of VERR_NOT_FOUND.", + s_aGetProperties[i].pcszName, rc2); + continue; + } + + if (s_aGetProperties[i].exists) + { + AssertRC(rc2); + + uint32_t u32ValueLen = UINT32_MAX; + RTTESTI_CHECK_RC(rc2 = HGCMSvcGetU32(&aParms[3], &u32ValueLen), VINF_SUCCESS); + if (RT_SUCCESS(rc2)) + { + RTTESTI_CHECK_MSG(u32ValueLen <= sizeof(szBuffer), ("u32ValueLen=%d", u32ValueLen)); + if (memcmp(szBuffer, s_aGetProperties[i].pchValue, s_aGetProperties[i].cchValue) != 0) + RTTestIFailed("Unexpected result '%.*s' for property '%s', expected '%.*s'.", + u32ValueLen, szBuffer, s_aGetProperties[i].pcszName, + s_aGetProperties[i].cchValue, s_aGetProperties[i].pchValue); + } + + if (s_aGetProperties[i].hasTimestamp) + { + uint64_t u64Timestamp = UINT64_MAX; + RTTESTI_CHECK_RC(rc2 = HGCMSvcGetU64(&aParms[2], &u64Timestamp), VINF_SUCCESS); + if (u64Timestamp != s_aGetProperties[i].u64Timestamp) + RTTestIFailed("Bad timestamp %llu for property '%s', expected %llu.", + u64Timestamp, s_aGetProperties[i].pcszName, + s_aGetProperties[i].u64Timestamp); + } + } + } +} + +/** Array of properties for testing GET_PROP_HOST. */ +static const struct +{ + /** Buffer returned */ + const char *pchBuffer; + /** What size should the buffer be? */ + uint32_t cbBuffer; +} +g_aGetNotifications[] = +{ + // Name\0Value\0Flags\0fWasDeleted\0 +#define STR_AND_SIZE(a_sz) { a_sz, sizeof(a_sz) } + STR_AND_SIZE("Red\0Stop!\0TRANSIENT\0" "0"), /* first test is used by testAsyncNotification, - testGetNotification skips it. (mess) */ + STR_AND_SIZE("Red\0Stop!\0TRANSIENT\0" "1"), + STR_AND_SIZE("Amber\0Caution!\0\0" "1"), + STR_AND_SIZE("Green\0Go!\0READONLY\0" "0"), + STR_AND_SIZE("Blue\0What on earth...?\0\0" "0"), + STR_AND_SIZE("/VirtualBox/GuestAdd/SomethingElse\0test\0\0" "0"), + STR_AND_SIZE("/VirtualBox/GuestAdd/SharedFolders/MountDir\0test\0RDONLYGUEST\0" "0"), + STR_AND_SIZE("/VirtualBox/HostInfo/VRDP/Client/1/Name\0test\0TRANSIENT, RDONLYGUEST, TRANSRESET\0" "0"), + STR_AND_SIZE("Red\0\0\0" "1"), + STR_AND_SIZE("Amber\0\0\0" "1"), +#undef STR_AND_SIZE +}; + +/** + * Test the GET_NOTIFICATION function. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testGetNotification(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("GET_NOTIFICATION"); + + /* Test "buffer too small" */ + static char s_szPattern[] = "/VirtualBox/GuestAdd/*|/VirtualBox/HostInfo/VRDP/Client*|Red*|Amber*|Green*|Blue*"; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + VBOXHGCMSVCPARM aParms[4]; + uint32_t cbRetNeeded = 0; + + for (uint32_t cbBuf = 1; + cbBuf < g_aGetNotifications[1].cbBuffer - 1; + cbBuf++) + { + void *pvBuf = RTTestGuardedAllocTail(g_hTest, cbBuf); + RTTESTI_CHECK_BREAK(pvBuf); + memset(pvBuf, 0x55, cbBuf); + + HGCMSvcSetStr(&aParms[0], s_szPattern); + HGCMSvcSetU64(&aParms[1], 1); + HGCMSvcSetPv(&aParms[2], pvBuf, cbBuf); + pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, GUEST_PROP_FN_GET_NOTIFICATION, 4, aParms, 0); + + if ( callHandle.rc != VERR_BUFFER_OVERFLOW + || RT_FAILURE(HGCMSvcGetU32(&aParms[3], &cbRetNeeded)) + || cbRetNeeded != g_aGetNotifications[1].cbBuffer + ) + RTTestIFailed("Getting notification for property '%s' with a too small buffer did not fail correctly: rc=%Rrc, cbRetNeeded=%#x (expected %#x)", + g_aGetNotifications[1].pchBuffer, callHandle.rc, cbRetNeeded, g_aGetNotifications[1].cbBuffer); + RTTestGuardedFree(g_hTest, pvBuf); + } + + /* Test successful notification queries. Start with an unknown timestamp + * to get the oldest available notification. */ + uint64_t u64Timestamp = 1; + for (unsigned i = 1; i < RT_ELEMENTS(g_aGetNotifications); ++i) + { + uint32_t cbBuf = g_aGetNotifications[i].cbBuffer + _1K; + void *pvBuf = RTTestGuardedAllocTail(g_hTest, cbBuf); + RTTESTI_CHECK_BREAK(pvBuf); + memset(pvBuf, 0x55, cbBuf); + + HGCMSvcSetStr(&aParms[0], s_szPattern); + HGCMSvcSetU64(&aParms[1], u64Timestamp); + HGCMSvcSetPv(&aParms[2], pvBuf, cbBuf); + pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, GUEST_PROP_FN_GET_NOTIFICATION, 4, aParms, 0); + if ( RT_FAILURE(callHandle.rc) + || (i == 0 && callHandle.rc != VWRN_NOT_FOUND) + || RT_FAILURE(HGCMSvcGetU64(&aParms[1], &u64Timestamp)) + || RT_FAILURE(HGCMSvcGetU32(&aParms[3], &cbRetNeeded)) + || cbRetNeeded != g_aGetNotifications[i].cbBuffer + || memcmp(pvBuf, g_aGetNotifications[i].pchBuffer, cbRetNeeded) != 0 + ) + { + RTTestIFailed("Failed to get notification for property '%s' (#%u): rc=%Rrc (expected %Rrc), cbRetNeeded=%#x (expected %#x)\n" + "%.*Rhxd\n---expected:---\n%.*Rhxd", + g_aGetNotifications[i].pchBuffer, i, callHandle.rc, i == 0 ? VWRN_NOT_FOUND : VINF_SUCCESS, + cbRetNeeded, g_aGetNotifications[i].cbBuffer, RT_MIN(cbRetNeeded, cbBuf), pvBuf, + g_aGetNotifications[i].cbBuffer, g_aGetNotifications[i].pchBuffer); + } + RTTestGuardedFree(g_hTest, pvBuf); + } +} + +/** Parameters for the asynchronous guest notification call */ +struct asyncNotification_ +{ + /** Call parameters */ + VBOXHGCMSVCPARM aParms[4]; + /** Result buffer */ + char abBuffer[GUEST_PROP_MAX_NAME_LEN + GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN]; + /** Return value */ + VBOXHGCMCALLHANDLE_TYPEDEF callHandle; +} g_AsyncNotification; + +/** + * Set up the test for the asynchronous GET_NOTIFICATION function. + */ +static void setupAsyncNotification(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("Async GET_NOTIFICATION without notifications"); + static char s_szPattern[] = ""; + + HGCMSvcSetStr(&g_AsyncNotification.aParms[0], s_szPattern); + HGCMSvcSetU64(&g_AsyncNotification.aParms[1], 0); + HGCMSvcSetPv(&g_AsyncNotification.aParms[2], g_AsyncNotification.abBuffer, sizeof(g_AsyncNotification.abBuffer)); + g_AsyncNotification.callHandle.rc = VINF_HGCM_ASYNC_EXECUTE; + pTable->pfnCall(pTable->pvService, &g_AsyncNotification.callHandle, 0, NULL, + GUEST_PROP_FN_GET_NOTIFICATION, 4, g_AsyncNotification.aParms, 0); + if (RT_FAILURE(g_AsyncNotification.callHandle.rc)) + RTTestIFailed("GET_NOTIFICATION call failed, rc=%Rrc.", g_AsyncNotification.callHandle.rc); + else if (g_AsyncNotification.callHandle.rc != VINF_HGCM_ASYNC_EXECUTE) + RTTestIFailed("GET_NOTIFICATION call completed when no new notifications should be available."); +} + +/** + * Test the asynchronous GET_NOTIFICATION function. + */ +static void testAsyncNotification(VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF1(pTable); + uint64_t u64Timestamp; + uint32_t cb = 0; + if ( g_AsyncNotification.callHandle.rc != VINF_SUCCESS + || RT_FAILURE(HGCMSvcGetU64(&g_AsyncNotification.aParms[1], &u64Timestamp)) + || RT_FAILURE(HGCMSvcGetU32(&g_AsyncNotification.aParms[3], &cb)) + || cb != g_aGetNotifications[0].cbBuffer + || memcmp(g_AsyncNotification.abBuffer, g_aGetNotifications[0].pchBuffer, cb) != 0 + ) + { + RTTestIFailed("Asynchronous GET_NOTIFICATION call did not complete as expected: rc=%Rrc, cb=%#x (expected %#x)\n" + "abBuffer=%.*Rhxs\n" + "expected=%.*Rhxs", + g_AsyncNotification.callHandle.rc, cb, g_aGetNotifications[0].cbBuffer, + cb, g_AsyncNotification.abBuffer, g_aGetNotifications[0].cbBuffer, g_aGetNotifications[0].pchBuffer); + } +} + + +static void test2(void) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + initTable(&svcTable, &svcHelpers); + + /* The function is inside the service, not HGCM. */ + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(&svcTable)); + + testSetPropsHost(&svcTable); + testEnumPropsHost(&svcTable); + + /* Set up the asynchronous notification test */ + setupAsyncNotification(&svcTable); + testSetProp(&svcTable); + RTTestISub("Async notification call data"); + testAsyncNotification(&svcTable); /* Our previous notification call should have completed by now. */ + + testDelProp(&svcTable); + testGetProp(&svcTable); + testGetNotification(&svcTable); + + /* Cleanup */ + RTTESTI_CHECK_RC_OK(svcTable.pfnUnload(svcTable.pvService)); +} + +/** + * Set the global flags value by calling the service + * @returns the status returned by the call to the service + * + * @param pTable the service instance handle + * @param fFlags the flags to set + */ +static int doSetGlobalFlags(VBOXHGCMSVCFNTABLE *pTable, uint32_t fFlags) +{ + VBOXHGCMSVCPARM paParm; + HGCMSvcSetU32(&paParm, fFlags); + int rc = pTable->pfnHostCall(pTable->pvService, GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS, 1, &paParm); + if (RT_FAILURE(rc)) + { + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + if (RT_FAILURE(GuestPropWriteFlags(fFlags, szFlags))) + RTTestIFailed("Failed to set the global flags."); + else + RTTestIFailed("Failed to set the global flags \"%s\".", szFlags); + } + return rc; +} + +/** + * Test the SET_PROP, SET_PROP_VALUE, SET_PROP_HOST and SET_PROP_VALUE_HOST + * functions. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testSetPropROGuest(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("global READONLYGUEST and SET_PROP*"); + + /** Array of properties for testing SET_PROP_HOST and _GUEST with the + * READONLYGUEST global flag set. */ + static const struct + { + /** Property name */ + const char *pcszName; + /** Property value */ + const char *pcszValue; + /** Property flags */ + const char *pcszFlags; + /** Should this be set as the host or the guest? */ + bool isHost; + /** Should we use SET_PROP or SET_PROP_VALUE? */ + bool useSetProp; + /** Should this succeed or be rejected with VERR_ (NOT VINF_!) + * PERMISSION_DENIED? The global check is done after the property one. */ + bool isAllowed; + } + s_aSetPropertiesROGuest[] = + { + { "Red", "Stop!", "transient", false, true, true }, + { "Amber", "Caution!", "", false, false, true }, + { "Green", "Go!", "readonly", true, true, true }, + { "Blue", "What on earth...?", "", true, false, true }, + { "/test/name", "test", "", false, true, true }, + { "TEST NAME", "test", "", true, true, true }, + { "Green", "gone out...", "", false, false, false }, + { "Green", "gone out....", "", true, false, false }, + }; + + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(pTable)); + int rc = doSetGlobalFlags(pTable, GUEST_PROP_F_RDONLYGUEST); + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < RT_ELEMENTS(s_aSetPropertiesROGuest); ++i) + { + rc = doSetProperty(pTable, s_aSetPropertiesROGuest[i].pcszName, + s_aSetPropertiesROGuest[i].pcszValue, + s_aSetPropertiesROGuest[i].pcszFlags, + s_aSetPropertiesROGuest[i].isHost, + s_aSetPropertiesROGuest[i].useSetProp); + if (s_aSetPropertiesROGuest[i].isAllowed && RT_FAILURE(rc)) + RTTestIFailed("Setting property '%s' to '%s' failed with rc=%Rrc.", + s_aSetPropertiesROGuest[i].pcszName, + s_aSetPropertiesROGuest[i].pcszValue, rc); + else if ( !s_aSetPropertiesROGuest[i].isAllowed + && rc != VERR_PERMISSION_DENIED) + RTTestIFailed("Setting property '%s' to '%s' returned %Rrc instead of VERR_PERMISSION_DENIED.\n", + s_aSetPropertiesROGuest[i].pcszName, + s_aSetPropertiesROGuest[i].pcszValue, rc); + else if ( !s_aSetPropertiesROGuest[i].isHost + && s_aSetPropertiesROGuest[i].isAllowed + && rc != VINF_PERMISSION_DENIED) + RTTestIFailed("Setting property '%s' to '%s' returned %Rrc instead of VINF_PERMISSION_DENIED.\n", + s_aSetPropertiesROGuest[i].pcszName, + s_aSetPropertiesROGuest[i].pcszValue, rc); + } + } + RTTESTI_CHECK_RC_OK(pTable->pfnUnload(pTable->pvService)); +} + +/** + * Test the DEL_PROP, and DEL_PROP_HOST functions. + * @returns iprt status value to indicate whether the test went as expected. + * @note prints its own diagnostic information to stdout. + */ +static void testDelPropROGuest(VBOXHGCMSVCFNTABLE *pTable) +{ + RTTestISub("global READONLYGUEST and DEL_PROP*"); + + /** Array of properties for testing DEL_PROP_HOST and _GUEST with + * READONLYGUEST set globally. */ + static const struct + { + /** Property name */ + const char *pcszName; + /** Should this be deleted as the host (or the guest)? */ + bool isHost; + /** Should this property be created first? (As host, obviously) */ + bool shouldCreate; + /** And with what flags? */ + const char *pcszFlags; + /** Should this succeed or be rejected with VERR_ (NOT VINF_!) + * PERMISSION_DENIED? The global check is done after the property one. */ + bool isAllowed; + } + s_aDelPropertiesROGuest[] = + { + { "Red", true, true, "", true }, + { "Amber", false, true, "", true }, + { "Red2", true, false, "", true }, + { "Amber2", false, false, "", true }, + { "Red3", true, true, "READONLY", false }, + { "Amber3", false, true, "READONLY", false }, + { "Red4", true, true, "RDONLYHOST", false }, + { "Amber4", false, true, "RDONLYHOST", true }, + }; + + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(pTable)); + int rc = doSetGlobalFlags(pTable, GUEST_PROP_F_RDONLYGUEST); + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < RT_ELEMENTS(s_aDelPropertiesROGuest); ++i) + { + if (s_aDelPropertiesROGuest[i].shouldCreate) + rc = doSetProperty(pTable, s_aDelPropertiesROGuest[i].pcszName, + "none", s_aDelPropertiesROGuest[i].pcszFlags, + true, true); + rc = doDelProp(pTable, s_aDelPropertiesROGuest[i].pcszName, + s_aDelPropertiesROGuest[i].isHost); + if (s_aDelPropertiesROGuest[i].isAllowed && RT_FAILURE(rc)) + RTTestIFailed("Deleting property '%s' failed with rc=%Rrc.", + s_aDelPropertiesROGuest[i].pcszName, rc); + else if ( !s_aDelPropertiesROGuest[i].isAllowed + && rc != VERR_PERMISSION_DENIED) + RTTestIFailed("Deleting property '%s' returned %Rrc instead of VERR_PERMISSION_DENIED.", + s_aDelPropertiesROGuest[i].pcszName, rc); + else if ( !s_aDelPropertiesROGuest[i].isHost + && s_aDelPropertiesROGuest[i].shouldCreate + && s_aDelPropertiesROGuest[i].isAllowed + && rc != VINF_PERMISSION_DENIED) + RTTestIFailed("Deleting property '%s' as guest returned %Rrc instead of VINF_PERMISSION_DENIED.", + s_aDelPropertiesROGuest[i].pcszName, rc); + } + } + RTTESTI_CHECK_RC_OK(pTable->pfnUnload(pTable->pvService)); +} + +static void test3(void) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + initTable(&svcTable, &svcHelpers); + testSetPropROGuest(&svcTable); + testDelPropROGuest(&svcTable); +} + +static void test4(void) +{ + RTTestISub("GET_PROP_HOST buffer handling"); + + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + initTable(&svcTable, &svcHelpers); + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(&svcTable)); + + /* Insert a property that we can mess around with. */ + static char const s_szProp[] = "/MyProperties/Sub/Sub/Sub/Sub/Sub/Sub/Sub/Property"; + static char const s_szValue[] = "Property Value"; + RTTESTI_CHECK_RC_OK(doSetProperty(&svcTable, s_szProp, s_szValue, "", true, true)); + + + /* Get the value with buffer sizes up to 1K. */ + for (unsigned iVariation = 0; iVariation < 2; iVariation++) + { + for (uint32_t cbBuf = 0; cbBuf < _1K; cbBuf++) + { + void *pvBuf; + RTTESTI_CHECK_RC_BREAK(RTTestGuardedAlloc(g_hTest, cbBuf, 1, iVariation == 0, &pvBuf), VINF_SUCCESS); + + VBOXHGCMSVCPARM aParms[4]; + HGCMSvcSetStr(&aParms[0], s_szProp); + HGCMSvcSetPv(&aParms[1], pvBuf, cbBuf); + svcTable.pfnHostCall(svcTable.pvService, GUEST_PROP_FN_HOST_GET_PROP, RT_ELEMENTS(aParms), aParms); + + RTTestGuardedFree(g_hTest, pvBuf); + } + } + + /* Done. */ + RTTESTI_CHECK_RC_OK(svcTable.pfnUnload(svcTable.pvService)); +} + +static void test5(void) +{ + RTTestISub("ENUM_PROPS_HOST buffer handling"); + + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + initTable(&svcTable, &svcHelpers); + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(&svcTable)); + + /* Insert a few property that we can mess around with. */ + RTTESTI_CHECK_RC_OK(doSetProperty(&svcTable, "/MyProperties/Sub/Sub/Sub/Sub/Sub/Sub/Sub/Property", "Property Value", "", true, true)); + RTTESTI_CHECK_RC_OK(doSetProperty(&svcTable, "/MyProperties/12357", "83848569", "", true, true)); + RTTESTI_CHECK_RC_OK(doSetProperty(&svcTable, "/MyProperties/56678", "abcdefghijklm", "", true, true)); + RTTESTI_CHECK_RC_OK(doSetProperty(&svcTable, "/MyProperties/932769", "n", "", true, true)); + + /* Get the value with buffer sizes up to 1K. */ + for (unsigned iVariation = 0; iVariation < 2; iVariation++) + { + for (uint32_t cbBuf = 0; cbBuf < _1K; cbBuf++) + { + void *pvBuf; + RTTESTI_CHECK_RC_BREAK(RTTestGuardedAlloc(g_hTest, cbBuf, 1, iVariation == 0, &pvBuf), VINF_SUCCESS); + + VBOXHGCMSVCPARM aParms[3]; + HGCMSvcSetStr(&aParms[0], "*"); + HGCMSvcSetPv(&aParms[1], pvBuf, cbBuf); + svcTable.pfnHostCall(svcTable.pvService, GUEST_PROP_FN_HOST_ENUM_PROPS, RT_ELEMENTS(aParms), aParms); + + RTTestGuardedFree(g_hTest, pvBuf); + } + } + + /* Done. */ + RTTESTI_CHECK_RC_OK(svcTable.pfnUnload(svcTable.pvService)); +} + +static void test6(void) +{ + RTTestISub("Max properties"); + + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + initTable(&svcTable, &svcHelpers); + RTTESTI_CHECK_RC_OK_RETV(VBoxHGCMSvcLoad(&svcTable)); + + /* Insert the max number of properties. */ + static char const s_szPropFmt[] = "/MyProperties/Sub/Sub/Sub/Sub/Sub/Sub/Sub/PropertyNo#%u"; + char szProp[80]; + unsigned cProps = 0; + for (;;) + { + RTStrPrintf(szProp, sizeof(szProp), s_szPropFmt, cProps); + int rc = doSetProperty(&svcTable, szProp, "myvalue", "", true, true); + if (rc == VERR_TOO_MUCH_DATA) + break; + if (RT_FAILURE(rc)) + { + RTTestIFailed("Unexpected error %Rrc setting property number %u", rc, cProps); + break; + } + cProps++; + } + RTTestIValue("Max Properties", cProps, RTTESTUNIT_OCCURRENCES); + + /* Touch them all again. */ + for (unsigned iProp = 0; iProp < cProps; iProp++) + { + RTStrPrintf(szProp, sizeof(szProp), s_szPropFmt, iProp); + int rc; + RTTESTI_CHECK_MSG((rc = doSetProperty(&svcTable, szProp, "myvalue", "", true, true)) == VINF_SUCCESS, + ("%Rrc - #%u\n", rc, iProp)); + RTTESTI_CHECK_MSG((rc = doSetProperty(&svcTable, szProp, "myvalue", "", true, false)) == VINF_SUCCESS, + ("%Rrc - #%u\n", rc, iProp)); + RTTESTI_CHECK_MSG((rc = doSetProperty(&svcTable, szProp, "myvalue", "", false, true)) == VINF_SUCCESS, + ("%Rrc - #%u\n", rc, iProp)); + RTTESTI_CHECK_MSG((rc = doSetProperty(&svcTable, szProp, "myvalue", "", false, false)) == VINF_SUCCESS, + ("%Rrc - #%u\n", rc, iProp)); + } + + /* Benchmark. */ + uint64_t cNsMax = 0; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsAvg = 0; + for (unsigned iProp = 0; iProp < cProps; iProp++) + { + size_t cchProp = RTStrPrintf(szProp, sizeof(szProp), s_szPropFmt, iProp); + + uint64_t cNsElapsed = RTTimeNanoTS(); + unsigned iCall; + for (iCall = 0; iCall < 1000; iCall++) + { + VBOXHGCMSVCPARM aParms[4]; + char szBuffer[256]; + HGCMSvcSetPv(&aParms[0], szProp, (uint32_t)cchProp + 1); + HGCMSvcSetPv(&aParms[1], szBuffer, sizeof(szBuffer)); + RTTESTI_CHECK_RC_BREAK(svcTable.pfnHostCall(svcTable.pvService, GUEST_PROP_FN_HOST_GET_PROP, 4, aParms), VINF_SUCCESS); + } + cNsElapsed = RTTimeNanoTS() - cNsElapsed; + if (iCall) + { + uint64_t cNsPerCall = cNsElapsed / iCall; + cNsAvg += cNsPerCall; + if (cNsPerCall < cNsMin) + cNsMin = cNsPerCall; + if (cNsPerCall > cNsMax) + cNsMax = cNsPerCall; + } + } + if (cProps) + cNsAvg /= cProps; + RTTestIValue("GET_PROP_HOST Min", cNsMin, RTTESTUNIT_NS_PER_CALL); + RTTestIValue("GET_PROP_HOST Avg", cNsAvg, RTTESTUNIT_NS_PER_CALL); + RTTestIValue("GET_PROP_HOST Max", cNsMax, RTTESTUNIT_NS_PER_CALL); + + /* Done. */ + RTTESTI_CHECK_RC_OK(svcTable.pfnUnload(svcTable.pvService)); +} + + + + +int main() +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstGuestPropSvc", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + testConvertFlags(); + test2(); + test3(); + test4(); + test5(); + test6(); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/HostServices/HostChannel/HostChannel.cpp b/src/VBox/HostServices/HostChannel/HostChannel.cpp new file mode 100644 index 00000000..d52e50d5 --- /dev/null +++ b/src/VBox/HostServices/HostChannel/HostChannel.cpp @@ -0,0 +1,1030 @@ +/** @file + * Host channel. + */ + +/* + * Copyright (C) 2012-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 + */ + +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/assert.h> + +#include "HostChannel.h" + + +static DECLCALLBACK(void) HostChannelCallbackEvent(void *pvCallbacks, void *pvInstance, + uint32_t u32Id, const void *pvEvent, uint32_t cbEvent); +static DECLCALLBACK(void) HostChannelCallbackDeleted(void *pvCallbacks, void *pvChannel); + + +/* A registered provider of channels. */ +typedef struct VBOXHOSTCHPROVIDER +{ + int32_t volatile cRefs; + + RTLISTNODE nodeContext; /* Member of the list of providers in the service context. */ + + VBOXHOSTCHCTX *pCtx; + + VBOXHOSTCHANNELINTERFACE iface; + + char *pszName; + + RTLISTANCHOR listChannels; +} VBOXHOSTCHPROVIDER; + +/* An established channel. */ +typedef struct VBOXHOSTCHINSTANCE +{ + int32_t volatile cRefs; + + RTLISTNODE nodeClient; /* In the client, for cleanup when a client disconnects. */ + RTLISTNODE nodeProvider; /* In the provider, needed for cleanup when the provider is unregistered. */ + + VBOXHOSTCHCLIENT *pClient; /* The client which uses the channel. */ + VBOXHOSTCHPROVIDER *pProvider; /* NULL if the provider was unregistered. */ + void *pvChannel; /* Provider's context of the channel. */ + uint32_t u32Handle; /* handle assigned to the channel by the service. */ +} VBOXHOSTCHINSTANCE; + +struct VBOXHOSTCHCTX +{ + bool fInitialized; + + RTLISTANCHOR listProviders; +}; + +/* The channel callbacks context. The provider passes the pointer as a callback parameter. + * Created for the provider and deleted when the provider says so. + */ +typedef struct VBOXHOSTCHCALLBACKCTX +{ + RTLISTNODE nodeClient; /* In the client, for cleanup when a client disconnects. */ + + VBOXHOSTCHCLIENT *pClient; /* The client which uses the channel, NULL when the client does not exist. */ +} VBOXHOSTCHCALLBACKCTX; + +/* Only one service instance is supported. */ +static VBOXHOSTCHCTX g_ctx = { false }; + +static VBOXHOSTCHANNELCALLBACKS g_callbacks = +{ + HostChannelCallbackEvent, + HostChannelCallbackDeleted +}; + + +/* + * Provider management. + */ + +static void vhcProviderDestroy(VBOXHOSTCHPROVIDER *pProvider) +{ + RTStrFree(pProvider->pszName); +} + +static int32_t vhcProviderAddRef(VBOXHOSTCHPROVIDER *pProvider) +{ + return ASMAtomicIncS32(&pProvider->cRefs); +} + +static void vhcProviderRelease(VBOXHOSTCHPROVIDER *pProvider) +{ + int32_t c = ASMAtomicDecS32(&pProvider->cRefs); + Assert(c >= 0); + if (c == 0) + { + vhcProviderDestroy(pProvider); + RTMemFree(pProvider); + } +} + +static VBOXHOSTCHPROVIDER *vhcProviderFind(VBOXHOSTCHCTX *pCtx, const char *pszName) +{ + VBOXHOSTCHPROVIDER *pProvider = NULL; + + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHPROVIDER *pIter; + RTListForEach(&pCtx->listProviders, pIter, VBOXHOSTCHPROVIDER, nodeContext) + { + if (RTStrCmp(pIter->pszName, pszName) == 0) + { + pProvider = pIter; + + vhcProviderAddRef(pProvider); + + break; + } + } + + vboxHostChannelUnlock(); + } + + return pProvider; +} + +static int vhcProviderRegister(VBOXHOSTCHCTX *pCtx, VBOXHOSTCHPROVIDER *pProvider) +{ + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + /** @todo check a duplicate. */ + + RTListAppend(&pCtx->listProviders, &pProvider->nodeContext); + + vboxHostChannelUnlock(); + } + + if (RT_FAILURE(rc)) + { + vhcProviderRelease(pProvider); + } + + return rc; +} + +static int vhcProviderUnregister(VBOXHOSTCHPROVIDER *pProvider) +{ + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + /** @todo check that the provider is in the list. */ + /** @todo mark the provider as invalid in each instance. also detach channels? */ + + RTListNodeRemove(&pProvider->nodeContext); + + vboxHostChannelUnlock(); + + vhcProviderRelease(pProvider); + } + + return rc; +} + + +/* + * Select an unique handle for the new channel. + * Works under the lock. + */ +static int vhcHandleCreate(VBOXHOSTCHCLIENT *pClient, uint32_t *pu32Handle) +{ + bool fOver = false; + + for(;;) + { + uint32_t u32Handle = ASMAtomicIncU32(&pClient->u32HandleSrc); + + if (u32Handle == 0) + { + if (fOver) + { + return VERR_NOT_SUPPORTED; + } + + fOver = true; + continue; + } + + VBOXHOSTCHINSTANCE *pDuplicate = NULL; + VBOXHOSTCHINSTANCE *pIter; + RTListForEach(&pClient->listChannels, pIter, VBOXHOSTCHINSTANCE, nodeClient) + { + if (pIter->u32Handle == u32Handle) + { + pDuplicate = pIter; + break; + } + } + + if (pDuplicate == NULL) + { + *pu32Handle = u32Handle; + break; + } + } + + return VINF_SUCCESS; +} + + +/* + * Channel instance management. + */ + +static void vhcInstanceDestroy(VBOXHOSTCHINSTANCE *pInstance) +{ + RT_NOREF1(pInstance); + HOSTCHLOG(("HostChannel: destroy %p\n", pInstance)); +} + +static int32_t vhcInstanceAddRef(VBOXHOSTCHINSTANCE *pInstance) +{ + HOSTCHLOG(("INST: %p %d addref\n", pInstance, pInstance->cRefs)); + return ASMAtomicIncS32(&pInstance->cRefs); +} + +static void vhcInstanceRelease(VBOXHOSTCHINSTANCE *pInstance) +{ + int32_t c = ASMAtomicDecS32(&pInstance->cRefs); + HOSTCHLOG(("INST: %p %d release\n", pInstance, pInstance->cRefs)); + Assert(c >= 0); + if (c == 0) + { + vhcInstanceDestroy(pInstance); + RTMemFree(pInstance); + } +} + +static int vhcInstanceCreate(VBOXHOSTCHCLIENT *pClient, VBOXHOSTCHINSTANCE **ppInstance) +{ + int rc = VINF_SUCCESS; + + VBOXHOSTCHINSTANCE *pInstance = (VBOXHOSTCHINSTANCE *)RTMemAllocZ(sizeof(VBOXHOSTCHINSTANCE)); + + if (pInstance) + { + rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + rc = vhcHandleCreate(pClient, &pInstance->u32Handle); + + if (RT_SUCCESS(rc)) + { + /* Used by the client, that is in the list of channels. */ + vhcInstanceAddRef(pInstance); + /* Add to the list of created channel instances. It is inactive while pClient is 0. */ + RTListAppend(&pClient->listChannels, &pInstance->nodeClient); + + /* Return to the caller. */ + vhcInstanceAddRef(pInstance); + *ppInstance = pInstance; + } + + vboxHostChannelUnlock(); + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pInstance); + } + } + else + { + rc = VERR_NO_MEMORY; + } + + return rc; +} + +static VBOXHOSTCHINSTANCE *vhcInstanceFind(VBOXHOSTCHCLIENT *pClient, uint32_t u32Handle) +{ + VBOXHOSTCHINSTANCE *pInstance = NULL; + + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHINSTANCE *pIter; + RTListForEach(&pClient->listChannels, pIter, VBOXHOSTCHINSTANCE, nodeClient) + { + if ( pIter->pClient + && pIter->u32Handle == u32Handle) + { + pInstance = pIter; + + vhcInstanceAddRef(pInstance); + + break; + } + } + + vboxHostChannelUnlock(); + } + + return pInstance; +} + +static VBOXHOSTCHINSTANCE *vhcInstanceFindByChannelPtr(VBOXHOSTCHCLIENT *pClient, void *pvChannel) +{ + VBOXHOSTCHINSTANCE *pInstance = NULL; + + if (pvChannel == NULL) + { + return NULL; + } + + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHINSTANCE *pIter; + RTListForEach(&pClient->listChannels, pIter, VBOXHOSTCHINSTANCE, nodeClient) + { + if ( pIter->pClient + && pIter->pvChannel == pvChannel) + { + pInstance = pIter; + + vhcInstanceAddRef(pInstance); + + break; + } + } + + vboxHostChannelUnlock(); + } + + return pInstance; +} + +static void vhcInstanceDetach(VBOXHOSTCHINSTANCE *pInstance) +{ + HOSTCHLOG(("HostChannel: detach %p\n", pInstance)); + + if (pInstance->pProvider) + { + pInstance->pProvider->iface.HostChannelDetach(pInstance->pvChannel); + RTListNodeRemove(&pInstance->nodeProvider); + vhcProviderRelease(pInstance->pProvider); + pInstance->pProvider = NULL; + vhcInstanceRelease(pInstance); /* Not in the provider's list anymore. */ + } + + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pInstance->nodeClient); + + vboxHostChannelUnlock(); + + vhcInstanceRelease(pInstance); /* Not used by the client anymore. */ + } +} + +/* + * Channel callback contexts. + */ +static int vhcCallbackCtxCreate(VBOXHOSTCHCLIENT *pClient, VBOXHOSTCHCALLBACKCTX **ppCallbackCtx) +{ + int rc = VINF_SUCCESS; + + VBOXHOSTCHCALLBACKCTX *pCallbackCtx = (VBOXHOSTCHCALLBACKCTX *)RTMemAllocZ(sizeof(VBOXHOSTCHCALLBACKCTX)); + + if (pCallbackCtx != NULL) + { + /* The callback context is accessed by the providers threads. */ + rc = vboxHostChannelLock(); + if (RT_SUCCESS(rc)) + { + RTListAppend(&pClient->listContexts, &pCallbackCtx->nodeClient); + pCallbackCtx->pClient = pClient; + + vboxHostChannelUnlock(); + } + else + { + RTMemFree(pCallbackCtx); + } + } + else + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + *ppCallbackCtx = pCallbackCtx; + } + + return rc; +} + +static int vhcCallbackCtxDelete(VBOXHOSTCHCALLBACKCTX *pCallbackCtx) +{ + int rc = vboxHostChannelLock(); + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHCLIENT *pClient = pCallbackCtx->pClient; + + if (pClient != NULL) + { + /* The callback is associated with a client. + * Check that the callback is in the list and remove it from the list. + */ + bool fFound = false; + + VBOXHOSTCHCALLBACKCTX *pIter; + RTListForEach(&pClient->listContexts, pIter, VBOXHOSTCHCALLBACKCTX, nodeClient) + { + if (pIter == pCallbackCtx) + { + fFound = true; + break; + } + } + + if (fFound) + { + RTListNodeRemove(&pCallbackCtx->nodeClient); + } + else + { + AssertFailed(); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + /* It is not in the clients anymore. May be the client has been disconnected. + * Just free the memory. + */ + } + + vboxHostChannelUnlock(); + } + + if (RT_SUCCESS(rc)) + { + RTMemFree(pCallbackCtx); + } + + return rc; +} + +/* + * Host channel service functions. + */ + +int vboxHostChannelInit(void) +{ + VBOXHOSTCHCTX *pCtx = &g_ctx; + + if (pCtx->fInitialized) + { + return VERR_NOT_SUPPORTED; + } + + pCtx->fInitialized = true; + RTListInit(&pCtx->listProviders); + + return VINF_SUCCESS; +} + +void vboxHostChannelDestroy(void) +{ + VBOXHOSTCHCTX *pCtx = &g_ctx; + + VBOXHOSTCHPROVIDER *pIter; + VBOXHOSTCHPROVIDER *pIterNext; + RTListForEachSafe(&pCtx->listProviders, pIter, pIterNext, VBOXHOSTCHPROVIDER, nodeContext) + { + vhcProviderUnregister(pIter); + } + pCtx->fInitialized = false; +} + +int vboxHostChannelClientConnect(VBOXHOSTCHCLIENT *pClient) +{ + /* A guest client is connecting to the service. + * Later the client will use Attach calls to connect to channel providers. + * pClient is already zeroed. + */ + pClient->pCtx = &g_ctx; + + RTListInit(&pClient->listChannels); + RTListInit(&pClient->listEvents); + RTListInit(&pClient->listContexts); + + return VINF_SUCCESS; +} + +void vboxHostChannelClientDisconnect(VBOXHOSTCHCLIENT *pClient) +{ + /* Clear the list of contexts and prevent acceess to the client. */ + int rc = vboxHostChannelLock(); + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHCALLBACKCTX *pIter; + VBOXHOSTCHCALLBACKCTX *pNext; + RTListForEachSafe(&pClient->listContexts, pIter, pNext, VBOXHOSTCHCALLBACKCTX, nodeClient) + { + pIter->pClient = NULL; + RTListNodeRemove(&pIter->nodeClient); + } + + vboxHostChannelUnlock(); + } + + /* If there are attached channels, detach them. */ + VBOXHOSTCHINSTANCE *pIter; + VBOXHOSTCHINSTANCE *pIterNext; + RTListForEachSafe(&pClient->listChannels, pIter, pIterNext, VBOXHOSTCHINSTANCE, nodeClient) + { + vhcInstanceDetach(pIter); + } +} + +int vboxHostChannelAttach(VBOXHOSTCHCLIENT *pClient, + uint32_t *pu32Handle, + const char *pszName, + uint32_t u32Flags) +{ + int rc = VINF_SUCCESS; + + HOSTCHLOG(("HostChannel: Attach: (%d) [%s] 0x%08X\n", pClient->u32ClientID, pszName, u32Flags)); + + /* Look if there is a provider. */ + VBOXHOSTCHPROVIDER *pProvider = vhcProviderFind(pClient->pCtx, pszName); + + if (pProvider) + { + VBOXHOSTCHINSTANCE *pInstance = NULL; + + rc = vhcInstanceCreate(pClient, &pInstance); + + if (RT_SUCCESS(rc)) + { + VBOXHOSTCHCALLBACKCTX *pCallbackCtx = NULL; + rc = vhcCallbackCtxCreate(pClient, &pCallbackCtx); + + if (RT_SUCCESS(rc)) + { + void *pvChannel = NULL; + rc = pProvider->iface.HostChannelAttach(pProvider->iface.pvProvider, + &pvChannel, + u32Flags, + &g_callbacks, pCallbackCtx); + + if (RT_SUCCESS(rc)) + { + vhcProviderAddRef(pProvider); + pInstance->pProvider = pProvider; + + pInstance->pClient = pClient; + pInstance->pvChannel = pvChannel; + + /* It is already in the channels list of the client. */ + + vhcInstanceAddRef(pInstance); /* Referenced by the list of provider's channels. */ + RTListAppend(&pProvider->listChannels, &pInstance->nodeProvider); + + *pu32Handle = pInstance->u32Handle; + + HOSTCHLOG(("HostChannel: Attach: (%d) handle %d\n", pClient->u32ClientID, pInstance->u32Handle)); + } + + if (RT_FAILURE(rc)) + { + vhcCallbackCtxDelete(pCallbackCtx); + } + } + + if (RT_FAILURE(rc)) + { + vhcInstanceDetach(pInstance); + } + + vhcInstanceRelease(pInstance); + } + + vhcProviderRelease(pProvider); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int vboxHostChannelDetach(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle) +{ + HOSTCHLOG(("HostChannel: Detach: (%d) handle %d\n", pClient->u32ClientID, u32Handle)); + + int rc = VINF_SUCCESS; + + VBOXHOSTCHINSTANCE *pInstance = vhcInstanceFind(pClient, u32Handle); + + if (pInstance) + { + vhcInstanceDetach(pInstance); + + vhcInstanceRelease(pInstance); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int vboxHostChannelSend(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + const void *pvData, + uint32_t cbData) +{ + HOSTCHLOG(("HostChannel: Send: (%d) handle %d, %d bytes\n", pClient->u32ClientID, u32Handle, cbData)); + + int rc = VINF_SUCCESS; + + VBOXHOSTCHINSTANCE *pInstance = vhcInstanceFind(pClient, u32Handle); + + if (pInstance) + { + if (pInstance->pProvider) + { + pInstance->pProvider->iface.HostChannelSend(pInstance->pvChannel, pvData, cbData); + } + + vhcInstanceRelease(pInstance); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int vboxHostChannelRecv(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeReceived, + uint32_t *pu32SizeRemaining) +{ + HOSTCHLOG(("HostChannel: Recv: (%d) handle %d, cbData %d\n", pClient->u32ClientID, u32Handle, cbData)); + + int rc = VINF_SUCCESS; + + VBOXHOSTCHINSTANCE *pInstance = vhcInstanceFind(pClient, u32Handle); + + if (pInstance) + { + if (pInstance->pProvider) + { + rc = pInstance->pProvider->iface.HostChannelRecv(pInstance->pvChannel, pvData, cbData, + pu32SizeReceived, pu32SizeRemaining); + + HOSTCHLOG(("HostChannel: Recv: (%d) handle %d, rc %Rrc, cbData %d, recv %d, rem %d\n", + pClient->u32ClientID, u32Handle, rc, cbData, *pu32SizeReceived, *pu32SizeRemaining)); + } + + vhcInstanceRelease(pInstance); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int vboxHostChannelControl(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned) +{ + HOSTCHLOG(("HostChannel: Control: (%d) handle %d, cbData %d\n", pClient->u32ClientID, u32Handle, cbData)); + + int rc = VINF_SUCCESS; + + VBOXHOSTCHINSTANCE *pInstance = vhcInstanceFind(pClient, u32Handle); + + if (pInstance) + { + if (pInstance->pProvider) + { + pInstance->pProvider->iface.HostChannelControl(pInstance->pvChannel, u32Code, + pvParm, cbParm, + pvData, cbData, pu32SizeDataReturned); + } + + vhcInstanceRelease(pInstance); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +typedef struct VBOXHOSTCHANNELEVENT +{ + RTLISTNODE NodeEvent; + + uint32_t u32ChannelHandle; + + uint32_t u32Id; + void *pvEvent; + uint32_t cbEvent; +} VBOXHOSTCHANNELEVENT; + +int vboxHostChannelEventWait(VBOXHOSTCHCLIENT *pClient, + bool *pfEvent, + VBOXHGCMCALLHANDLE callHandle, + VBOXHGCMSVCPARM *paParms) +{ + int rc = vboxHostChannelLock(); + if (RT_FAILURE(rc)) + { + return rc; + } + + if (pClient->fAsync) + { + /* If there is a wait request already, cancel it. */ + vboxHostChannelReportAsync(pClient, 0, VBOX_HOST_CHANNEL_EVENT_CANCELLED, NULL, 0); + pClient->fAsync = false; + } + + /* Check if there is something in the client's event queue. */ + VBOXHOSTCHANNELEVENT *pEvent = RTListGetFirst(&pClient->listEvents, VBOXHOSTCHANNELEVENT, NodeEvent); + + HOSTCHLOG(("HostChannel: QueryEvent: (%d), event %p\n", pClient->u32ClientID, pEvent)); + + if (pEvent) + { + /* Report the event. */ + RTListNodeRemove(&pEvent->NodeEvent); + + HOSTCHLOG(("HostChannel: QueryEvent: (%d), cbEvent %d\n", + pClient->u32ClientID, pEvent->cbEvent)); + + vboxHostChannelEventParmsSet(paParms, pEvent->u32ChannelHandle, + pEvent->u32Id, pEvent->pvEvent, pEvent->cbEvent); + + *pfEvent = true; + + RTMemFree(pEvent); + } + else + { + /* No event available at the time. Process asynchronously. */ + pClient->fAsync = true; + pClient->async.callHandle = callHandle; + pClient->async.paParms = paParms; + + /* Tell the caller that there is no event. */ + *pfEvent = false; + } + + vboxHostChannelUnlock(); + return rc; +} + +int vboxHostChannelEventCancel(VBOXHOSTCHCLIENT *pClient) +{ + int rc = vboxHostChannelLock(); + + if (RT_SUCCESS(rc)) + { + if (pClient->fAsync) + { + /* If there is a wait request alredy, cancel it. */ + vboxHostChannelReportAsync(pClient, 0, VBOX_HOST_CHANNEL_EVENT_CANCELLED, NULL, 0); + + pClient->fAsync = false; + } + + vboxHostChannelUnlock(); + } + + return rc; +} + +/* @thread provider */ +static DECLCALLBACK(void) HostChannelCallbackEvent(void *pvCallbacks, void *pvChannel, + uint32_t u32Id, const void *pvEvent, uint32_t cbEvent) +{ + VBOXHOSTCHCALLBACKCTX *pCallbackCtx = (VBOXHOSTCHCALLBACKCTX *)pvCallbacks; + + int rc = vboxHostChannelLock(); + if (RT_FAILURE(rc)) + { + return; + } + + /* Check that the structure is still associated with a client. + * The client can disconnect and will be invalid. + */ + VBOXHOSTCHCLIENT *pClient = pCallbackCtx->pClient; + + if (pClient == NULL) + { + vboxHostChannelUnlock(); + + HOSTCHLOG(("HostChannel: CallbackEvent[%p]: client gone.\n", pvEvent)); + + /* The client does not exist anymore, skip the event. */ + return; + } + + bool fFound = false; + + VBOXHOSTCHCALLBACKCTX *pIter; + RTListForEach(&pClient->listContexts, pIter, VBOXHOSTCHCALLBACKCTX, nodeClient) + { + if (pIter == pCallbackCtx) + { + fFound = true; + break; + } + } + + if (!fFound) + { + AssertFailed(); + + vboxHostChannelUnlock(); + + HOSTCHLOG(("HostChannel: CallbackEvent[%p]: client does not have the context.\n", pvEvent)); + + /* The context is not in the list of contexts. Skip the event. */ + return; + } + + VBOXHOSTCHINSTANCE *pInstance = vhcInstanceFindByChannelPtr(pClient, pvChannel); + + HOSTCHLOG(("HostChannel: CallbackEvent[%p]: (%d) instance %p\n", + pCallbackCtx, pClient->u32ClientID, pInstance)); + + if (!pInstance) + { + /* Instance was already detached. Skip the event. */ + vboxHostChannelUnlock(); + + return; + } + + uint32_t u32ChannelHandle = pInstance->u32Handle; + + HOSTCHLOG(("HostChannel: CallbackEvent: (%d) handle %d, async %d, cbEvent %d\n", + pClient->u32ClientID, u32ChannelHandle, pClient->fAsync, cbEvent)); + + /* Check whether the event is waited. */ + if (pClient->fAsync) + { + /* Report the event. */ + vboxHostChannelReportAsync(pClient, u32ChannelHandle, u32Id, pvEvent, cbEvent); + + pClient->fAsync = false; + } + else + { + /* Put it to the queue. */ + VBOXHOSTCHANNELEVENT *pEvent = (VBOXHOSTCHANNELEVENT *)RTMemAlloc(sizeof(VBOXHOSTCHANNELEVENT) + cbEvent); + + if (pEvent) + { + pEvent->u32ChannelHandle = u32ChannelHandle; + pEvent->u32Id = u32Id; + + if (cbEvent) + { + pEvent->pvEvent = &pEvent[1]; + memcpy(pEvent->pvEvent, pvEvent, cbEvent); + } + else + { + pEvent->pvEvent = NULL; + } + + pEvent->cbEvent = cbEvent; + + RTListAppend(&pClient->listEvents, &pEvent->NodeEvent); + } + } + + vboxHostChannelUnlock(); + + vhcInstanceRelease(pInstance); +} + +/* @thread provider */ +static DECLCALLBACK(void) HostChannelCallbackDeleted(void *pvCallbacks, void *pvChannel) +{ + RT_NOREF1(pvChannel); + vhcCallbackCtxDelete((VBOXHOSTCHCALLBACKCTX *)pvCallbacks); +} + +int vboxHostChannelQuery(VBOXHOSTCHCLIENT *pClient, + const char *pszName, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned) +{ + HOSTCHLOG(("HostChannel: Query: (%d) name [%s], cbData %d\n", pClient->u32ClientID, pszName, cbData)); + + int rc = VINF_SUCCESS; + + /* Look if there is a provider. */ + VBOXHOSTCHPROVIDER *pProvider = vhcProviderFind(pClient->pCtx, pszName); + + if (pProvider) + { + pProvider->iface.HostChannelControl(NULL, u32Code, + pvParm, cbParm, + pvData, cbData, pu32SizeDataReturned); + + vhcProviderRelease(pProvider); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int vboxHostChannelRegister(const char *pszName, + const VBOXHOSTCHANNELINTERFACE *pInterface, + uint32_t cbInterface) +{ + RT_NOREF1(cbInterface); + int rc = VINF_SUCCESS; + + VBOXHOSTCHCTX *pCtx = &g_ctx; + + VBOXHOSTCHPROVIDER *pProvider = (VBOXHOSTCHPROVIDER *)RTMemAllocZ(sizeof(VBOXHOSTCHPROVIDER)); + + if (pProvider) + { + pProvider->pCtx = pCtx; + pProvider->iface = *pInterface; + + RTListInit(&pProvider->listChannels); + + pProvider->pszName = RTStrDup(pszName); + if (pProvider->pszName) + { + vhcProviderAddRef(pProvider); + rc = vhcProviderRegister(pCtx, pProvider); + } + else + { + RTMemFree(pProvider); + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NO_MEMORY; + } + + return rc; +} + +int vboxHostChannelUnregister(const char *pszName) +{ + int rc = VINF_SUCCESS; + + VBOXHOSTCHCTX *pCtx = &g_ctx; + + VBOXHOSTCHPROVIDER *pProvider = vhcProviderFind(pCtx, pszName); + + if (pProvider) + { + rc = vhcProviderUnregister(pProvider); + vhcProviderRelease(pProvider); + } + + return rc; +} diff --git a/src/VBox/HostServices/HostChannel/HostChannel.h b/src/VBox/HostServices/HostChannel/HostChannel.h new file mode 100644 index 00000000..75cb0385 --- /dev/null +++ b/src/VBox/HostServices/HostChannel/HostChannel.h @@ -0,0 +1,147 @@ +/* @file + * + * Host Channel + */ + +/* + * Copyright (C) 2012-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 + */ + +#ifndef VBOX_INCLUDED_SRC_HostChannel_HostChannel_h +#define VBOX_INCLUDED_SRC_HostChannel_HostChannel_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/list.h> + +#define LOG_GROUP LOG_GROUP_HGCM +#include <VBox/log.h> +#include <VBox/HostServices/VBoxHostChannel.h> + +#define HOSTCHLOG Log + +#ifdef DEBUG_sunlover +# undef HOSTCHLOG +# define HOSTCHLOG LogRel +#endif /* DEBUG_sunlover */ + +struct VBOXHOSTCHCTX; +typedef struct VBOXHOSTCHCTX VBOXHOSTCHCTX; + +typedef struct VBOXHOSTCHCLIENT +{ + RTLISTNODE nodeClient; + + VBOXHOSTCHCTX *pCtx; + + uint32_t u32ClientID; + + RTLISTANCHOR listChannels; + uint32_t volatile u32HandleSrc; + + RTLISTANCHOR listContexts; /* Callback contexts. */ + + RTLISTANCHOR listEvents; + + bool fAsync; /* Guest is waiting for a message. */ + + struct { + VBOXHGCMCALLHANDLE callHandle; + VBOXHGCMSVCPARM *paParms; + } async; + +} VBOXHOSTCHCLIENT; + + +/* + * The service functions. Locking is between the service thread and the host channel provider thread. + */ +int vboxHostChannelLock(void); +void vboxHostChannelUnlock(void); + +int vboxHostChannelInit(void); +void vboxHostChannelDestroy(void); + +int vboxHostChannelClientConnect(VBOXHOSTCHCLIENT *pClient); +void vboxHostChannelClientDisconnect(VBOXHOSTCHCLIENT *pClient); + +int vboxHostChannelAttach(VBOXHOSTCHCLIENT *pClient, + uint32_t *pu32Handle, + const char *pszName, + uint32_t u32Flags); +int vboxHostChannelDetach(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle); + +int vboxHostChannelSend(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + const void *pvData, + uint32_t cbData); +int vboxHostChannelRecv(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + void *pvData, + uint32_t cbData, + uint32_t *pu32DataReceived, + uint32_t *pu32DataRemaining); +int vboxHostChannelControl(VBOXHOSTCHCLIENT *pClient, + uint32_t u32Handle, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned); + +int vboxHostChannelEventWait(VBOXHOSTCHCLIENT *pClient, + bool *pfEvent, + VBOXHGCMCALLHANDLE callHandle, + VBOXHGCMSVCPARM *paParms); + +int vboxHostChannelEventCancel(VBOXHOSTCHCLIENT *pClient); + +int vboxHostChannelQuery(VBOXHOSTCHCLIENT *pClient, + const char *pszName, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned); + +int vboxHostChannelRegister(const char *pszName, + const VBOXHOSTCHANNELINTERFACE *pInterface, + uint32_t cbInterface); +int vboxHostChannelUnregister(const char *pszName); + + +void vboxHostChannelEventParmsSet(VBOXHGCMSVCPARM *paParms, + uint32_t u32ChannelHandle, + uint32_t u32Id, + const void *pvEvent, + uint32_t cbEvent); + +void vboxHostChannelReportAsync(VBOXHOSTCHCLIENT *pClient, + uint32_t u32ChannelHandle, + uint32_t u32Id, + const void *pvEvent, + uint32_t cbEvent); + +#endif /* !VBOX_INCLUDED_SRC_HostChannel_HostChannel_h */ diff --git a/src/VBox/HostServices/HostChannel/Makefile.kmk b/src/VBox/HostServices/HostChannel/Makefile.kmk new file mode 100644 index 00000000..377bd7e6 --- /dev/null +++ b/src/VBox/HostServices/HostChannel/Makefile.kmk @@ -0,0 +1,50 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Host Channel Service. +# + +# +# Copyright (C) 2012-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# The service DLL. +# +DLLS += VBoxHostChannel +VBoxHostChannel_TEMPLATE = VBoxR3Dll +VBoxHostChannel_DEFS = VBOX_WITH_HGCM +VBoxHostChannel_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxHostChannel_SOURCES = \ + VBoxHostChannelSvc.cpp \ + HostChannel.cpp + +VBoxHostChannel_SOURCES.win = \ + VBoxHostChannelSvc.rc + +VBoxHostChannel_LIBS = \ + $(LIB_RUNTIME) + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.cpp b/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.cpp new file mode 100644 index 00000000..3df8b9d5 --- /dev/null +++ b/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.cpp @@ -0,0 +1,891 @@ +/* $Id: VBoxHostChannelSvc.cpp $ */ +/* @file + * Host Channel: Host service entry points. + */ + +/* + * Copyright (C) 2012-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 + */ + + +/* + * The HostChannel host service provides a generic proxy between a host's + * channel provider and a client running in the guest. + * + * Host providers must register via a HostCall. + * + * A guest client can connect to a host provider and send/receive data. + * + * GuestCalls: + * * Attach - attach to a host channel + * * Detach - completely detach from a channel + * * Send - send data from the guest to the channel + * * Recv - non blocking read of available data from the channel + * * Control - generic channel specific command exchange + * * EventWait - wait for a host event + * * EventCancel - make the blocking EventWait call to return + * HostCalls: + * * Register - register a host channel + * * Unregister - unregister it + * + * The guest HGCM client connects to the service. The client can attach multiple channels. + * + */ + +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <VBox/vmm/ssm.h> + +#include "HostChannel.h" + + +static void VBoxHGCMParmUInt32Set(VBOXHGCMSVCPARM *pParm, uint32_t u32) +{ + pParm->type = VBOX_HGCM_SVC_PARM_32BIT; + pParm->u.uint32 = u32; +} + +static int VBoxHGCMParmUInt32Get(VBOXHGCMSVCPARM *pParm, uint32_t *pu32) +{ + if (pParm->type == VBOX_HGCM_SVC_PARM_32BIT) + { + *pu32 = pParm->u.uint32; + return VINF_SUCCESS; + } + + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + +#if 0 /* unused */ +static void VBoxHGCMParmPtrSet(VBOXHGCMSVCPARM *pParm, void *pv, uint32_t cb) +{ + pParm->type = VBOX_HGCM_SVC_PARM_PTR; + pParm->u.pointer.size = cb; + pParm->u.pointer.addr = pv; +} +#endif + +static int VBoxHGCMParmPtrGet(VBOXHGCMSVCPARM *pParm, void **ppv, uint32_t *pcb) +{ + if (pParm->type == VBOX_HGCM_SVC_PARM_PTR) + { + *ppv = pParm->u.pointer.addr; + *pcb = pParm->u.pointer.size; + return VINF_SUCCESS; + } + + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + + +static PVBOXHGCMSVCHELPERS g_pHelpers = NULL; + +static RTCRITSECT g_critsect; + +/* + * Helpers. + */ + +int vboxHostChannelLock(void) +{ + return RTCritSectEnter(&g_critsect); +} + +void vboxHostChannelUnlock(void) +{ + RTCritSectLeave(&g_critsect); +} + +void vboxHostChannelEventParmsSet(VBOXHGCMSVCPARM *paParms, + uint32_t u32ChannelHandle, + uint32_t u32Id, + const void *pvEvent, + uint32_t cbEvent) +{ + if (cbEvent > 0) + { + void *pvParm = NULL; + uint32_t cbParm = 0; + + VBoxHGCMParmPtrGet(&paParms[2], &pvParm, &cbParm); + + uint32_t cbToCopy = RT_MIN(cbParm, cbEvent); + if (cbToCopy > 0) + { + Assert(pvParm); + memcpy(pvParm, pvEvent, cbToCopy); + } + } + + VBoxHGCMParmUInt32Set(&paParms[0], u32ChannelHandle); + VBoxHGCMParmUInt32Set(&paParms[1], u32Id); + VBoxHGCMParmUInt32Set(&paParms[3], cbEvent); +} + +/* This is called under the lock. */ +void vboxHostChannelReportAsync(VBOXHOSTCHCLIENT *pClient, + uint32_t u32ChannelHandle, + uint32_t u32Id, + const void *pvEvent, + uint32_t cbEvent) +{ + Assert(RTCritSectIsOwner(&g_critsect)); + + vboxHostChannelEventParmsSet(pClient->async.paParms, + u32ChannelHandle, + u32Id, + pvEvent, + cbEvent); + + LogRelFlow(("svcCall: CallComplete for pending\n")); + + g_pHelpers->pfnCallComplete (pClient->async.callHandle, VINF_SUCCESS); +} + + +/* + * Service entry points. + */ + +static DECLCALLBACK(int) svcUnload(void *pvService) +{ + NOREF(pvService); + vboxHostChannelDestroy(); + RTCritSectDelete(&g_critsect); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect(void *pvService, uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF2(pvService, u32ClientID); + + VBOXHOSTCHCLIENT *pClient = (VBOXHOSTCHCLIENT *)pvClient; + + vboxHostChannelClientDisconnect(pClient); + + memset(pClient, 0, sizeof(VBOXHOSTCHCLIENT)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcConnect(void *pvService, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + RT_NOREF(pvService, fRequestor, fRestoring); + VBOXHOSTCHCLIENT *pClient = (VBOXHOSTCHCLIENT *)pvClient; + + /* Register the client. */ + memset(pClient, 0, sizeof(VBOXHOSTCHCLIENT)); + + pClient->u32ClientID = u32ClientID; + + int rc = vboxHostChannelClientConnect(pClient); + + LogRel2(("svcConnect: rc = %Rrc\n", rc)); + + return rc; +} + +static DECLCALLBACK(void) svcCall(void *pvService, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32ClientID, + void *pvClient, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[], + uint64_t tsArrival) +{ + RT_NOREF(pvService, tsArrival); + + int rc = VINF_SUCCESS; + + LogRel2(("svcCall: u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n", + u32ClientID, u32Function, cParms, paParms)); + + VBOXHOSTCHCLIENT *pClient = (VBOXHOSTCHCLIENT *)pvClient; + + bool fAsynchronousProcessing = false; + +#ifdef DEBUG + uint32_t i; + + for (i = 0; i < cParms; i++) + { + /** @todo parameters other than 32 bit */ + LogRel2((" pparms[%d]: type %d value %d\n", i, paParms[i].type, paParms[i].u.uint32)); + } +#endif + + switch (u32Function) + { + case VBOX_HOST_CHANNEL_FN_ATTACH: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_ATTACH\n")); + + if (cParms != 3) + rc = VERR_INVALID_PARAMETER; + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* name */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + ) + rc = VERR_INVALID_PARAMETER; + else + { + uint32_t u32Flags = 0; /* Shut up msvc*/ + const char *pszName; + uint32_t cbName; + + rc = VBoxHGCMParmPtrGet(&paParms[0], (void **)&pszName, &cbName); + if ( RT_SUCCESS(rc) + && pszName[cbName - 1] != '\0') + rc = VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + rc = VBoxHGCMParmUInt32Get(&paParms[1], &u32Flags); + if (RT_SUCCESS(rc)) + { + uint32_t u32Handle = 0; + rc = vboxHostChannelAttach(pClient, &u32Handle, pszName, u32Flags); + if (RT_SUCCESS(rc)) + VBoxHGCMParmUInt32Set(&paParms[2], u32Handle); + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_DETACH: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_DETACH\n")); + + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Handle; + + rc = VBoxHGCMParmUInt32Get(&paParms[0], &u32Handle); + + if (RT_SUCCESS(rc)) + { + rc = vboxHostChannelDetach(pClient, u32Handle); + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_SEND: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_SEND\n")); + + if (cParms != 2) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* data */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Handle; + void *pvData; + uint32_t cbData; + + rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Handle); + + if (RT_SUCCESS (rc)) + { + rc = VBoxHGCMParmPtrGet (&paParms[1], &pvData, &cbData); + + if (RT_SUCCESS (rc)) + { + rc = vboxHostChannelSend(pClient, u32Handle, pvData, cbData); + } + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_RECV: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_RECV\n")); + + if (cParms != 4) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* data */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* sizeReceived */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* sizeRemaining */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Handle; + void *pvData; + uint32_t cbData; + + rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Handle); + + if (RT_SUCCESS (rc)) + { + rc = VBoxHGCMParmPtrGet (&paParms[1], &pvData, &cbData); + + if (RT_SUCCESS (rc)) + { + uint32_t u32SizeReceived = 0; + uint32_t u32SizeRemaining = 0; + + rc = vboxHostChannelRecv(pClient, u32Handle, + pvData, cbData, + &u32SizeReceived, &u32SizeRemaining); + + if (RT_SUCCESS(rc)) + { + VBoxHGCMParmUInt32Set(&paParms[2], u32SizeReceived); + VBoxHGCMParmUInt32Set(&paParms[3], u32SizeRemaining); + } + } + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_CONTROL: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_CONTROL\n")); + + if (cParms != 5) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* code */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* parm */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* data */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_32BIT /* sizeDataReturned */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Handle; + uint32_t u32Code; + void *pvParm; + uint32_t cbParm; + void *pvData; + uint32_t cbData; + + rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Handle); + + if (RT_SUCCESS (rc)) + { + rc = VBoxHGCMParmUInt32Get (&paParms[1], &u32Code); + + if (RT_SUCCESS (rc)) + { + rc = VBoxHGCMParmPtrGet (&paParms[2], &pvParm, &cbParm); + + if (RT_SUCCESS (rc)) + { + rc = VBoxHGCMParmPtrGet (&paParms[3], &pvData, &cbData); + + if (RT_SUCCESS (rc)) + { + uint32_t u32SizeDataReturned = 0; + + rc = vboxHostChannelControl(pClient, u32Handle, u32Code, + pvParm, cbParm, + pvData, cbData, &u32SizeDataReturned); + if (RT_SUCCESS(rc)) + { + VBoxHGCMParmUInt32Set(&paParms[4], u32SizeDataReturned); + } + } + } + } + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_EVENT_WAIT: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_EVENT_WAIT\n")); + + if (cParms != 4) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* handle */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* id */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* parm */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* sizeReturned */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + bool fEvent = false; + + rc = vboxHostChannelEventWait(pClient, &fEvent, callHandle, paParms); + + if (RT_SUCCESS(rc)) + { + if (!fEvent) + { + /* No event available at the time. Process asynchronously. */ + fAsynchronousProcessing = true; + + LogRel2(("svcCall: async.\n")); + } + } + } + } break; + + case VBOX_HOST_CHANNEL_FN_EVENT_CANCEL: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_EVENT_CANCEL\n")); + + if (cParms != 0) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + rc = vboxHostChannelEventCancel(pClient); + } + } break; + + case VBOX_HOST_CHANNEL_FN_QUERY: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_FN_QUERY\n")); + + if (cParms != 5) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* channel name */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* code */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* parm */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* data */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_32BIT /* sizeDataReturned */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + const char *pszName; + uint32_t cbName; + uint32_t u32Code = 0; /* Shut up msvc*/ + void *pvParm = NULL; /* Shut up msvc*/ + uint32_t cbParm = 0; /* Shut up msvc*/ + void *pvData = NULL; /* Shut up msvc*/ + uint32_t cbData = 0; /* Shut up msvc*/ + + rc = VBoxHGCMParmPtrGet(&paParms[0], (void **)&pszName, &cbName); + if ( RT_SUCCESS(rc) + && pszName[cbName - 1] != '\0') + rc = VERR_INVALID_PARAMETER; + + if (RT_SUCCESS(rc)) + rc = VBoxHGCMParmUInt32Get(&paParms[1], &u32Code); + if (RT_SUCCESS (rc)) + rc = VBoxHGCMParmPtrGet(&paParms[2], &pvParm, &cbParm); + if (RT_SUCCESS (rc)) + rc = VBoxHGCMParmPtrGet(&paParms[3], &pvData, &cbData); + if (RT_SUCCESS (rc)) + { + uint32_t u32SizeDataReturned = 0; + rc = vboxHostChannelQuery(pClient, pszName, u32Code, + pvParm, cbParm, + pvData, cbData, &u32SizeDataReturned); + if (RT_SUCCESS(rc)) + VBoxHGCMParmUInt32Set(&paParms[4], u32SizeDataReturned); + } + } + } break; + + default: + { + rc = VERR_NOT_IMPLEMENTED; + } + } + + LogRelFlow(("svcCall: rc = %Rrc, async %d\n", rc, fAsynchronousProcessing)); + + if (!fAsynchronousProcessing) + { + g_pHelpers->pfnCallComplete(callHandle, rc); + } +} + +static DECLCALLBACK(int) svcHostCall(void *pvService, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[]) +{ + NOREF(pvService); + + int rc = VINF_SUCCESS; + + LogRel2(("svcHostCall: fn = %d, cParms = %d, pparms = %d\n", + u32Function, cParms, paParms)); + + switch (u32Function) + { + case VBOX_HOST_CHANNEL_HOST_FN_REGISTER: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_HOST_FN_REGISTER\n")); + + if (cParms != 2) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* name */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* iface */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + void *pvName; + uint32_t cbName; + void *pvInterface; + uint32_t cbInterface; + + rc = VBoxHGCMParmPtrGet(&paParms[0], &pvName, &cbName); + + if (RT_SUCCESS(rc)) + { + rc = VBoxHGCMParmPtrGet(&paParms[1], &pvInterface, &cbInterface); + + if (RT_SUCCESS(rc)) + { + rc = vboxHostChannelRegister((const char *)pvName, + (VBOXHOSTCHANNELINTERFACE *)pvInterface, cbInterface); + } + } + } + } break; + + case VBOX_HOST_CHANNEL_HOST_FN_UNREGISTER: + { + LogRel2(("svcCall: VBOX_HOST_CHANNEL_HOST_FN_UNREGISTER\n")); + + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* name */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + void *pvName; + uint32_t cbName; + + rc = VBoxHGCMParmPtrGet(&paParms[0], &pvName, &cbName); + + if (RT_SUCCESS(rc)) + { + rc = vboxHostChannelUnregister((const char *)pvName); + } + } + } break; + + default: + break; + } + + LogRelFlow(("svcHostCall: rc = %Rrc\n", rc)); + return rc; +} + +#if 0 +/** If the client in the guest is waiting for a read operation to complete + * then complete it, otherwise return. See the protocol description in the + * shared clipboard module description. */ +void vboxSvcClipboardCompleteReadData(VBOXHOSTCHCLIENT *pClient, int rc, uint32_t cbActual) +{ + VBOXHGCMCALLHANDLE callHandle = NULL; + VBOXHGCMSVCPARM *paParms = NULL; + bool fReadPending = false; + if (vboxSvcClipboardLock()) /* if not can we do anything useful? */ + { + callHandle = pClient->asyncRead.callHandle; + paParms = pClient->asyncRead.paParms; + fReadPending = pClient->fReadPending; + pClient->fReadPending = false; + vboxSvcClipboardUnlock(); + } + if (fReadPending) + { + VBoxHGCMParmUInt32Set (&paParms[2], cbActual); + g_pHelpers->pfnCallComplete (callHandle, rc); + } +} + +/** + * SSM descriptor table for the VBOXHOSTCHCLIENT structure. + */ +static SSMFIELD const g_aClipboardClientDataFields[] = +{ + SSMFIELD_ENTRY(VBOXHOSTCHCLIENT, u32ClientID), /* for validation purposes */ + SSMFIELD_ENTRY(VBOXHOSTCHCLIENT, fMsgQuit), + SSMFIELD_ENTRY(VBOXHOSTCHCLIENT, fMsgReadData), + SSMFIELD_ENTRY(VBOXHOSTCHCLIENT, fMsgFormats), + SSMFIELD_ENTRY(VBOXHOSTCHCLIENT, u32RequestedFormat), + SSMFIELD_ENTRY_TERM() +}; + +static DECLCALLBACK(int) svcSaveState(void *pvService, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM) +{ + NOREF(pvService); + + /* If there are any pending requests, they must be completed here. Since + * the service is single threaded, there could be only requests + * which the service itself has postponed. + * + * HGCM knows that the state is being saved and that the pfnComplete + * calls are just clean ups. These requests are saved by the VMMDev. + * + * When the state will be restored, these requests will be reissued + * by VMMDev. The service therefore must save state as if there were no + * pending request. + */ + LogRel2 (("svcSaveState: u32ClientID = %d\n", u32ClientID)); + + VBOXHOSTCHCLIENT *pClient = (VBOXHOSTCHCLIENT *)pvClient; + + /* This field used to be the length. We're using it as a version field + with the high bit set. */ + SSMR3PutU32 (pSSM, UINT32_C (0x80000002)); + int rc = SSMR3PutStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL); + AssertRCReturn (rc, rc); + + if (pClient->fAsync) + { + g_pHelpers->pfnCallComplete (pClient->async.callHandle, VINF_SUCCESS /* error code is not important here. */); + pClient->fAsync = false; + } + + vboxSvcClipboardCompleteReadData (pClient, VINF_SUCCESS, 0); + + return VINF_SUCCESS; +} + +/** + * This structure corresponds to the original layout of the + * VBOXHOSTCHCLIENT structure. As the structure was saved as a whole + * when saving state, we need to remember it forever in order to preserve + * compatibility. + * + * (Starting with 3.1 this is no longer used.) + * + * @remarks Putting this outside svcLoadState to avoid visibility warning caused + * by -Wattributes. + */ +typedef struct CLIPSAVEDSTATEDATA +{ + struct CLIPSAVEDSTATEDATA *pNext; + struct CLIPSAVEDSTATEDATA *pPrev; + + VBOXCLIPBOARDCONTEXT *pCtx; + + uint32_t u32ClientID; + + bool fAsync: 1; /* Guest is waiting for a message. */ + + bool fMsgQuit: 1; + bool fMsgReadData: 1; + bool fMsgFormats: 1; + + struct { + VBOXHGCMCALLHANDLE callHandle; + VBOXHGCMSVCPARM *paParms; + } async; + + struct { + void *pv; + uint32_t cb; + uint32_t u32Format; + } data; + + uint32_t u32AvailableFormats; + uint32_t u32RequestedFormat; + +} CLIPSAVEDSTATEDATA; + +static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM) +{ + LogRel2 (("svcLoadState: u32ClientID = %d\n", u32ClientID)); + + VBOXHOSTCHCLIENT *pClient = (VBOXHOSTCHCLIENT *)pvClient; + + /* Existing client can not be in async state yet. */ + Assert (!pClient->fAsync); + + /* Save the client ID for data validation. */ + /** @todo isn't this the same as u32ClientID? Playing safe for now... */ + uint32_t const u32ClientIDOld = pClient->u32ClientID; + + /* Restore the client data. */ + uint32_t lenOrVer; + int rc = SSMR3GetU32 (pSSM, &lenOrVer); + AssertRCReturn (rc, rc); + if (lenOrVer == UINT32_C (0x80000002)) + { + rc = SSMR3GetStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL); + AssertRCReturn (rc, rc); + } + else if (lenOrVer == (SSMR3HandleHostBits (pSSM) == 64 ? 72 : 48)) + { + /** + * SSM descriptor table for the CLIPSAVEDSTATEDATA structure. + */ + static SSMFIELD const s_aClipSavedStateDataFields30[] = + { + SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pNext), + SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pPrev), + SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pCtx), + SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32ClientID), + SSMFIELD_ENTRY_CUSTOM(fMsgQuit+fMsgReadData+fMsgFormats, RT_OFFSETOF(CLIPSAVEDSTATEDATA, u32ClientID) + 4, 4), + SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.callHandle), + SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.paParms), + SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.pv), + SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.cb), + SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.u32Format), + SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, u32AvailableFormats), + SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32RequestedFormat), + SSMFIELD_ENTRY_TERM() + }; + + CLIPSAVEDSTATEDATA savedState; + RT_ZERO (savedState); + rc = SSMR3GetStructEx (pSSM, &savedState, sizeof(savedState), SSMSTRUCT_FLAGS_MEM_BAND_AID, + &s_aClipSavedStateDataFields30[0], NULL); + AssertRCReturn (rc, rc); + + pClient->fMsgQuit = savedState.fMsgQuit; + pClient->fMsgReadData = savedState.fMsgReadData; + pClient->fMsgFormats = savedState.fMsgFormats; + pClient->u32RequestedFormat = savedState.u32RequestedFormat; + } + else + { + LogRel (("Client data size mismatch: got %#x\n", lenOrVer)); + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + } + + /* Verify the client ID. */ + if (pClient->u32ClientID != u32ClientIDOld) + { + LogRel (("Client ID mismatch: expected %d, got %d\n", u32ClientIDOld, pClient->u32ClientID)); + pClient->u32ClientID = u32ClientIDOld; + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + } + + /* Actual host data are to be reported to guest (SYNC). */ + vboxClipboardSync (pClient); + + return VINF_SUCCESS; +} +#endif + +static int svcInit(void) +{ + int rc = RTCritSectInit(&g_critsect); + + if (RT_SUCCESS (rc)) + { + rc = vboxHostChannelInit(); + + /* Clean up on failure, because 'svnUnload' will not be called + * if the 'svcInit' returns an error. + */ + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&g_critsect); + } + } + + return rc; +} + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = VINF_SUCCESS; + + LogRelFlowFunc(("pTable = %p\n", pTable)); + + if (!pTable) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + LogRel2(("VBoxHGCMSvcLoad: 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_INVALID_PARAMETER; + } + else + { + g_pHelpers = pTable->pHelpers; + + pTable->cbClient = sizeof(VBOXHOSTCHCLIENT); + + pTable->pfnUnload = svcUnload; + pTable->pfnConnect = svcConnect; + pTable->pfnDisconnect = svcDisconnect; + pTable->pfnCall = svcCall; + pTable->pfnHostCall = svcHostCall; + pTable->pfnSaveState = NULL; // svcSaveState; + pTable->pfnLoadState = NULL; // svcLoadState; + pTable->pfnRegisterExtension = NULL; + pTable->pvService = NULL; + + /* Service specific initialization. */ + rc = svcInit(); + } + } + + return rc; +} diff --git a/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.rc b/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.rc new file mode 100644 index 00000000..aabf0874 --- /dev/null +++ b/src/VBox/HostServices/HostChannel/VBoxHostChannelSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxHostChannelSvc.rc $ */ +/** @file + * VBoxHostChannel - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Host Channel Service\0" + VALUE "InternalName", "VBoxHostChannel\0" + VALUE "OriginalFilename", "VBoxHostChannel.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/Makefile.kmk b/src/VBox/HostServices/Makefile.kmk new file mode 100644 index 00000000..fccaf42b --- /dev/null +++ b/src/VBox/HostServices/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# Top-level makefile for the VBox Host Services. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +include $(PATH_SUB_CURRENT)/auth/Makefile.kmk +ifdef VBOX_WITH_SHARED_FOLDERS + include $(PATH_SUB_CURRENT)/SharedFolders/Makefile.kmk +endif +if1of ($(KBUILD_TARGET), win linux solaris darwin freebsd) + ifdef VBOX_WITH_SHARED_CLIPBOARD + include $(PATH_SUB_CURRENT)/SharedClipboard/Makefile.kmk + endif +endif +ifdef VBOX_WITH_GUEST_PROPS + include $(PATH_SUB_CURRENT)/GuestProperties/Makefile.kmk +endif +ifdef VBOX_WITH_GUEST_CONTROL + include $(PATH_SUB_CURRENT)/GuestControl/Makefile.kmk +endif +ifdef VBOX_WITH_DRAG_AND_DROP + include $(PATH_SUB_CURRENT)/DragAndDrop/Makefile.kmk +endif +ifdef VBOX_WITH_HOST_CHANNEL + include $(PATH_SUB_CURRENT)/HostChannel/Makefile.kmk +endif +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk new file mode 100644 index 00000000..e8c9b7b6 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -0,0 +1,121 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The Shared Clipboard service DLL. +# +DLLS += VBoxSharedClipboard +VBoxSharedClipboard_TEMPLATE = VBoxR3Dll +VBoxSharedClipboard_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) +VBoxSharedClipboard_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxSharedClipboard_SOURCES = \ + VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +VBoxSharedClipboard_SOURCES.win = \ + VBoxSharedClipboardSvc-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ + VBoxSharedClipboardSvc.rc +VBoxSharedClipboard_SOURCES.darwin = \ + VBoxSharedClipboardSvc-darwin.cpp \ + darwin-pasteboard.cpp +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if1of ($(KBUILD_TARGET), linux) + VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + VBoxSharedClipboard_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp + endif + VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_HOST + VBoxSharedClipboard_SOURCES += \ + VBoxSharedClipboardSvc-transfers.cpp \ + VBoxSharedClipboardSvc-utils.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + VBoxSharedClipboard_SOURCES.win += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp +endif +if1of ($(KBUILD_TARGET), linux solaris freebsd) ## @todo X11 + ifndef VBOX_HEADLESS + VBoxSharedClipboard_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ + VBoxSharedClipboardSvc-x11.cpp + else + VBoxSharedClipboard_SOURCES += \ + VBoxSharedClipboardSvc-x11-stubs.cpp + endif +endif + +VBoxSharedClipboard_LIBS = \ + $(LIB_RUNTIME) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + ifndef VBOX_HEADLESS + VBoxSharedClipboard_LIBPATH = \ + $(VBOX_LIBPATH_X11) + VBoxSharedClipboard_LIBS += \ + Xt \ + X11 + endif +endif + +VBoxSharedClipboard_LDFLAGS.darwin = \ + -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib + +if 0 ## Disabled for now; needs to be adapted to the new protocol first. + if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) + # + # Set this in LocalConfig.kmk if you are working on the X11 clipboard service + # to automatically run the unit test at build time. + # OTHERS += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + PROGRAMS += tstClipboardX11-2 + TESTING += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + tstClipboardX11-2_TEMPLATE = VBoxR3TstExe + tstClipboardX11-2_DEFS = VBOX_WITH_HGCM TESTCASE + tstClipboardX11-2_SOURCES = VBoxSharedClipboardSvc-x11.cpp + tstClipboardX11-2_LIBS = $(LIB_RUNTIME) + tstClipboardX11-2_CLEAN = $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + + $$(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run: $$(tstClipboardX11-2_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardX11-2_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif # 1of ($(KBUILD_TARGET),freebsd linux netbsd openbsd solaris) + endif +endif + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp new file mode 100644 index 00000000..23e6cd8d --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp @@ -0,0 +1,346 @@ +/* $Id: VBoxSharedClipboardSvc-darwin.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host. + */ + +/* + * Copyright (C) 2008-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#include "darwin-pasteboard.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Global clipboard context information */ +typedef struct SHCLCONTEXT +{ + /** We have a separate thread to poll for new clipboard content. */ + RTTHREAD hThread; + /** Termination indicator. */ + bool volatile fTerminate; + /** The reference to the current pasteboard */ + PasteboardRef hPasteboard; + /** Shared clipboard client. */ + PSHCLCLIENT pClient; + /** Random 64-bit number embedded into szGuestOwnershipFlavor. */ + uint64_t idGuestOwnership; + /** Ownership flavor CFStringRef returned by takePasteboardOwnership(). + * This is the same a szGuestOwnershipFlavor only in core foundation terms. */ + void *hStrOwnershipFlavor; + /** The guest ownership flavor (type) string. */ + char szGuestOwnershipFlavor[64]; +} SHCLCONTEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Only one client is supported. There seems to be no need for more clients. */ +static SHCLCONTEXT g_ctx; + + +/** + * Checks if something is present on the clipboard and calls shclSvcReportMsg. + * + * @returns IPRT status code (ignored). + * @param pCtx The context. + * + * @note Call must own lock. + */ +static int vboxClipboardChanged(SHCLCONTEXT *pCtx) +{ + if (pCtx->pClient == NULL) + return VINF_SUCCESS; + + /* Retrieve the formats currently in the clipboard and supported by vbox */ + uint32_t fFormats = 0; + bool fChanged = false; + int rc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, + &fFormats, &fChanged); + if ( RT_SUCCESS(rc) + && fChanged + && ShClSvcIsBackendActive()) + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @callback_method_impl{FNRTTHREAD, The poller thread. + * + * This thread will check for the arrival of new data on the clipboard.} + */ +static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) +{ + SHCLCONTEXT *pCtx = (SHCLCONTEXT *)pvUser; + AssertPtr(pCtx); + LogFlowFuncEnter(); + + while (!pCtx->fTerminate) + { + /* call this behind the lock because we don't know if the api is + thread safe and in any case we're calling several methods. */ + ShClSvcLock(); + vboxClipboardChanged(pCtx); + ShClSvcUnlock(); + + /* Sleep for 200 msecs before next poll */ + RTThreadUserWait(ThreadSelf, 200); + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend, pTable); + g_ctx.fTerminate = false; + + int rc = initPasteboard(&g_ctx.hPasteboard); + AssertRCReturn(rc, rc); + + rc = RTThreadCreate(&g_ctx.hThread, vboxClipboardThread, &g_ctx, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); + if (RT_FAILURE(rc)) + { + g_ctx.hThread = NIL_RTTHREAD; + destroyPasteboard(&g_ctx.hPasteboard); + } + + return rc; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + + /* + * Signal the termination of the polling thread and wait for it to respond. + */ + ASMAtomicWriteBool(&g_ctx.fTerminate, true); + int rc = RTThreadUserSignal(g_ctx.hThread); + AssertRC(rc); + rc = RTThreadWait(g_ctx.hThread, RT_INDEFINITE_WAIT, NULL); + AssertRC(rc); + + /* + * Destroy the hPasteboard and uninitialize the global context record. + */ + destroyPasteboard(&g_ctx.hPasteboard); + g_ctx.hThread = NIL_RTTHREAD; + g_ctx.pClient = NULL; +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, fHeadless); + + if (g_ctx.pClient != NULL) + { + /* One client only. */ + return VERR_NOT_SUPPORTED; + } + + ShClSvcLock(); + + pClient->State.pCtx = &g_ctx; + pClient->State.pCtx->pClient = pClient; + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + /* Sync the host clipboard content with the client. */ + ShClSvcLock(); + + int rc = vboxClipboardChanged(pClient->State.pCtx); + + ShClSvcUnlock(); + + return rc; +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + ShClSvcLock(); + + pClient->State.pCtx->pClient = NULL; + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + LogFlowFunc(("fFormats=%02X\n", fFormats)); + + /** @todo r=bird: BUGBUG: The following is probably a mistake. */ + /** @todo r=andy: BUGBUG: Has been there since forever; needs investigation first before removing. */ + if (fFormats == VBOX_SHCL_FMT_NONE) + { + /* This is just an automatism, not a genuine announcement */ + return VINF_SUCCESS; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormats & VBOX_SHCL_FMT_URI_LIST) /* No transfer support yet. */ + return VINF_SUCCESS; +#endif + + SHCLCONTEXT *pCtx = pClient->State.pCtx; + ShClSvcLock(); + + /* + * Generate a unique flavor string for this format announcement. + */ + uint64_t idFlavor = RTRandU64(); + pCtx->idGuestOwnership = idFlavor; + RTStrPrintf(pCtx->szGuestOwnershipFlavor, sizeof(pCtx->szGuestOwnershipFlavor), + "org.virtualbox.sharedclipboard.%RTproc.%RX64", RTProcSelf(), idFlavor); + + /* + * Empty the pasteboard and put our ownership indicator flavor there + * with the stringified formats as value. + */ + char szValue[32]; + RTStrPrintf(szValue, sizeof(szValue), "%#x", fFormats); + + takePasteboardOwnership(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->szGuestOwnershipFlavor, szValue, + &pCtx->hStrOwnershipFlavor); + + ShClSvcUnlock(); + + /* + * Now, request the data from the guest. + */ + return ShClSvcGuestDataRequest(pClient, fFormats, NULL /* pidEvent */); +} + +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pBackend, pCmdCtx); + + ShClSvcLock(); + + /* Default to no data available. */ + *pcbActual = 0; + + int rc = readFromPasteboard(pClient->State.pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data from macOS, rc=%Rrc\n", rc)); + + ShClSvcUnlock(); + + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pCmdCtx); + + LogFlowFuncEnter(); + + ShClSvcLock(); + + writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); + + ShClSvcUnlock(); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClBackendTransferReadDir(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pBackend, pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteDir(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pBackend, pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferReadFileHdr(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pBackend, pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteFileHdr(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pBackend, pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferReadFileData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(pBackend, pClient, pFileData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteFileData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(pBackend, pClient, pFileData); + return VERR_NOT_IMPLEMENTED; +} + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h new file mode 100644 index 00000000..add48a0c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -0,0 +1,531 @@ +/* $Id: VBoxSharedClipboardSvc-internal.h $ */ +/** @file + * Shared Clipboard Service - Internal header. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h +#define VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <algorithm> +#include <list> +#include <map> + +#include <iprt/cpp/list.h> /* For RTCList. */ +#include <iprt/list.h> +#include <iprt/semaphore.h> + +#include <VBox/hgcmsvc.h> +#include <VBox/log.h> + +#include <VBox/HostServices/Service.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-transfers.h> + +using namespace HGCM; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +struct SHCLCLIENTSTATE; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** + * A queued message for the guest. + */ +typedef struct _SHCLCLIENTMSG +{ + /** The queue list entry. */ + RTLISTNODE ListEntry; + /** Stored message ID (VBOX_SHCL_HOST_MSG_XXX). */ + uint32_t idMsg; + /** Context ID. */ + uint64_t idCtx; + /** Number of stored parameters in aParms. */ + uint32_t cParms; + /** HGCM parameters. */ + RT_FLEXIBLE_ARRAY_EXTENSION + VBOXHGCMSVCPARM aParms[RT_FLEXIBLE_ARRAY]; +} SHCLCLIENTMSG; +/** Pointer to a queue message for the guest. */ +typedef SHCLCLIENTMSG *PSHCLCLIENTMSG; + +typedef struct SHCLCLIENTTRANSFERSTATE +{ + /** Directory of the transfer to start. */ + SHCLTRANSFERDIR enmTransferDir; +} SHCLCLIENTTRANSFERSTATE, *PSHCLCLIENTTRANSFERSTATE; + +/** + * Structure for holding a single POD (plain old data) transfer. + * + * This mostly is plain text, but also can be stuff like bitmap (BMP) or other binary data. + */ +typedef struct SHCLCLIENTPODSTATE +{ + /** POD transfer direction. */ + SHCLTRANSFERDIR enmDir; + /** Format of the data to be read / written. */ + SHCLFORMAT uFormat; + /** How much data (in bytes) to read/write for the current operation. */ + uint64_t cbToReadWriteTotal; + /** How much data (in bytes) already has been read/written for the current operation. */ + uint64_t cbReadWritten; + /** Timestamp (in ms) of Last read/write operation. */ + uint64_t tsLastReadWrittenMs; +} SHCLCLIENTPODSTATE, *PSHCLCLIENTPODSTATE; + +/** @name SHCLCLIENTSTATE_FLAGS_XXX + * @note Part of saved state! + * @{ */ +/** No Shared Clipboard client flags defined. */ +#define SHCLCLIENTSTATE_FLAGS_NONE 0 +/** Client has a guest read operation active. Currently unused. */ +#define SHCLCLIENTSTATE_FLAGS_READ_ACTIVE RT_BIT(0) +/** Client has a guest write operation active. Currently unused. */ +#define SHCLCLIENTSTATE_FLAGS_WRITE_ACTIVE RT_BIT(1) +/** @} */ + +/** + * Structure needed to support backwards compatbility for old(er) Guest Additions (< 6.1), + * which did not know the context ID concept then. + */ +typedef struct SHCLCLIENTLEGACYCID +{ + /** List node. */ + RTLISTNODE Node; + /** The actual context ID. */ + uint64_t uCID; + /** Not used yet; useful to have it in the saved state though. */ + uint32_t enmType; + /** @todo Add an union here as soon as we utilize \a enmType. */ + SHCLFORMAT uFormat; +} SHCLCLIENTLEGACYCID; +/** Pointer to a SHCLCLIENTLEGACYCID struct. */ +typedef SHCLCLIENTLEGACYCID *PSHCLCLIENTLEGACYCID; + +/** + * Structure for keeping legacy state, required for keeping backwards compatibility + * to old(er) Guest Additions. + */ +typedef struct SHCLCLIENTLEGACYSTATE +{ + /** List of context IDs (of type SHCLCLIENTLEGACYCID) for older Guest Additions which (< 6.1) + * which did not know the concept of context IDs. */ + RTLISTANCHOR lstCID; + /** Number of context IDs currently in \a lstCID. */ + uint16_t cCID; +} SHCLCLIENTLEGACYSTATE; + +/** + * Structure for keeping generic client state data within the Shared Clipboard host service. + * This structure needs to be serializable by SSM (must be a POD type). + */ +typedef struct SHCLCLIENTSTATE +{ + struct SHCLCLIENTSTATE *pNext; + struct SHCLCLIENTSTATE *pPrev; + + /** Backend-dependent opaque context structure. + * This contains data only known to a certain backend implementation. + * Optional and can be NULL. */ + SHCLCONTEXT *pCtx; + /** The client's HGCM ID. Not related to the session ID below! */ + uint32_t uClientID; + /** The client's session ID. */ + SHCLSESSIONID uSessionID; + /** Guest feature flags, VBOX_SHCL_GF_0_XXX. */ + uint64_t fGuestFeatures0; + /** Guest feature flags, VBOX_SHCL_GF_1_XXX. */ + uint64_t fGuestFeatures1; + /** Chunk size to use for data transfers. */ + uint32_t cbChunkSize; + /** Where the transfer sources its data from. */ + SHCLSOURCE enmSource; + /** Client state flags of type SHCLCLIENTSTATE_FLAGS_. */ + uint32_t fFlags; + /** POD (plain old data) state. */ + SHCLCLIENTPODSTATE POD; + /** The client's transfers state. */ + SHCLCLIENTTRANSFERSTATE Transfers; +} SHCLCLIENTSTATE, *PSHCLCLIENTSTATE; + +typedef struct _SHCLCLIENTCMDCTX +{ + uint64_t uContextID; +} SHCLCLIENTCMDCTX, *PSHCLCLIENTCMDCTX; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Structure for keeping transfer-related data per HGCM client. + */ +typedef struct _SHCLIENTTRANSFERS +{ + /** Transfer context. */ + SHCLTRANSFERCTX Ctx; +} SHCLIENTTRANSFERS, *PSHCLIENTTRANSFERS; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** Prototypes for the Shared Clipboard backend. */ +struct SHCLBACKEND; +typedef SHCLBACKEND *PSHCLBACKEND; + +/** + * Structure for keeping data per (connected) HGCM client. + */ +typedef struct _SHCLCLIENT +{ + /** Pointer to associated backend, if any. + * Might be NULL if not being used. */ + PSHCLBACKEND pBackend; + /** General client state data. */ + SHCLCLIENTSTATE State; + /** The critical section protecting the queue, event source and whatnot. */ + RTCRITSECT CritSect; + /** The client's message queue (SHCLCLIENTMSG). */ + RTLISTANCHOR MsgQueue; + /** Number of allocated messages (updated atomically, not under critsect). */ + uint32_t volatile cMsgAllocated; + /** Legacy cruft we have to keep to support old(er) Guest Additions. */ + SHCLCLIENTLEGACYSTATE Legacy; + /** The client's own event source. + * Needed for events which are not bound to a specific transfer. */ + SHCLEVENTSOURCE EventSrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLIENTTRANSFERS Transfers; +#endif + /** Structure for keeping the client's pending (deferred return) state. + * A client is in a deferred state when it asks for the next HGCM message, + * but the service can't provide it yet. That way a client will block (on the guest side, does not return) + * until the service can complete the call. */ + struct + { + /** The client's HGCM call handle. Needed for completing a deferred call. */ + VBOXHGCMCALLHANDLE hHandle; + /** Message type (function number) to use when completing the deferred call. + * A non-0 value means the client is in pending mode. */ + uint32_t uType; + /** Parameter count to use when completing the deferred call. */ + uint32_t cParms; + /** Parameters to use when completing the deferred call. */ + PVBOXHGCMSVCPARM paParms; + } Pending; +} SHCLCLIENT, *PSHCLCLIENT; + +/** + * Structure for keeping a single event source map entry. + * Currently empty. + */ +typedef struct _SHCLEVENTSOURCEMAPENTRY +{ +} SHCLEVENTSOURCEMAPENTRY; + +/** Map holding information about connected HGCM clients. Key is the (unique) HGCM client ID. + * The value is a weak pointer to PSHCLCLIENT, which is owned by HGCM. */ +typedef std::map<uint32_t, PSHCLCLIENT> ClipboardClientMap; + +/** Map holding information about event sources. Key is the (unique) event source ID. */ +typedef std::map<SHCLEVENTSOURCEID, SHCLEVENTSOURCEMAPENTRY> ClipboardEventSourceMap; + +/** Simple queue (list) which holds deferred (waiting) clients. */ +typedef std::list<uint32_t> ClipboardClientQueue; + +/** + * Structure for keeping the Shared Clipboard service extension state. + * + * A service extension is optional, and can be installed by a host component + * to communicate with the Shared Clipboard host service. + */ +typedef struct _SHCLEXTSTATE +{ + /** Pointer to the actual service extension handle. */ + PFNHGCMSVCEXT pfnExtension; + /** Opaque pointer to extension-provided data. Don't touch. */ + void *pvExtension; + /** The HGCM client ID currently assigned to this service extension. + * At the moment only one HGCM client can be assigned per extension. */ + uint32_t uClientID; + /** Whether the host service is reading clipboard data currently. */ + bool fReadingData; + /** Whether the service extension has sent the clipboard formats while + * the the host service is reading clipboard data from it. */ + bool fDelayedAnnouncement; + /** The actual clipboard formats announced while the host service + * is reading clipboard data from the extension. */ + uint32_t fDelayedFormats; +} SHCLEXTSTATE, *PSHCLEXTSTATE; + +extern SHCLEXTSTATE g_ExtState; + +int shClSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource); + +void shClSvcMsgQueueReset(PSHCLCLIENT pClient); +PSHCLCLIENTMSG shClSvcMsgAlloc(PSHCLCLIENT pClient, uint32_t uMsg, uint32_t cParms); +void shClSvcMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg); +void shClSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend); +int shClSvcMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg); + +int shClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID); +void shClSvcClientDestroy(PSHCLCLIENT pClient); +void shClSvcClientLock(PSHCLCLIENT pClient); +void shClSvcClientUnlock(PSHCLCLIENT pClient); +void shClSvcClientReset(PSHCLCLIENT pClient); + +int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID); +int shClSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState); +void shclSvcClientStateReset(PSHCLCLIENTSTATE pClientState); + +int shClSvcClientWakeup(PSHCLCLIENT pClient); + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int shClSvcTransferModeSet(uint32_t fMode); +int shClSvcTransferStart(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFER *ppTransfer); +int shClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg); +void shClSvcClientTransfersReset(PSHCLCLIENT pClient); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** @name Service functions, accessible by the backends. + * Locking is between the (host) service thread and the platform-dependent (window) thread. + * @{ + */ +int ShClSvcGuestDataRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); +int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +PSHCLBACKEND ShClSvcGetBackend(void); +uint32_t ShClSvcGetMode(void); +bool ShClSvcGetHeadless(void); +bool ShClSvcLock(void); +void ShClSvcUnlock(void); + +/** + * Checks if the backend is active (@c true), or if VRDE is in control of + * the host side. + */ +DECLINLINE(bool) ShClSvcIsBackendActive(void) +{ + return g_ExtState.pfnExtension == NULL; +} +/** @} */ + +/** @name Platform-dependent implementations for the Shared Clipboard host service ("backends"), + * called *only* by the host service. + * @{ + */ +/** + * Structure for keeping Shared Clipboard backend instance data. + */ +typedef struct SHCLBACKEND +{ + /** Callback table to use. + * Some callbacks might be optional and therefore NULL -- see the table for more details. */ + SHCLCALLBACKS Callbacks; +} SHCLBACKEND; +/** Pointer to a Shared Clipboard backend. */ +typedef SHCLBACKEND *PSHCLBACKEND; + +/** + * Called on initialization. + * + * @param pBackend Shared Clipboard backend to initialize. + * @param pTable The HGCM service call and parameter table. Mainly for + * adjusting the limits. + */ +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable); + +/** + * Called on destruction. + * + * @param pBackend Shared Clipboard backend to destroy. + */ +void ShClBackendDestroy(PSHCLBACKEND pBackend); + +/** + * Called when a new HGCM client connects. + * + * @param pBackend Shared Clipboard backend to set callbacks for. + * @param pCallbacks Backend callbacks to use. + * When NULL is specified, the backend's default callbacks are being used. + */ +void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks); + +/** + * Called when a new HGCM client connects. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to connect to. + * @param pClient Shared Clipboard client context. + * @param fHeadless Whether this is a headless connection or not. + */ +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless); + +/** + * Called when a HGCM client disconnects. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to disconnect from. + * @param pClient Shared Clipboard client context. + */ +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); + +/** + * Called when the guest reports available clipboard formats to the host OS. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to announce formats to. + * @param pClient Shared Clipboard client context. + * @param fFormats The announced formats from the guest, + * VBOX_SHCL_FMT_XXX. + */ +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); + +/** + * Called when the guest wants to read host clipboard data. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to read data from. + * @param pClient Shared Clipboard client context. + * @param pCmdCtx Shared Clipboard command context. + * @param uFormat Clipboard format to read. + * @param pvData Where to return the read clipboard data. + * @param cbData Size (in bytes) of buffer where to return the clipboard data. + * @param pcbActual Where to return the amount of bytes read. + * + * @todo Document: Can return VINF_HGCM_ASYNC_EXECUTE to defer returning read + * data + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual); + +/** + * Called when the guest writes clipboard data to the host. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to write data to. + * @param pClient Shared Clipboard client context. + * @param pCmdCtx Shared Clipboard command context. + * @param uFormat Clipboard format to write. + * @param pvData Clipboard data to write. + * @param cbData Size (in bytes) of buffer clipboard data to write. + */ +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + +/** + * Called when synchronization of the clipboard contents of the host clipboard with the guest is needed. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to synchronize. + * @param pClient Shared Clipboard client context. + */ +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); +/** @} */ + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Host implementations for Shared Clipboard transfers. + * @{ + */ +/** + * Called after a transfer got created. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer created. + */ +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called before a transfer gets destroyed. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to destroy. + */ +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called when getting (determining) the transfer roots on the host side. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to get roots for. + */ +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** @} */ +#endif + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Internal Shared Clipboard transfer host service functions. + * @{ + */ +int shClSvcTransferHandler(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE callHandle, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival); +int shClSvcTransferHostHandler(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); +/** @} */ + +/** @name Shared Clipboard transfer interface implementations for the host service. + * @{ + */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + +int shClSvcTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList); + +int shClSvcTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList); +int shClSvcTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList); +int shClSvcTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListHdrWrite(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); +int shClSvcTransferIfaceListEntryWrite(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); + +int shClSvcTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj); +int shClSvcTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj); +int shClSvcTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead); +int shClSvcTransferIfaceObjWrite(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbWritten); +/** @} */ + +/** @name Shared Clipboard transfer callbacks for the host service. + * @{ + */ +DECLCALLBACK(void) VBoxSvcClipboardTransferPrepareCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardDataHeaderCompleteCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardDataCompleteCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardTransferCompleteCallback(PSHCLTXPROVIDERCTX pCtx, int rc); +DECLCALLBACK(void) VBoxSvcClipboardTransferCanceledCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardTransferErrorCallback(PSHCLTXPROVIDERCTX pCtx, int rc); +/** @} */ +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/* Host unit testing interface */ +#ifdef UNIT_TEST +uint32_t TestClipSvcGetMode(void); +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp new file mode 100644 index 00000000..3f36f170 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -0,0 +1,2067 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp $ */ +/** @file + * Shared Clipboard Service - Internal code for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> + +#include <VBox/err.h> + +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <VBox/AssertGuest.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#include "VBoxSharedClipboardSvc-transfers.h" + + +/********************************************************************************************************************************* +* Externals * +*********************************************************************************************************************************/ +extern uint32_t g_fTransferMode; +extern SHCLEXTSTATE g_ExtState; +extern PVBOXHGCMSVCHELPERS g_pHelpers; +extern ClipboardClientMap g_mapClients; +extern ClipboardClientQueue g_listClientsDeferred; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static int shClSvcTransferSetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, PSHCLLISTOPENPARMS pOpenParms); +static int shClSvcTransferSetListClose(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, SHCLLISTHANDLE hList); + + +/********************************************************************************************************************************* +* Provider implementation * +*********************************************************************************************************************************/ + +/** + * Resets all transfers of a Shared Clipboard client. + * + * @param pClient Client to reset transfers for. + */ +void shClSvcClientTransfersReset(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + + LogFlowFuncEnter(); + + /* Make sure to let the backend know that all transfers are getting destroyed. */ + uint32_t uIdx = 0; + PSHCLTRANSFER pTransfer; + while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, uIdx++))) + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + + ShClTransferCtxDestroy(&pClient->Transfers.Ctx); +} + + +/********************************************************************************************************************************* +* Provider interface implementation * +*********************************************************************************************************************************/ + +DECLCALLBACK(int) shClSvcTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsgHdr = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ, + VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ_REQ); + if (pMsgHdr) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgHdr->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU32(&pMsgHdr->aParms[1], 0 /* fRoots */); + + shClSvcClientLock(pClient); + + shClSvcMsgAdd(pClient, pMsgHdr, true /* fAppend */); + rc = shClSvcClientWakeup(pClient); + + shClSvcClientUnlock(pClient); + + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayloadHdr; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayloadHdr); + if (RT_SUCCESS(rc)) + { + PSHCLROOTLISTHDR pSrcRootListHdr = (PSHCLROOTLISTHDR)pPayloadHdr->pvData; + Assert(pPayloadHdr->cbData == sizeof(SHCLROOTLISTHDR)); + + LogFlowFunc(("cRoots=%RU32, fRoots=0x%x\n", pSrcRootListHdr->cRoots, pSrcRootListHdr->fRoots)); + + PSHCLROOTLIST pRootList = ShClTransferRootListAlloc(); + if (pRootList) + { + if (pSrcRootListHdr->cRoots) + { + pRootList->paEntries = + (PSHCLROOTLISTENTRY)RTMemAllocZ(pSrcRootListHdr->cRoots * sizeof(SHCLROOTLISTENTRY)); + + if (pRootList->paEntries) + { + for (uint32_t i = 0; i < pSrcRootListHdr->cRoots; i++) + { + PSHCLCLIENTMSG pMsgEntry = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ, + VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ_REQ); + + PSHCLEVENT pEvRoot; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvRoot); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgEntry->aParms[0], + VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uClientID, + pCtx->pTransfer->State.uID, pEvRoot->idEvent)); + HGCMSvcSetU32(&pMsgEntry->aParms[1], 0 /* fRoots */); + HGCMSvcSetU32(&pMsgEntry->aParms[2], i /* uIndex */); + + shClSvcClientLock(pClient); + shClSvcMsgAdd(pClient, pMsgEntry, true /* fAppend */); + shClSvcClientUnlock(pClient); + + PSHCLEVENTPAYLOAD pPayloadEntry; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayloadEntry); + if (RT_FAILURE(rc)) + break; + + PSHCLROOTLISTENTRY pSrcRootListEntry = (PSHCLROOTLISTENTRY)pPayloadEntry->pvData; + Assert(pPayloadEntry->cbData == sizeof(SHCLROOTLISTENTRY)); + + rc = ShClTransferListEntryCopy(&pRootList->paEntries[i], pSrcRootListEntry); + + ShClPayloadFree(pPayloadEntry); + + ShClEventRelease(pEvRoot); + pEvRoot = NULL; + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + if (RT_FAILURE(rc)) + break; + } + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + pRootList->Hdr.cRoots = pSrcRootListHdr->cRoots; + pRootList->Hdr.fRoots = 0; /** @todo Implement this. */ + + *ppRootList = pRootList; + } + else + ShClTransferRootListFree(pRootList); + + ShClPayloadFree(pPayloadHdr); + } + else + rc = VERR_NO_MEMORY; + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeave(); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, + PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN, + VBOX_SHCL_CPARMS_LIST_OPEN); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + pEvent->idEvent); + + rc = shClSvcTransferSetListOpen(pMsg->cParms, pMsg->aParms, pMsg->idCtx, pOpenParms); + if (RT_SUCCESS(rc)) + { + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN); + + LogFlowFunc(("hList=%RU64\n", pReply->u.ListOpen.uHandle)); + + *phList = pReply->u.ListOpen.uHandle; + + ShClPayloadFree(pPayload); + } + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE, + VBOX_SHCL_CPARMS_LIST_CLOSE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + pEvent->idEvent); + + rc = shClSvcTransferSetListClose(pMsg->cParms, pMsg->aParms, pMsg->idCtx, hList); + if (RT_SUCCESS(rc)) + { + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ, + VBOX_SHCL_CPARMS_LIST_HDR_READ_REQ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hList); + HGCMSvcSetU32(&pMsg->aParms[2], 0 /* fFlags */); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTHDR)); + + *pListHdr = *(PSHCLLISTHDR)pPayload->pvData; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrWrite(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + RT_NOREF(pCtx, hList, pListHdr); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ, + VBOX_SHCL_CPARMS_LIST_ENTRY_READ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hList); + HGCMSvcSetU32(&pMsg->aParms[2], 0 /* fInfo */); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTENTRY)); + + rc = ShClTransferListEntryCopy(pListEntry, (PSHCLLISTENTRY)pPayload->pvData); + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryWrite(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry) +{ + RT_NOREF(pCtx, hList, pListEntry); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +int shClSvcTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN, + VBOX_SHCL_CPARMS_OBJ_OPEN); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("pszPath=%s, fCreate=0x%x\n", pCreateParms->pszPath, pCreateParms->fCreate)); + + const uint32_t cbPath = (uint32_t)strlen(pCreateParms->pszPath) + 1; /* Include terminating zero */ + + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], 0); /* uHandle */ + HGCMSvcSetU32(&pMsg->aParms[2], cbPath); + HGCMSvcSetPv (&pMsg->aParms[3], pCreateParms->pszPath, cbPath); + HGCMSvcSetU32(&pMsg->aParms[4], pCreateParms->fCreate); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN); + + LogFlowFunc(("hObj=%RU64\n", pReply->u.ObjOpen.uHandle)); + + *phObj = pReply->u.ObjOpen.uHandle; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE, + VBOX_SHCL_CPARMS_OBJ_CLOSE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); +#ifdef VBOX_STRICT + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE); + + LogFlowFunc(("hObj=%RU64\n", pReply->u.ObjClose.uHandle)); +#endif + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ, + VBOX_SHCL_CPARMS_OBJ_READ_REQ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + HGCMSvcSetU32(&pMsg->aParms[2], cbData); + HGCMSvcSetU32(&pMsg->aParms[3], fFlags); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLOBJDATACHUNK)); + + PSHCLOBJDATACHUNK pDataChunk = (PSHCLOBJDATACHUNK)pPayload->pvData; + AssertPtr(pDataChunk); + + const uint32_t cbRead = RT_MIN(cbData, pDataChunk->cbData); + + memcpy(pvData, pDataChunk->pvData, cbRead); + + if (pcbRead) + *pcbRead = cbRead; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjWrite(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbWritten) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE, + VBOX_SHCL_CPARMS_OBJ_WRITE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + HGCMSvcSetU64(&pMsg->aParms[2], cbData); + HGCMSvcSetU64(&pMsg->aParms[3], fFlags); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + const uint32_t cbRead = RT_MIN(cbData, pPayload->cbData); + + memcpy(pvData, pPayload->pvData, cbRead); + + if (pcbWritten) + *pcbWritten = cbRead; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/********************************************************************************************************************************* +* HGCM getters / setters * +*********************************************************************************************************************************/ + +/** + * Returns whether a HGCM message is allowed in a certain service mode or not. + * + * @returns \c true if message is allowed, \c false if not. + * @param uMode Service mode to check allowance for. + * @param uMsg HGCM message to check allowance for. + */ +bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg) +{ + const bool fHostToGuest = uMode == VBOX_SHCL_MODE_HOST_TO_GUEST + || uMode == VBOX_SHCL_MODE_BIDIRECTIONAL; + + const bool fGuestToHost = uMode == VBOX_SHCL_MODE_GUEST_TO_HOST + || uMode == VBOX_SHCL_MODE_BIDIRECTIONAL; + + bool fAllowed = false; /* If in doubt, don't allow. */ + + switch (uMsg) + { + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_WRITE: + fAllowed = fGuestToHost; + break; + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_HDR_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_READ: + fAllowed = fHostToGuest; + break; + + case VBOX_SHCL_GUEST_FN_CONNECT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_REPORT_FEATURES: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_QUERY_FEATURES: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_GET: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_REPLY: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_CANCEL: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ERROR: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_CLOSE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_CLOSE: + fAllowed = fHostToGuest || fGuestToHost; + break; + + default: + break; + } + + LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClGuestMsgToStr(uMsg), uMode, fAllowed)); + return fAllowed; +} + +/** + * Gets a transfer message reply from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pReply Where to store the reply. + */ +static int shClSvcTransferGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLREPLY pReply) +{ + int rc; + + if (cParms >= VBOX_SHCL_CPARMS_REPLY_MIN) + { + /* aParms[0] has the context ID. */ + rc = HGCMSvcGetU32(&aParms[1], &pReply->uType); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pReply->rc); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], &pReply->pvPayload, &pReply->cbPayload); + + if (RT_SUCCESS(rc)) + { + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + const unsigned idxParm = VBOX_SHCL_CPARMS_REPLY_MIN; + + switch (pReply->uType) + { + case VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS: + { + if (cParms > idxParm) + rc = HGCMSvcGetU32(&aParms[idxParm], &pReply->u.TransferStatus.uStatus); + + LogFlowFunc(("uTransferStatus=%RU32\n", pReply->u.TransferStatus.uStatus)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListOpen.uHandle); + + LogFlowFunc(("hListOpen=%RU64\n", pReply->u.ListOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListClose.uHandle); + + LogFlowFunc(("hListClose=%RU64\n", pReply->u.ListClose.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjOpen.uHandle); + + LogFlowFunc(("hObjOpen=%RU64\n", pReply->u.ObjOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjClose.uHandle); + + LogFlowFunc(("hObjClose=%RU64\n", pReply->u.ObjClose.uHandle)); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer root list header from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pRootLstHdr Where to store the transfer root list header on success. + */ +static int shClSvcTransferGetRootListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLROOTLISTHDR pRootLstHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ROOT_LIST_HDR_WRITE) + { + rc = HGCMSvcGetU32(&aParms[1], &pRootLstHdr->fRoots); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pRootLstHdr->cRoots); + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer root list entry from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListEntry Where to store the root list entry. + */ +static int shClSvcTransferGetRootListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLROOTLISTENTRY pListEntry) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_WRITE) + { + rc = HGCMSvcGetU32(&aParms[1], &pListEntry->fInfo); + /* Note: aParms[2] contains the entry index, currently being ignored. */ + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], (void **)&pListEntry->pszName, &pListEntry->cbName); + if (RT_SUCCESS(rc)) + { + uint32_t cbInfo; + rc = HGCMSvcGetU32(&aParms[4], &cbInfo); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[5], &pListEntry->pvInfo, &pListEntry->cbInfo); + AssertReturn(cbInfo == pListEntry->cbInfo, VERR_INVALID_PARAMETER); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list open request from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pOpenParms Where to store the open parameters of the request. + */ +static int shClSvcTransferGetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTOPENPARMS pOpenParms) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_OPEN) + { + rc = HGCMSvcGetU32(&aParms[1], &pOpenParms->fList); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetStr(&aParms[2], &pOpenParms->pszFilter, &pOpenParms->cbFilter); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetStr(&aParms[3], &pOpenParms->pszPath, &pOpenParms->cbPath); + + /** @todo Some more validation. */ + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list open request to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param idCtx Context ID to use. + * @param pOpenParms List open parameters to set. + */ +static int shClSvcTransferSetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, PSHCLLISTOPENPARMS pOpenParms) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_OPEN) + { + HGCMSvcSetU64(&aParms[0], idCtx); + HGCMSvcSetU32(&aParms[1], pOpenParms->fList); + HGCMSvcSetPv (&aParms[2], pOpenParms->pszFilter, pOpenParms->cbFilter); + HGCMSvcSetPv (&aParms[3], pOpenParms->pszPath, pOpenParms->cbPath); + HGCMSvcSetU64(&aParms[4], 0); /* OUT: uHandle */ + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list close request to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param idCtx Context ID to use. + * @param hList Handle of list to close. + */ +static int shClSvcTransferSetListClose(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, SHCLLISTHANDLE hList) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_CLOSE) + { + HGCMSvcSetU64(&aParms[0], idCtx); + HGCMSvcSetU64(&aParms[1], hList); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list header from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param phList Where to store the list handle. + * @param pListHdr Where to store the list header. + */ +static int shClSvcTransferGetListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTHANDLE phList, PSHCLLISTHDR pListHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_HDR) + { + rc = HGCMSvcGetU64(&aParms[1], phList); + /* Note: Flags (aParms[2]) not used here. */ + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[3], &pListHdr->fFeatures); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&aParms[4], &pListHdr->cTotalObjects); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&aParms[5], &pListHdr->cbTotalSize); + + if (RT_SUCCESS(rc)) + { + /** @todo Validate pvMetaFmt + cbMetaFmt. */ + /** @todo Validate header checksum. */ + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list header to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListHdr Pointer to list header to set. + */ +static int shClSvcTransferSetListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], PSHCLLISTHDR pListHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_HDR) + { + /** @todo Set pvMetaFmt + cbMetaFmt. */ + /** @todo Calculate header checksum. */ + + HGCMSvcSetU32(&aParms[3], pListHdr->fFeatures); + HGCMSvcSetU64(&aParms[4], pListHdr->cTotalObjects); + HGCMSvcSetU64(&aParms[5], pListHdr->cbTotalSize); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list entry from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param phList Where to store the list handle. + * @param pListEntry Where to store the list entry. + */ +static int shClSvcTransferGetListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTHANDLE phList, PSHCLLISTENTRY pListEntry) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_ENTRY) + { + rc = HGCMSvcGetU64(&aParms[1], phList); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pListEntry->fInfo); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], (void **)&pListEntry->pszName, &pListEntry->cbName); + if (RT_SUCCESS(rc)) + { + uint32_t cbInfo; + rc = HGCMSvcGetU32(&aParms[4], &cbInfo); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[5], &pListEntry->pvInfo, &pListEntry->cbInfo); + AssertReturn(cbInfo == pListEntry->cbInfo, VERR_INVALID_PARAMETER); + } + } + + if (RT_SUCCESS(rc)) + { + if (!ShClTransferListEntryIsValid(pListEntry)) + rc = VERR_INVALID_PARAMETER; + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a Shared Clipboard list entry to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListEntry Pointer list entry to set. + */ +static int shClSvcTransferSetListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTENTRY pListEntry) +{ + int rc; + + /* Sanity. */ + AssertReturn(ShClTransferListEntryIsValid(pListEntry), VERR_INVALID_PARAMETER); + + if (cParms == VBOX_SHCL_CPARMS_LIST_ENTRY) + { + HGCMSvcSetPv (&aParms[3], pListEntry->pszName, pListEntry->cbName); + HGCMSvcSetU32(&aParms[4], pListEntry->cbInfo); + HGCMSvcSetPv (&aParms[5], pListEntry->pvInfo, pListEntry->cbInfo); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer object data chunk from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pDataChunk Where to store the object data chunk data. + */ +static int shClSvcTransferGetObjDataChunk(uint32_t cParms, VBOXHGCMSVCPARM aParms[], PSHCLOBJDATACHUNK pDataChunk) +{ + AssertPtrReturn(aParms, VERR_INVALID_PARAMETER); + AssertPtrReturn(pDataChunk, VERR_INVALID_PARAMETER); + + int rc; + + if (cParms == VBOX_SHCL_CPARMS_OBJ_WRITE) + { + rc = HGCMSvcGetU64(&aParms[1], &pDataChunk->uHandle); + if (RT_SUCCESS(rc)) + { + uint32_t cbData; + rc = HGCMSvcGetU32(&aParms[2], &cbData); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[3], &pDataChunk->pvData, &pDataChunk->cbData); + AssertReturn(cbData == pDataChunk->cbData, VERR_INVALID_PARAMETER); + + /** @todo Implement checksum handling. */ + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Handles a guest reply (VBOX_SHCL_GUEST_FN_REPLY) message. + * + * @returns VBox status code. + * @param pClient Pointer to associated client. + * @param pTransfer Pointer to transfer to handle guest reply for. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + */ +static int shClSvcTransferHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, + uint32_t cParms, VBOXHGCMSVCPARM aParms[]) +{ + RT_NOREF(pClient, pTransfer); + + int rc; + + uint32_t cbReply = sizeof(SHCLREPLY); + PSHCLREPLY pReply = (PSHCLREPLY)RTMemAlloc(cbReply); + if (pReply) + { + rc = shClSvcTransferGetReply(cParms, aParms, pReply); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload + = (PSHCLEVENTPAYLOAD)RTMemAlloc(sizeof(SHCLEVENTPAYLOAD)); + if (pPayload) + { + pPayload->pvData = pReply; + pPayload->cbData = cbReply; + + switch (pReply->uType) + { + case VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE: + { + uint64_t uCID; + rc = HGCMSvcGetU64(&aParms[0], &uCID); + if (RT_SUCCESS(rc)) + { + const PSHCLEVENT pEvent + = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + LogFlowFunc(("uCID=%RU64 -> idEvent=%RU32\n", uCID, pEvent->idEvent)); + + rc = ShClEventSignal(pEvent, pPayload); + } + /** @todo Silently skip? */ + } + break; + } + + default: + rc = VERR_NOT_FOUND; + break; + } + + if (RT_FAILURE(rc)) + { + if (pPayload) + RTMemFree(pPayload); + } + } + else + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + if (pReply) + RTMemFree(pReply); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Transfer client (guest) handler for the Shared Clipboard host service. + * + * @returns VBox status code, or VINF_HGCM_ASYNC_EXECUTE if returning to the client will be deferred. + * @param pClient Pointer to associated client. + * @param callHandle The client's call handle of this call. + * @param u32Function Function number being called. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + * @param tsArrival Timestamp of arrival. + */ +int shClSvcTransferHandler(PSHCLCLIENT pClient, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM aParms[], + uint64_t tsArrival) +{ + RT_NOREF(callHandle, aParms, tsArrival); + + LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, g_ExtState.pfnExtension=%p\n", + pClient->State.uClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); + + /* Check if we've the right mode set. */ + if (!shClSvcTransferMsgIsAllowed(ShClSvcGetMode(), u32Function)) + { + LogFunc(("Wrong clipboard mode, denying access\n")); + return VERR_ACCESS_DENIED; + } + + int rc = VERR_INVALID_PARAMETER; /* Play safe by default. */ + + /* + * Pre-check: For certain messages we need to make sure that a (right) transfer is present. + */ + uint64_t uCID = 0; /* Context ID */ + PSHCLTRANSFER pTransfer = NULL; + + switch (u32Function) + { + default: + { + if (!ShClTransferCtxGetTotalTransfers(&pClient->Transfers.Ctx)) + { + LogFunc(("No transfers found\n")); + rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + break; + } + + if (cParms < 1) + break; + + rc = HGCMSvcGetU64(&aParms[0], &uCID); + if (RT_FAILURE(rc)) + break; + + const SHCLTRANSFERID uTransferID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(uCID); + + pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, uTransferID); + if (!pTransfer) + { + LogFunc(("Transfer with ID %RU16 not found\n", uTransferID)); + rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + } + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + switch (u32Function) + { + case VBOX_SHCL_GUEST_FN_REPLY: + { + rc = shClSvcTransferHandleReply(pClient, pTransfer, cParms, aParms); + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ: + { + if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ) + break; + + if ( ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL + && ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE) + { + /* Get roots if this is a local write transfer (host -> guest). */ + rc = ShClBackendTransferGetRoots(pClient->pBackend, pClient, pTransfer); + } + else + { + rc = VERR_INVALID_PARAMETER; + break; + } + + SHCLROOTLISTHDR rootListHdr; + RT_ZERO(rootListHdr); + + rootListHdr.cRoots = ShClTransferRootsCount(pTransfer); + + HGCMSvcSetU64(&aParms[0], 0 /* Context ID */); + HGCMSvcSetU32(&aParms[1], rootListHdr.fRoots); + HGCMSvcSetU32(&aParms[2], rootListHdr.cRoots); + + rc = VINF_SUCCESS; + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE: + { + SHCLROOTLISTHDR lstHdr; + rc = shClSvcTransferGetRootListHdr(cParms, aParms, &lstHdr); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferRootListHdrDup(&lstHdr); + uint32_t cbData = sizeof(SHCLROOTLISTHDR); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ: + { + if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ) + break; + + /* aParms[1] contains fInfo flags, currently unused. */ + uint32_t uIndex; + rc = HGCMSvcGetU32(&aParms[2], &uIndex); + if (RT_SUCCESS(rc)) + { + SHCLROOTLISTENTRY rootListEntry; + rc = ShClTransferRootsEntry(pTransfer, uIndex, &rootListEntry); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetPv (&aParms[3], rootListEntry.pszName, rootListEntry.cbName); + HGCMSvcSetU32(&aParms[4], rootListEntry.cbInfo); + HGCMSvcSetPv (&aParms[5], rootListEntry.pvInfo, rootListEntry.cbInfo); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE: + { + SHCLROOTLISTENTRY lstEntry; + rc = shClSvcTransferGetRootListEntry(cParms, aParms, &lstEntry); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferRootListEntryDup(&lstEntry); + uint32_t cbData = sizeof(SHCLROOTLISTENTRY); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_OPEN: + { + SHCLLISTOPENPARMS listOpenParms; + rc = shClSvcTransferGetListOpen(cParms, aParms, &listOpenParms); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = ShClTransferListOpen(pTransfer, &listOpenParms, &hList); + if (RT_SUCCESS(rc)) + { + /* Return list handle. */ + HGCMSvcSetU64(&aParms[6], hList); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_CLOSE: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_CLOSE) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferListClose(pTransfer, hList); + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_HDR_READ: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_HDR) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); /* Get list handle. */ + if (RT_SUCCESS(rc)) + { + SHCLLISTHDR hdrList; + rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferSetListHdr(cParms, aParms, &hdrList); + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE: + { + SHCLLISTHDR hdrList; + rc = ShClTransferListHdrInit(&hdrList); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = shClSvcTransferGetListHdr(cParms, aParms, &hList, &hdrList); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferListHdrDup(&hdrList); + uint32_t cbData = sizeof(SHCLLISTHDR); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_ENTRY) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); /* Get list handle. */ + if (RT_SUCCESS(rc)) + { + SHCLLISTENTRY entryList; + rc = ShClTransferListEntryInit(&entryList); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferListRead(pTransfer, hList, &entryList); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferSetListEntry(cParms, aParms, &entryList); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE: + { + SHCLLISTENTRY entryList; + rc = ShClTransferListEntryInit(&entryList); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = shClSvcTransferGetListEntry(cParms, aParms, &hList, &entryList); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferListEntryDup(&entryList); + uint32_t cbData = sizeof(SHCLLISTENTRY); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_OPEN: + { + ASSERT_GUEST_STMT_BREAK(cParms == VBOX_SHCL_CPARMS_OBJ_OPEN, VERR_WRONG_PARAMETER_COUNT); + + SHCLOBJOPENCREATEPARMS openCreateParms; + RT_ZERO(openCreateParms); + + /* aParms[1] will return the object handle on success; see below. */ + rc = HGCMSvcGetStr(&aParms[2], &openCreateParms.pszPath, &openCreateParms.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[3], &openCreateParms.fCreate); + + if (RT_SUCCESS(rc)) + { + SHCLOBJHANDLE hObj; + rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("hObj=%RU64\n", hObj)); + + HGCMSvcSetU64(&aParms[1], hObj); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_CLOSE: + { + if (cParms != VBOX_SHCL_CPARMS_OBJ_CLOSE) + break; + + SHCLOBJHANDLE hObj; + rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ + if (RT_SUCCESS(rc)) + rc = ShClTransferObjClose(pTransfer, hObj); + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_READ: + { + if (cParms != VBOX_SHCL_CPARMS_OBJ_READ) + break; + + SHCLOBJHANDLE hObj; + rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ + + uint32_t cbToRead = 0; + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &cbToRead); + + void *pvBuf = NULL; + uint32_t cbBuf = 0; + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], &pvBuf, &cbBuf); + + LogFlowFunc(("hObj=%RU64, cbBuf=%RU32, cbToRead=%RU32, rc=%Rrc\n", hObj, cbBuf, cbToRead, rc)); + + if ( RT_SUCCESS(rc) + && ( !cbBuf + || !cbToRead + || cbBuf < cbToRead + ) + ) + { + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + uint32_t cbRead; + rc = ShClTransferObjRead(pTransfer, hObj, pvBuf, cbToRead, 0 /* fFlags */, &cbRead); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU32(&aParms[3], cbRead); + + /** @todo Implement checksum support. */ + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_WRITE: + { + SHCLOBJDATACHUNK dataChunk; + rc = shClSvcTransferGetObjDataChunk(cParms, aParms, &dataChunk); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferObjDataChunkDup(&dataChunk); + uint32_t cbData = sizeof(SHCLOBJDATACHUNK); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFunc(("[Client %RU32] Returning rc=%Rrc\n", pClient->State.uClientID, rc)); + return rc; +} + +/** + * Transfer host handler for the Shared Clipboard host service. + * + * @returns VBox status code. + * @param u32Function Function number being called. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + */ +int shClSvcTransferHostHandler(uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM aParms[]) +{ + RT_NOREF(cParms, aParms); + + int rc = VERR_NOT_IMPLEMENTED; /* Play safe. */ + + switch (u32Function) + { + case VBOX_SHCL_HOST_FN_CANCEL: /** @todo Implement this. */ + break; + + case VBOX_SHCL_HOST_FN_ERROR: /** @todo Implement this. */ + break; + + default: + break; + + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferHostMsgHandler(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + + int rc; + + switch (pMsg->idMsg) + { + default: + rc = VINF_SUCCESS; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports a transfer status to the guest. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param pTransfer Transfer to report status for. + * @param uStatus Status to report. + * @param rcTransfer Result code to report. Optional and depending on status. + * @param ppEvent Where to return the wait event on success. Optional. + * Must be released by the caller with ShClEventRelease(). + */ +int shClSvcTransferSendStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS uStatus, + int rcTransfer, PSHCLEVENT *ppEvent) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + /* ppEvent is optional. */ + + PSHCLCLIENTMSG pMsgReadData = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_STATUS, + VBOX_SHCL_CPARMS_TRANSFER_STATUS); + if (!pMsgReadData) + return VERR_NO_MEMORY; + + PSHCLEVENT pEvent; + int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgReadData->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU32(&pMsgReadData->aParms[1], pTransfer->State.enmDir); + HGCMSvcSetU32(&pMsgReadData->aParms[2], uStatus); + HGCMSvcSetU32(&pMsgReadData->aParms[3], (uint32_t)rcTransfer); /** @todo uint32_t vs. int. */ + HGCMSvcSetU32(&pMsgReadData->aParms[4], 0 /* fFlags, unused */); + + shClSvcMsgAdd(pClient, pMsgReadData, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU32 to guest\n", + ShClTransferStatusToStr(uStatus), rcTransfer, pTransfer->State.uID)); + + if (ppEvent) + { + *ppEvent = pEvent; /* Takes ownership. */ + } + else /* If event is not consumed by the caller, release the event again. */ + ShClEventRelease(pEvent); + } + else + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Starts a new transfer, waiting for acknowledgement by the guest side. + * + * @note Assumes that the client's critical section is taken. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param enmDir Transfer direction to start. + * @param enmSource Transfer source to start. + * @param ppTransfer Where to return the created transfer on success. Optional. + */ +int shClSvcTransferStart(PSHCLCLIENT pClient, + SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFER *ppTransfer) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + /* ppTransfer is optional. */ + + LogFlowFuncEnter(); + + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + ShClTransferCtxCleanup(pTxCtx); + + int rc; + + if (!ShClTransferCtxTransfersMaximumReached(pTxCtx)) + { + LogRel2(("Shared Clipboard: Starting %s transfer ...\n", enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "read" : "write")); + + PSHCLTRANSFER pTransfer; + rc = ShClTransferCreate(&pTransfer); + if (RT_SUCCESS(rc)) + { + SHCLTXPROVIDERCREATIONCTX creationCtx; + RT_ZERO(creationCtx); + + if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) + { + creationCtx.Interface.pfnRootsGet = shClSvcTransferIfaceGetRoots; + + creationCtx.Interface.pfnListOpen = shClSvcTransferIfaceListOpen; + creationCtx.Interface.pfnListClose = shClSvcTransferIfaceListClose; + creationCtx.Interface.pfnListHdrRead = shClSvcTransferIfaceListHdrRead; + creationCtx.Interface.pfnListEntryRead = shClSvcTransferIfaceListEntryRead; + + creationCtx.Interface.pfnObjOpen = shClSvcTransferIfaceObjOpen; + creationCtx.Interface.pfnObjClose = shClSvcTransferIfaceObjClose; + creationCtx.Interface.pfnObjRead = shClSvcTransferIfaceObjRead; + } + else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) + { + creationCtx.Interface.pfnListHdrWrite = shClSvcTransferIfaceListHdrWrite; + creationCtx.Interface.pfnListEntryWrite = shClSvcTransferIfaceListEntryWrite; + creationCtx.Interface.pfnObjWrite = shClSvcTransferIfaceObjWrite; + } + else + AssertFailed(); + + creationCtx.enmSource = pClient->State.enmSource; + creationCtx.pvUser = pClient; + + rc = ShClTransferSetProviderIface(pTransfer, &creationCtx); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferInit(pTransfer, enmDir, enmSource); + if (RT_SUCCESS(rc)) + { + SHCLTRANSFERID uTransferID = 0; + rc = ShClTransferCtxTransferRegister(pTxCtx, pTransfer, &uTransferID); + if (RT_SUCCESS(rc)) + { + rc = ShClBackendTransferCreate(pClient->pBackend, pClient, pTransfer); + if (RT_SUCCESS(rc)) + { + if (RT_SUCCESS(rc)) + rc = ShClTransferStart(pTransfer); + + if (RT_SUCCESS(rc)) + { + PSHCLEVENT pEvent; + rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS, &pEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for start of transfer %RU32 on guest ...\n", + pTransfer->State.uID)); + + /* Leave the client's critical section before waiting. */ + RTCritSectLeave(&pClient->CritSect); + + PSHCLEVENTPAYLOAD pPayload = NULL; + rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS); + + if (pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_STARTED) + { + LogRel2(("Shared Clipboard: Started transfer %RU32 on guest\n", pTransfer->State.uID)); + } + else + LogRel(("Shared Clipboard: Guest reported status %s (error %Rrc) while starting transfer %RU32\n", + ShClTransferStatusToStr(pReply->u.TransferStatus.uStatus), + pReply->rc, pTransfer->State.uID)); + + rc = pReply->rc; /* Set guest rc. */ + } + else + LogRel(("Shared Clipboard: Unable to start transfer %RU32 on guest, rc=%Rrc\n", + pTransfer->State.uID, rc)); + + ShClPayloadFree(pPayload); + ShClEventRelease(pEvent); + + /* Re-enter the client's critical section again. */ + RTCritSectEnter(&pClient->CritSect); + } + } + } + } + + if (RT_FAILURE(rc)) + ShClTransferCtxTransferUnregister(pTxCtx, uTransferID); + } + } + + if (RT_FAILURE(rc)) + { + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + ShClTransferDestroy(pTransfer); + + RTMemFree(pTransfer); + pTransfer = NULL; + } + else + { + if (ppTransfer) + *ppTransfer = pTransfer; + } + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Starting transfer failed with %Rrc\n", rc)); + } + else + rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Stops (and destroys) a transfer, communicating the status to the guest side. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param pTransfer Transfer to stop. + */ +int shClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + PSHCLEVENT pEvent; + int rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_STOPPED, VINF_SUCCESS, &pEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for stop of transfer %RU32 on guest ...\n", pTransfer->State.uID)); + + rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, NULL /* ppPayload */); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Stopped transfer %RU32 on guest\n", pTransfer->State.uID)); + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Unable to stop transfer %RU32 on guest, rc=%Rrc\n", + pTransfer->State.uID, rc)); + + /* Regardless of whether the guest was able to report back and/or stop the transfer, remove the transfer on the host + * so that we don't risk of having stale transfers here. */ + int rc2 = ShClTransferCtxTransferUnregister(&pClient->Transfers.Ctx, ShClTransferGetID(pTransfer)); + if (RT_SUCCESS(rc2)) + { + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + ShClTransferDestroy(pTransfer); + pTransfer = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets the host service's (file) transfer mode. + * + * @returns VBox status code. + * @param fMode Transfer mode to set. + */ +int shClSvcTransferModeSet(uint32_t fMode) +{ + if (fMode & ~VBOX_SHCL_TRANSFER_MODE_VALID_MASK) + return VERR_INVALID_FLAGS; + + g_fTransferMode = fMode; + +#ifdef DEBUG_andy +g_fTransferMode = VBOX_SHCL_TRANSFER_MODE_ENABLED; +#endif + + LogRel2(("Shared Clipboard: File transfers are now %s\n", + g_fTransferMode != VBOX_SHCL_TRANSFER_MODE_DISABLED ? "enabled" : "disabled")); + + /* If file transfers are being disabled, make sure to also reset (destroy) all pending transfers. */ + if (g_fTransferMode == VBOX_SHCL_TRANSFER_MODE_DISABLED) + { + ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); + while (itClient != g_mapClients.end()) + { + PSHCLCLIENT pClient = itClient->second; + AssertPtr(pClient); + + shClSvcClientTransfersReset(pClient); + + ++itClient; + } + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h new file mode 100644 index 00000000..89968d40 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -0,0 +1,35 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.h $ */ +/** @file + * Shared Clipboard Service - Internal header for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h +#define VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp new file mode 100644 index 00000000..b008b2a2 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp @@ -0,0 +1,42 @@ +/* $Id: VBoxSharedClipboardSvc-utils.cpp $ */ +/** @file + * Shared Clipboard Service - Host service utility functions. + */ + +/* + * Copyright (C) 2019-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <iprt/errcore.h> +#include <iprt/path.h> + +#include "VBoxSharedClipboardSvc-internal.h" + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp new file mode 100644 index 00000000..2a8db65d --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp @@ -0,0 +1,890 @@ +/* $Id: VBoxSharedClipboardSvc-win.cpp $ */ +/** @file + * Shared Clipboard Service - Win32 host. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <iprt/win/windows.h> + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/GuestHost/SharedClipboard-win.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <VBox/GuestHost/SharedClipboard-transfers.h> +#endif + +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <iprt/utf16.h> +#endif + +#include <process.h> +#include <iprt/win/shlobj.h> /* Needed for shell objects. */ + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx); + +struct SHCLCONTEXT +{ + /** Handle for window message handling thread. */ + RTTHREAD hThread; + /** Structure for keeping and communicating with service client. */ + PSHCLCLIENT pClient; + /** Windows-specific context data. */ + SHCLWINCTX Win; +}; + + +/** + * Copy clipboard data into the guest buffer. + * + * At first attempt, guest will provide a buffer of default size. + * Usually 1K or 4K (see platform specific Guest Additions code around + * VbglR3ClipboardReadData calls). If this buffer is not big enough + * to fit host clipboard content, this function will return VINF_BUFFER_OVERFLOW + * and provide guest with host's clipboard buffer actual size. This will be a + * signal for the guest to re-read host clipboard data providing bigger buffer + * to store it. + * + * @returns IPRT status code. + * @returns VINF_BUFFER_OVERFLOW returned when guest buffer size if not big + * enough to store host clipboard data. This is a signal to the guest + * to re-issue host clipboard read request with bigger buffer size + * (specified in @a pcbActualDst output parameter). + * @param u32Format VBox clipboard format (VBOX_SHCL_FMT_XXX) of copied data. + * @param pvSrc Pointer to host clipboard data. + * @param cbSrc Size (in bytes) of actual clipboard data to copy. + * @param pvDst Pointer to guest buffer to store clipboard data. + * @param cbDst Size (in bytes) of guest buffer. + * @param pcbActualDst Actual size (in bytes) of host clipboard data. + * Only set if guest buffer size if not big enough + * to store host clipboard content. When set, + * function returns VINF_BUFFER_OVERFLOW. + */ +static int vboxClipboardSvcWinDataGet(uint32_t u32Format, const void *pvSrc, uint32_t cbSrc, + void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst) +{ + AssertPtrReturn(pvSrc, VERR_INVALID_POINTER); + AssertReturn (cbSrc, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvDst, VERR_INVALID_POINTER); + AssertReturn (cbDst, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbActualDst, VERR_INVALID_POINTER); + + LogFlowFunc(("cbSrc = %d, cbDst = %d\n", cbSrc, cbDst)); + + if ( u32Format == VBOX_SHCL_FMT_HTML + && SharedClipboardWinIsCFHTML((const char *)pvSrc)) + { + /** @todo r=bird: Why the double conversion? */ + char *pszBuf = NULL; + uint32_t cbBuf = 0; + int rc = SharedClipboardWinConvertCFHTMLToMIME((const char *)pvSrc, cbSrc, &pszBuf, &cbBuf); + if (RT_SUCCESS(rc)) + { + *pcbActualDst = cbBuf; + if (cbBuf > cbDst) + { + /* Do not copy data. The dst buffer is not enough. */ + RTMemFree(pszBuf); + return VINF_BUFFER_OVERFLOW; + } + memcpy(pvDst, pszBuf, cbBuf); + RTMemFree(pszBuf); + } + else + *pcbActualDst = 0; + } + else + { + *pcbActualDst = cbSrc; /* Tell the caller how much space we need. */ + + if (cbSrc > cbDst) + return VINF_BUFFER_OVERFLOW; + + memcpy(pvDst, pvSrc, cbSrc); + } + +#ifdef LOG_ENABLED + ShClDbgDumpData(pvDst, cbSrc, u32Format); +#endif + + return VINF_SUCCESS; +} + +static int vboxClipboardSvcWinDataRead(PSHCLCONTEXT pCtx, UINT uFormat, void **ppvData, uint32_t *pcbData) +{ + SHCLFORMAT fFormat = SharedClipboardWinClipboardFormatToVBox(uFormat); + LogFlowFunc(("uFormat=%u -> uFmt=0x%x\n", uFormat, fFormat)); + + if (fFormat == VBOX_SHCL_FMT_NONE) + { + LogRel2(("Shared Clipboard: Windows format %u not supported, ignoring\n", uFormat)); + return VERR_NOT_SUPPORTED; + } + + PSHCLEVENT pEvent; + int rc = ShClSvcGuestDataRequest(pCtx->pClient, fFormat, &pEvent); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + *ppvData = pPayload ? pPayload->pvData : NULL; + *pcbData = pPayload ? pPayload->cbData : 0; + } + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reading guest clipboard data for Windows host failed with %Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, + HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + AssertPtr(pCtx); + + LRESULT lresultRc = 0; + + const PSHCLWINCTX pWinCtx = &pCtx->Win; + + switch (uMsg) + { + case WM_CLIPBOARDUPDATE: + { + LogFunc(("WM_CLIPBOARDUPDATE\n")); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + if (RT_SUCCESS(rc)) + { + const HWND hWndClipboardOwner = GetClipboardOwner(); + + LogFunc(("WM_CLIPBOARDUPDATE: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", + pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); + + if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + + /* Clipboard was updated by another application, retrieve formats and report back. */ + rc = vboxClipboardSvcWinSyncInternal(pCtx); + } + else + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: WM_CLIPBOARDUPDATE failed with %Rrc\n", rc)); + + break; + } + + case WM_CHANGECBCHAIN: + { + LogFunc(("WM_CHANGECBCHAIN\n")); + lresultRc = SharedClipboardWinHandleWMChangeCBChain(pWinCtx, hWnd, uMsg, wParam, lParam); + break; + } + + case WM_DRAWCLIPBOARD: + { + LogFunc(("WM_DRAWCLIPBOARD\n")); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + if (RT_SUCCESS(rc)) + { + const HWND hWndClipboardOwner = GetClipboardOwner(); + + LogFunc(("WM_DRAWCLIPBOARD: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", + pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); + + if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + + /* Clipboard was updated by another application, retrieve formats and report back. */ + rc = vboxClipboardSvcWinSyncInternal(pCtx); + } + else + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + } + } + + lresultRc = SharedClipboardWinChainPassToNext(pWinCtx, uMsg, wParam, lParam); + break; + } + + case WM_TIMER: + { + int rc = SharedClipboardWinHandleWMTimer(pWinCtx); + AssertRC(rc); + + break; + } + + case WM_RENDERFORMAT: + { + LogFunc(("WM_RENDERFORMAT\n")); + + /* Insert the requested clipboard format data into the clipboard. */ + const UINT uFormat = (UINT)wParam; + const SHCLFORMAT fFormat = SharedClipboardWinClipboardFormatToVBox(uFormat); + LogFunc(("WM_RENDERFORMAT: uFormat=%u -> fFormat=0x%x\n", uFormat, fFormat)); + + if ( fFormat == VBOX_SHCL_FMT_NONE + || pCtx->pClient == NULL) + { + /* Unsupported clipboard format is requested. */ + LogFunc(("WM_RENDERFORMAT unsupported format requested or client is not active\n")); + SharedClipboardWinClear(); + } + else + { + void *pvData = NULL; + uint32_t cbData = 0; + int rc = vboxClipboardSvcWinDataRead(pCtx, uFormat, &pvData, &cbData); + if ( RT_SUCCESS(rc) + && pvData + && cbData) + { + /* Wrap HTML clipboard content info CF_HTML format if needed. */ + if (fFormat == VBOX_SHCL_FMT_HTML + && !SharedClipboardWinIsCFHTML((char *)pvData)) + { + char *pszWrapped = NULL; + uint32_t cbWrapped = 0; + rc = SharedClipboardWinConvertMIMEToCFHTML((char *)pvData, cbData, &pszWrapped, &cbWrapped); + if (RT_SUCCESS(rc)) + { + /* Replace buffer with wrapped data content. */ + RTMemFree(pvData); + pvData = (void *)pszWrapped; + cbData = cbWrapped; + } + else + LogRel(("Shared Clipboard: cannot convert HTML clipboard into CF_HTML format, rc=%Rrc\n", rc)); + } + + rc = SharedClipboardWinDataWrite(uFormat, pvData, cbData); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Setting clipboard data for Windows host failed with %Rrc\n", rc)); + + RTMemFree(pvData); + cbData = 0; + } + + if (RT_FAILURE(rc)) + SharedClipboardWinClear(); + } + + break; + } + + case WM_RENDERALLFORMATS: + { + LogFunc(("WM_RENDERALLFORMATS\n")); + + int rc = SharedClipboardWinHandleWMRenderAllFormats(pWinCtx, hWnd); + AssertRC(rc); + + break; + } + + case SHCL_WIN_WM_REPORT_FORMATS: + { + /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT (or via IDataObject). */ + SHCLFORMATS fFormats = (uint32_t)lParam; + LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=%#xn", fFormats)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + PSHCLTRANSFER pTransfer; + int rc = shClSvcTransferStart(pCtx->pClient, + SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + &pTransfer); + if (RT_SUCCESS(rc)) + { + /* Create the IDataObject implementation the host OS needs and assign + * the newly created transfer to this object. */ + rc = SharedClipboardWinTransferCreate(&pCtx->Win, pTransfer); + + /* Note: The actual requesting + retrieving of data will be done in the IDataObject implementation + (ClipboardDataObjectImpl::GetData()). */ + } + else + LogRel(("Shared Clipboard: Initializing read transfer failed with %Rrc\n", rc)); + } + else + { +#endif + int rc = SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hWnd); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting clipboard formats %#x to Windows host failed with %Rrc\n", fFormats, rc)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + } +#endif + LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: lastErr=%ld\n", GetLastError())); + break; + } + + case WM_DESTROY: + { + LogFunc(("WM_DESTROY\n")); + + int rc = SharedClipboardWinHandleWMDestroy(pWinCtx); + AssertRC(rc); + + PostQuitMessage(0); + break; + } + + default: + lresultRc = DefWindowProc(hWnd, uMsg, wParam, lParam); + break; + } + + LogFlowFunc(("LEAVE hWnd=%p, WM_ %u -> %#zx\n", hWnd, uMsg, lresultRc)); + return lresultRc; +} + +/** + * Static helper function for having a per-client proxy window instances. + */ +static LRESULT CALLBACK vboxClipboardSvcWinWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA); + AssertPtrReturn(pUserData, 0); + + PSHCLCONTEXT pCtx = reinterpret_cast<PSHCLCONTEXT>(pUserData); + if (pCtx) + return vboxClipboardSvcWinWndProcMain(pCtx, hWnd, uMsg, wParam, lParam); + + return 0; +} + +/** + * Static helper function for routing Windows messages to a specific + * proxy window instance. + */ +static LRESULT CALLBACK vboxClipboardSvcWinWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + /* Note: WM_NCCREATE is not the first ever message which arrives, but + * early enough for us. */ + if (uMsg == WM_NCCREATE) + { + LogFlowFunc(("WM_NCCREATE\n")); + + LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam; + AssertPtr(pCS); + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams); + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxClipboardSvcWinWndProcInstance); + + return vboxClipboardSvcWinWndProcInstance(hWnd, uMsg, wParam, lParam); + } + + /* No window associated yet. */ + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) +{ + LogFlowFuncEnter(); + + bool fThreadSignalled = false; + + const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; + AssertPtr(pCtx); + const PSHCLWINCTX pWinCtx = &pCtx->Win; + + HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); + + /* Register the Window Class. */ + WNDCLASS wc; + RT_ZERO(wc); + + wc.style = CS_NOCLOSE; + wc.lpfnWndProc = vboxClipboardSvcWinWndProc; + wc.hInstance = hInstance; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + + /* Register an unique wnd class name. */ + char szWndClassName[32]; + RTStrPrintf2(szWndClassName, sizeof(szWndClassName), + "%s-%RU64", SHCL_WIN_WNDCLASS_NAME, RTThreadGetNative(hThreadSelf)); + wc.lpszClassName = szWndClassName; + + int rc; + + ATOM atomWindowClass = RegisterClass(&wc); + if (atomWindowClass == 0) + { + LogFunc(("Failed to register window class\n")); + rc = VERR_NOT_SUPPORTED; + } + else + { + /* Create a window and make it a clipboard viewer. */ + pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, + szWndClassName, szWndClassName, + WS_POPUPWINDOW, + -200, -200, 100, 100, NULL, NULL, hInstance, pCtx /* lpParam */); + if (pWinCtx->hWnd == NULL) + { + LogFunc(("Failed to create window\n")); + rc = VERR_NOT_SUPPORTED; + } + else + { + SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0, + SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); + + rc = SharedClipboardWinChainAdd(&pCtx->Win); + if (RT_SUCCESS(rc)) + { + if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI)) + pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000, NULL); + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (RT_SUCCESS(rc)) + { + HRESULT hr = OleInitialize(NULL); + if (FAILED(hr)) + { + LogRel(("Shared Clipboard: Initializing window thread OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); + /* Not critical, the rest of the clipboard might work. */ + } + else + LogRel(("Shared Clipboard: Initialized window thread OLE\n")); + } +#endif + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + + fThreadSignalled = true; + + MSG msg; + BOOL msgret = 0; + while ((msgret = GetMessage(&msg, NULL, 0, 0)) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* + * Window procedure can return error, * but this is exceptional situation that should be + * identified in testing. + */ + Assert(msgret >= 0); + LogFunc(("Message loop finished. GetMessage returned %d, message id: %d \n", msgret, msg.message)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ + OleUninitialize(); +#endif + } + } + + pWinCtx->hWnd = NULL; + + if (atomWindowClass != 0) + { + UnregisterClass(szWndClassName, hInstance); + atomWindowClass = 0; + } + + if (!fThreadSignalled) + { + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Synchronizes the host and the guest clipboard formats by sending all supported host clipboard + * formats to the guest. + * + * @returns VBox status code, VINF_NO_CHANGE if no synchronization was required. + * @param pCtx Clipboard context to synchronize. + */ +static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + int rc; + + if (pCtx->pClient) + { + SHCLFORMATS fFormats = 0; + rc = SharedClipboardWinGetFormats(&pCtx->Win, &fFormats); + if ( RT_SUCCESS(rc) + && fFormats != VBOX_SHCL_FMT_NONE /** @todo r=bird: BUGBUG: revisit this. */ + && ShClSvcIsBackendActive()) + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + } + else /* If we don't have any client data (yet), bail out. */ + rc = VINF_NO_CHANGE; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* + * Public platform dependent functions. + */ + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend, pTable); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + HRESULT hr = OleInitialize(NULL); + if (FAILED(hr)) + { + LogRel(("Shared Clipboard: Initializing OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); + /* Not critical, the rest of the clipboard might work. */ + } + else + LogRel(("Shared Clipboard: Initialized OLE\n")); +#endif + + return VINF_SUCCESS; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ + OleUninitialize(); +#endif +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, fHeadless); + + LogFlowFuncEnter(); + + int rc; + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = SharedClipboardWinCtxInit(&pCtx->Win); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pCtx->hThread, vboxClipboardSvcWinThread, pCtx /* pvUser */, _64K /* Stack size */, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); + if (RT_SUCCESS(rc)) + { + int rc2 = RTThreadUserWait(pCtx->hThread, 30 * 1000 /* Timeout in ms */); + AssertRC(rc2); + } + } + + pClient->State.pCtx = pCtx; + pClient->State.pCtx->pClient = pClient; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + /* Sync the host clipboard content with the client. */ + return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + int rc = VINF_SUCCESS; + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + if (pCtx) + { + if (pCtx->Win.hWnd) + PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */); + + if (pCtx->hThread != NIL_RTTHREAD) + { + LogFunc(("Waiting for thread to terminate ...\n")); + + /* Wait for the window thread to terminate. */ + rc = RTThreadWait(pCtx->hThread, 30 * 1000 /* Timeout in ms */, NULL); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Waiting for window thread termination failed with rc=%Rrc\n", rc)); + + pCtx->hThread = NIL_RTTHREAD; + } + + SharedClipboardWinCtxDestroy(&pCtx->Win); + + if (RT_SUCCESS(rc)) + { + RTMemFree(pCtx); + pCtx = NULL; + + pClient->State.pCtx = NULL; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=0x%x, hWnd=%p\n", fFormats, pCtx->Win.hWnd)); + + /* + * The guest announced formats. Forward to the window thread. + */ + PostMessage(pCtx->Win.hWnd, SHCL_WIN_WM_REPORT_FORMATS, + 0 /* wParam */, fFormats /* lParam */); + + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pBackend, pCmdCtx); + + AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("uFmt=%#x\n", uFmt)); + + HANDLE hClip = NULL; + + const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + + /* + * The guest wants to read data in the given format. + */ + int rc = SharedClipboardWinOpen(pWinCtx->hWnd); + if (RT_SUCCESS(rc)) + { + if (uFmt & VBOX_SHCL_FMT_BITMAP) + { + LogFunc(("CF_DIB\n")); + hClip = GetClipboardData(CF_DIB); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + if (lp != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_BITMAP, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFmt & VBOX_SHCL_FMT_UNICODETEXT) + { + LogFunc(("CF_UNICODETEXT\n")); + hClip = GetClipboardData(CF_UNICODETEXT); + if (hClip != NULL) + { + LPWSTR uniString = (LPWSTR)GlobalLock(hClip); + if (uniString != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_UNICODETEXT, uniString, (lstrlenW(uniString) + 1) * 2, + pvData, cbData, pcbActual); + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFmt & VBOX_SHCL_FMT_HTML) + { + LogFunc(("SHCL_WIN_REGFMT_HTML\n")); + UINT uRegFmt = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML); + if (uRegFmt != 0) + { + hClip = GetClipboardData(uRegFmt); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + if (lp != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_HTML, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); +#ifdef LOG_ENABLED + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Raw HTML clipboard data from host:\n")); + ShClDbgDumpHtml((char *)pvData, cbData); + } +#endif + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (uFmt & VBOX_SHCL_FMT_URI_LIST) + { + AssertFailed(); /** @todo */ + } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + SharedClipboardWinClose(); + } + + if (hClip == NULL) /* Empty data is not fatal. */ + { + /* Reply with empty data. */ + vboxClipboardSvcWinDataGet(0, NULL, 0, pvData, cbData, pcbActual); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data in format %#x from Windows, rc=%Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncEnter(); + + /* Nothing to do here yet. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend, pClient, pTransfer); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + SharedClipboardWinTransferDestroy(&pClient->State.pCtx->Win, pTransfer); + + return VINF_SUCCESS; +} + +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + + int rc = SharedClipboardWinGetRoots(pWinCtx, pTransfer); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp new file mode 100644 index 00000000..13499bf3 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp @@ -0,0 +1,123 @@ +/* $Id: VBoxSharedClipboardSvc-x11-stubs.cpp $*/ +/** @file + * Shared Clipboard Service - Linux host, a stub version with no functionality for use on headless hosts. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/alloc.h> +#include <iprt/asm.h> /* For atomic operations */ +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <string.h> +#include <stdio.h> +#include <stdint.h> + +#include "VBoxSharedClipboardSvc-internal.h" + + +/* + * Initialise the host side of the shared clipboard - called by the hgcm layer. + */ +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pTable); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/* + * Terminate the host side of the shared clipboard - called by the hgcm layer. + */ +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + LogFlowFunc(("called, returning\n")); +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, pClient, fHeadless); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/* + * Synchronise the contents of the host clipboard with the guest, called by the HGCM layer + * after a save and restore of the guest. + */ +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend, pClient); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend, pClient); + return VINF_SUCCESS; +} + +/* + * The guest is taking possession of the shared clipboard. + * Called by the HGCM clipboard subsystem. + */ +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend, pClient, fFormats); + return VINF_SUCCESS; +} + +/* + * Called by the HGCM clipboard subsystem when the guest wants to read the host clipboard. + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + /* No data available. */ + *pcbActual = 0; + + return VINF_SUCCESS; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + return VERR_NOT_IMPLEMENTED; +} + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp new file mode 100644 index 00000000..e4eacb07 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp @@ -0,0 +1,566 @@ +/* $Id: VBoxSharedClipboardSvc-x11.cpp $ */ +/** @file + * Shared Clipboard Service - Linux host. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/asm.h> + +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <iprt/errcore.h> + +#include "VBoxSharedClipboardSvc-internal.h" + +/* Number of currently extablished connections. */ +static volatile uint32_t g_cShClConnections; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Global context information used by the host glue for the X11 clipboard backend. + */ +struct SHCLCONTEXT +{ + /** This mutex is grabbed during any critical operations on the clipboard + * which might clash with others. */ + RTCRITSECT CritSect; + /** X11 context data. */ + SHCLX11CTX X11; + /** Pointer to the VBox host client data structure. */ + PSHCLCLIENT pClient; + /** We set this when we start shutting down as a hint not to post any new + * requests. */ + bool fShuttingDown; +}; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser); +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser); +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser); + + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* Override the connection limit. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]); + + RT_ZERO(pBackend->Callbacks); + /* Use internal callbacks by default. */ + pBackend->Callbacks.pfnReportFormats = shClReportFormatsCallback; + pBackend->Callbacks.pfnOnRequestDataFromSource = shClRequestDataFromSourceCallback; + pBackend->Callbacks.pfnOnSendDataToDest = shClSendDataToDestCallback; + + return VINF_SUCCESS; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); +} + +void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) +{ +#define SET_FN_IF_NOT_NULL(a_Fn) \ + if (pCallbacks->pfn##a_Fn) \ + pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn; + + SET_FN_IF_NOT_NULL(ReportFormats); + SET_FN_IF_NOT_NULL(OnClipboardRead); + SET_FN_IF_NOT_NULL(OnClipboardWrite); + SET_FN_IF_NOT_NULL(OnRequestDataFromSource); + SET_FN_IF_NOT_NULL(OnSendDataToDest); + +#undef SET_FN_IF_NOT_NULL +} + +/** + * @note On the host, we assume that some other application already owns + * the clipboard and leave ownership to X11. + */ +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + int rc; + + /* Check if maximum allowed connections count has reached. */ + if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX) + { + ASMAtomicDecU32(&g_cShClConnections); + LogRel(("Shared Clipboard: maximum amount for client connections reached\n")); + return VERR_OUT_OF_RESOURCES; + } + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = RTCritSectInit(&pCtx->CritSect); + if (RT_SUCCESS(rc)) + { + rc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless); + if (RT_SUCCESS(rc)) + { + pClient->State.pCtx = pCtx; + pCtx->pClient = pClient; + + rc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); + if (RT_FAILURE(rc)) + ShClX11Destroy(&pCtx->X11); + } + + if (RT_FAILURE(rc)) + RTCritSectDelete(&pCtx->CritSect); + } + + if (RT_FAILURE(rc)) + { + pClient->State.pCtx = NULL; + RTMemFree(pCtx); + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + /* Restore active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* Tell the guest we have no data in case X11 is not available. If + * there is data in the host clipboard it will automatically be sent to + * the guest when the clipboard starts up. */ + if (ShClSvcIsBackendActive()) + return ShClSvcHostReportFormats(pClient, VBOX_SHCL_FMT_NONE); + return VINF_SUCCESS; +} + +/* + * Shut down the shared clipboard service and "disconnect" the guest. + * Note! Host glue code + */ +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtr(pCtx); + + /* Drop the reference to the client, in case it is still there. This + * will cause any outstanding clipboard data requests from X11 to fail + * immediately. */ + pCtx->fShuttingDown = true; + + int rc = ShClX11ThreadStop(&pCtx->X11); + /** @todo handle this slightly more reasonably, or be really sure + * it won't go wrong. */ + AssertRC(rc); + + ShClX11Destroy(&pCtx->X11); + RTCritSectDelete(&pCtx->CritSect); + + RTMemFree(pCtx); + + /* Decrease active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + int rc = ShClX11ReportFormatsToX11(&pClient->State.pCtx->X11, fFormats); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** Structure describing a request for clipoard data from the guest. */ +struct CLIPREADCBREQ +{ + /** User-supplied data pointer, based on the request type. */ + void *pv; + /** The size (in bytes) of the the user-supplied pointer in pv. */ + uint32_t cb; + /** The actual size of the data written. */ + uint32_t *pcbActual; + /** The request's event ID. */ + SHCLEVENTID idEvent; +}; + +/** + * @note We always fail or complete asynchronously. + * @note On success allocates a CLIPREADCBREQ structure which must be + * freed in ClipCompleteDataRequestFromX11 when it is called back from + * the backend code. + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pCmdCtx); + + LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", + pClient, uFormat, pvData, cbData, pcbActual)); + + int rc; + + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pReq->pv = pvData; + pReq->cb = cbData; + pReq->pcbActual = pcbActual; + pReq->idEvent = pEvent->idEvent; + + /* Note: ShClX11ReadDataFromX11() will consume pReq on success. */ + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, uFormat, pReq); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if (pPayload) + { + memcpy(pvData, pPayload->pvData, RT_MIN(cbData, pPayload->cbData)); + + *pcbActual = (uint32_t)pPayload->cbData; + + ShClPayloadFree(pPayload); + } + else /* No payload given; could happen on invalid / not-expected formats. */ + *pcbActual = 0; + } + } + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data from X11, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncEnter(); + + /* Nothing to do here yet. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnReportFormats */ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); + + int rc = VINF_SUCCESS; + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (ShClSvcIsBackendActive()) + { + /** @todo r=bird: BUGBUG: Revisit this */ + if (fFormats != VBOX_SHCL_FMT_NONE) /* No formats to report? */ + { + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + } + } + + RTCritSectLeave(&pClient->CritSect); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** @copydoc SHCLCALLBACKS::pfnOnSendDataToDest */ +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + + PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; + CLIPREADCBREQ *pReq = pData->pReq; + + LogFlowFunc(("rcCompletion=%Rrc, pReq=%p, pv=%p, cb=%RU32, idEvent=%RU32\n", + pData->rcCompletion, pReq, pv, cb, pReq->idEvent)); + + if (pReq->idEvent != NIL_SHCLEVENTID) + { + int rc2; + + PSHCLEVENTPAYLOAD pPayload = NULL; + if ( RT_SUCCESS(pData->rcCompletion) + && pv + && cb) + { + rc2 = ShClPayloadAlloc(pReq->idEvent, pv, cb, &pPayload); + AssertRC(rc2); + } + + rc2 = RTCritSectEnter(&pCtx->pClient->CritSect); + if (RT_SUCCESS(rc2)) + { + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pCtx->pClient->EventSrc, pReq->idEvent); + if (pEvent) + rc2 = ShClEventSignal(pEvent, pPayload); + + RTCritSectLeave(&pCtx->pClient->CritSect); + + if (RT_SUCCESS(rc2)) + pPayload = NULL; + } + + if (pPayload) + ShClPayloadFree(pPayload); + } + + if (pReq) + RTMemFree(pReq); + + LogRel2(("Shared Clipboard: Reading X11 clipboard data from host completed with %Rrc\n", pData->rcCompletion)); + + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource */ +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, uFmt=0x%x\n", pCtx, uFmt)); + + if (pCtx->fShuttingDown) + { + /* The shared clipboard is disconnecting. */ + LogRel(("Shared Clipboard: Host requested guest clipboard data after guest had disconnected\n")); + return VERR_WRONG_ORDER; + } + + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + RTCritSectEnter(&pClient->CritSect); + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* + * Note: We always return a generic URI list here. + * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs + * to decide & transform the list into the actual clipboard Atom target format the caller wanted. + */ + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + { + PSHCLTRANSFER pTransfer; + rc = shClSvcTransferStart(pCtx->pClient, + SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + &pTransfer); + if (RT_SUCCESS(rc)) + { + + } + else + LogRel(("Shared Clipboard: Initializing read transfer from guest failed with %Rrc\n", rc)); + + *ppv = NULL; + *pcb = 0; + + rc = VERR_NO_DATA; + } +#endif + + if (RT_SUCCESS(rc)) + { + /* Request data from the guest. */ + PSHCLEVENT pEvent; + rc = ShClSvcGuestDataRequest(pCtx->pClient, uFmt, &pEvent); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pClient->CritSect); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if ( !pPayload + || !pPayload->cbData) + { + rc = VERR_NO_DATA; + } + else + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + } + } + + RTCritSectEnter(&pClient->CritSect); + + ShClEventRelease(pEvent); + } + } + + RTCritSectLeave(&pClient->CritSect); + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Requesting data in format %#x for X11 host failed with %Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferRegister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferUnregister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + + return VINF_SUCCESS; +} + +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLEVENT pEvent; + int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->idEvent = pEvent->idEvent; + + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, VBOX_SHCL_FMT_URI_LIST, pReq); + if (RT_SUCCESS(rc)) + { + /* X supplies the data asynchronously, so we need to wait for data to arrive first. */ + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootsSet(pTransfer, + (char *)pPayload->pvData, pPayload->cbData + 1 /* Include termination */); + } + } + } + else + rc = VERR_NO_MEMORY; + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp new file mode 100644 index 00000000..571e47e8 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -0,0 +1,2834 @@ +/* $Id: VBoxSharedClipboardSvc.cpp $ */ +/** @file + * Shared Clipboard Service - Host service entry points. + */ + +/* + * Copyright (C) 2006-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_hostclip The Shared Clipboard Host Service + * + * The shared clipboard host service is the host half of the clibpoard proxying + * between the host and the guest. The guest parts live in VBoxClient, VBoxTray + * and VBoxService depending on the OS, with code shared between host and guest + * under src/VBox/GuestHost/SharedClipboard/. + * + * The service is split into a platform-independent core and platform-specific + * backends. The service defines two communication protocols - one to + * communicate with the clipboard service running on the guest, and one to + * communicate with the backend. These will be described in a very skeletal + * fashion here. + * + * r=bird: The "two communication protocols" does not seems to be factual, there + * is only one protocol, the first one mentioned. It cannot be backend + * specific, because the guest/host protocol is platform and backend agnostic in + * nature. You may call it versions, but I take a great dislike to "protocol + * versions" here, as you've just extended the existing protocol with a feature + * that allows to transfer files and directories too. See @bugref{9437#c39}. + * + * + * @section sec_hostclip_guest_proto The guest communication protocol + * + * The guest clipboard service communicates with the host service over HGCM + * (the host is a HGCM service). HGCM is connection based, so the guest side + * has to connect before anything else can be done. (Windows hosts currently + * only support one simultaneous connection.) Once it has connected, it can + * send messages to the host services, some of which will receive immediate + * replies from the host, others which will block till a reply becomes + * available. The latter is because HGCM does't allow the host to initiate + * communication, it must be guest triggered. The HGCM service is single + * threaded, so it doesn't matter if the guest tries to send lots of requests in + * parallel, the service will process them one at the time. + * + * There are currently four messages defined. The first is + * VBOX_SHCL_GUEST_FN_MSG_GET / VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, which waits + * for a message from the host. If a host message is sent while the guest is + * not waiting, it will be queued until the guest requests it. The host code + * only supports a single simultaneous GET call from one client guest. + * + * The second guest message is VBOX_SHCL_GUEST_FN_REPORT_FORMATS, which tells + * the host that the guest has new clipboard data available. The third is + * VBOX_SHCL_GUEST_FN_DATA_READ, which asks the host to send its clipboard data + * and waits until it arrives. The host supports at most one simultaneous + * VBOX_SHCL_GUEST_FN_DATA_READ call from a guest - if a second call is made + * before the first has returned, the first will be aborted. + * + * The last guest message is VBOX_SHCL_GUEST_FN_DATA_WRITE, which is used to + * send the contents of the guest clipboard to the host. This call should be + * used after the host has requested data from the guest. + * + * + * @section sec_hostclip_backend_proto The communication protocol with the + * platform-specific backend + * + * The initial protocol implementation (called protocol v0) was very simple, + * and could only handle simple data (like copied text and so on). It also + * was limited to two (2) fixed parameters at all times. + * + * Since VBox 6.1 a newer protocol (v1) has been established to also support + * file transfers. This protocol uses a (per-client) message queue instead + * (see VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT vs. VBOX_SHCL_GUEST_FN_GET_HOST_MSG). + * + * To distinguish the old (legacy) or new(er) protocol, the VBOX_SHCL_GUEST_FN_CONNECT + * message has been introduced. If an older guest does not send this message, + * an appropriate translation will be done to serve older Guest Additions (< 6.1). + * + * The protocol also support out-of-order messages by using so-called "context IDs", + * which are generated by the host. A context ID consists of a so-called "source event ID" + * and a so-called "event ID". Each HGCM client has an own, random, source event ID and + * generates non-deterministic event IDs so that the guest side does not known what + * comes next; the guest side has to reply with the same conext ID which was sent by + * the host request. + * + * Also see the protocol changelog at VBoxShClSvc.h. + * + * + * @section sec_uri_intro Transferring files + * + * Since VBox x.x.x transferring files via Shared Clipboard is supported. + * See the VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS define for supported / enabled + * platforms. This is called "Shared Clipboard transfers". + * + * Copying files / directories from guest A to guest B requires the host + * service to act as a proxy and cache, as we don't allow direct VM-to-VM + * communication. Copying from / to the host also is taken into account. + * + * At the moment a transfer is a all-or-nothing operation, e.g. it either + * completes or fails completely. There might be callbacks in the future + * to e.g. skip failing entries. + * + * Known limitations: + * + * - Support for VRDE (VRDP) is not implemented yet (see #9498). + * - Unicode support on Windows hosts / guests is not enabled (yet). + * - Symbolic links / Windows junctions are not allowed. + * - Windows alternate data streams (ADS) are not allowed. + * - No support for ACLs yet. + * - No (maybe never) support for NT4. + + * @section sec_transfer_structure Transfer handling structure + * + * All structures / classes are designed for running on both, on the guest + * (via VBoxTray / VBoxClient) or on the host (host service) to avoid code + * duplication where applicable. + * + * Per HGCM client there is a so-called "transfer context", which in turn can + * have one or mulitple so-called "Shared Clipboard transfer" objects. At the + * moment we only support on concurrent Shared Clipboard transfer per transfer + * context. It's being used for reading from a source or writing to destination, + * depening on its direction. An Shared Clipboard transfer can have optional + * callbacks which might be needed by various implementations. Also, transfers + * optionally can run in an asynchronous thread to prevent blocking the UI while + * running. + * + * @section sec_transfer_providers Transfer providers + * + * For certain implementations (for example on Windows guests / hosts, using + * IDataObject and IStream objects) a more flexible approach reqarding reading / + * writing is needed. For this so-called transfer providers abstract the way of how + * data is being read / written in the current context (host / guest), while + * the rest of the code stays the same. + * + * @section sec_transfer_protocol Transfer protocol + * + * The host service issues commands which the guest has to respond with an own + * message to. The protocol itself is designed so that it has primitives to list + * directories and open/close/read/write file system objects. + * + * Note that this is different from the DnD approach, as Shared Clipboard transfers + * need to be deeper integrated within the host / guest OS (i.e. for progress UI), + * and this might require non-monolithic / random access APIs to achieve. + * + * As there can be multiple file system objects (fs objects) selected for transfer, + * a transfer can be queried for its root entries, which then contains the top-level + * elements. Based on these elements, (a) (recursive) listing(s) can be performed + * to (partially) walk down into directories and query fs object information. The + * provider provides appropriate interface for this, even if not all implementations + * might need this mechanism. + * + * An Shared Clipboard transfer has three stages: + * - 1. Announcement: An Shared Clipboard transfer-compatible format (currently only one format available) + * has been announced, the destination side creates a transfer object, which then, + * depending on the actual implementation, can be used to tell the OS that + * there is transfer (file) data available. + * At this point this just acts as a (kind-of) promise to the OS that we + * can provide (file) data at some later point in time. + * + * - 2. Initialization: As soon as the OS requests the (file) data, mostly triggered + * by the user starting a paste operation (CTRL + V), the transfer get initialized + * on the destination side, which in turn lets the source know that a transfer + * is going to happen. + * + * - 3. Transfer: At this stage the actual transfer from source to the destination takes + * place. How the actual transfer is structurized (e.g. which files / directories + * are transferred in which order) depends on the destination implementation. This + * is necessary in order to fulfill requirements on the destination side with + * regards to ETA calculation or other dependencies. + * Both sides can abort or cancel the transfer at any time. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> +#include <VBox/vmm/vmmr3vtable.h> /* must be included before hgcmsvc.h */ + +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <VBox/AssertGuest.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> +#include <VBox/vmm/ssm.h> + +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/rand.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + +using namespace HGCM; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name The saved state versions for the shared clipboard service. + * + * @note We set bit 31 because prior to version 0x80000002 there would be a + * structure size rather than a version number. Setting bit 31 dispells + * any possible ambiguity. + * + * @{ */ +/** The current saved state version. */ +#define VBOX_SHCL_SAVED_STATE_VER_CURRENT VBOX_SHCL_SAVED_STATE_LEGACY_CID +/** Adds the legacy context ID list. */ +#define VBOX_SHCL_SAVED_STATE_LEGACY_CID UINT32_C(0x80000005) +/** Adds the client's POD state and client state flags. + * @since 6.1 RC1 */ +#define VBOX_SHCL_SAVED_STATE_VER_6_1RC1 UINT32_C(0x80000004) +/** First attempt saving state during @bugref{9437} development. + * @since 6.1 BETA 2 */ +#define VBOX_SHCL_SAVED_STATE_VER_6_1B2 UINT32_C(0x80000003) +/** First structured version. + * @since 3.1 / r53668 */ +#define VBOX_SHCL_SAVED_STATE_VER_3_1 UINT32_C(0x80000002) +/** This was just a state memory dump, including pointers and everything. + * @note This is not supported any more. Sorry. */ +#define VBOX_SHCL_SAVED_STATE_VER_NOT_SUPP (ARCH_BITS == 64 ? UINT32_C(72) : UINT32_C(48)) +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The backend instance data. + * Only one backend at a time is supported currently. */ +SHCLBACKEND g_ShClBackend; +PVBOXHGCMSVCHELPERS g_pHelpers; + +static RTCRITSECT g_CritSect; /** @todo r=andy Put this into some instance struct, avoid globals. */ +/** Global Shared Clipboard mode. */ +static uint32_t g_uMode = VBOX_SHCL_MODE_OFF; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Global Shared Clipboard (file) transfer mode. */ +uint32_t g_fTransferMode = VBOX_SHCL_TRANSFER_MODE_DISABLED; +#endif + +/** Is the clipboard running in headless mode? */ +static bool g_fHeadless = false; + +/** Holds the service extension state. */ +SHCLEXTSTATE g_ExtState = { 0 }; + +/** Global map of all connected clients. */ +ClipboardClientMap g_mapClients; + +/** Global list of all clients which are queued up (deferred return) and ready + * to process new commands. The key is the (unique) client ID. */ +ClipboardClientQueue g_listClientsDeferred; + +/** Host feature mask (VBOX_SHCL_HF_0_XXX) for VBOX_SHCL_GUEST_FN_REPORT_FEATURES + * and VBOX_SHCL_GUEST_FN_QUERY_FEATURES. */ +static uint64_t const g_fHostFeatures0 = VBOX_SHCL_HF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_HF_0_TRANSFERS +#endif + ; + + +/** + * Returns the current Shared Clipboard service mode. + * + * @returns Current Shared Clipboard service mode. + */ +uint32_t ShClSvcGetMode(void) +{ + return g_uMode; +} + +/** + * Returns the Shared Clipboard backend in use. + * + * @returns Pointer to backend instance. + */ +PSHCLBACKEND ShClSvcGetBackend(void) +{ + return &g_ShClBackend; +} + +/** + * Getter for headless setting. Also needed by testcase. + * + * @returns Whether service currently running in headless mode or not. + */ +bool ShClSvcGetHeadless(void) +{ + return g_fHeadless; +} + +static int shClSvcModeSet(uint32_t uMode) +{ + int rc = VERR_NOT_SUPPORTED; + + switch (uMode) + { + case VBOX_SHCL_MODE_OFF: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_HOST_TO_GUEST: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_GUEST_TO_HOST: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_BIDIRECTIONAL: + { + g_uMode = uMode; + + rc = VINF_SUCCESS; + break; + } + + default: + { + g_uMode = VBOX_SHCL_MODE_OFF; + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Takes the global Shared Clipboard service lock. + * + * @returns \c true if locking was successful, or \c false if not. + */ +bool ShClSvcLock(void) +{ + return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); +} + +/** + * Unlocks the formerly locked global Shared Clipboard service lock. + */ +void ShClSvcUnlock(void) +{ + int rc2 = RTCritSectLeave(&g_CritSect); + AssertRC(rc2); +} + +/** + * Resets a client's state message queue. + * + * @param pClient Pointer to the client data structure to reset message queue for. + * @note Caller enters pClient->CritSect. + */ +void shClSvcMsgQueueReset(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + LogFlowFuncEnter(); + + while (!RTListIsEmpty(&pClient->MsgQueue)) + { + PSHCLCLIENTMSG pMsg = RTListRemoveFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + shClSvcMsgFree(pClient, pMsg); + } + pClient->cMsgAllocated = 0; + + while (!RTListIsEmpty(&pClient->Legacy.lstCID)) + { + PSHCLCLIENTLEGACYCID pCID = RTListRemoveFirst(&pClient->Legacy.lstCID, SHCLCLIENTLEGACYCID, Node); + RTMemFree(pCID); + } + pClient->Legacy.cCID = 0; +} + +/** + * Allocates a new clipboard message. + * + * @returns Allocated clipboard message, or NULL on failure. + * @param pClient The client which is target of this message. + * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use + * @param cParms The number of parameters the message takes. + */ +PSHCLCLIENTMSG shClSvcMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) +{ + RT_NOREF(pClient); + PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); + if (pMsg) + { + uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); + if (cAllocated <= 4096) + { + RTListInit(&pMsg->ListEntry); + pMsg->cParms = cParms; + pMsg->idMsg = idMsg; + return pMsg; + } + AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); + ASMAtomicDecU32(&pClient->cMsgAllocated); + RTMemFree(pMsg); + } + return NULL; +} + +/** + * Frees a formerly allocated clipboard message. + * + * @param pClient The client which was the target of this message. + * @param pMsg Clipboard message to free. + */ +void shClSvcMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + /** @todo r=bird: Do accounting. */ + if (pMsg) + { + pMsg->idMsg = UINT32_C(0xdeadface); + RTMemFree(pMsg); + + uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); + Assert(cAllocated < UINT32_MAX / 2); + RT_NOREF(cAllocated); + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT + * return parameters. + * + * @param pMsg Message to set return parameters to. + * @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. + */ +static void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + Assert(cDstParms >= 2); + if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + paDstParms[0].u.uint32 = pMsg->idMsg; + else + paDstParms[0].u.uint64 = pMsg->idMsg; + paDstParms[1].u.uint32 = pMsg->cParms; + + uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); + while (i-- > 2) + switch (pMsg->aParms[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 = pMsg->aParms[i - 2].u.pointer.size; break; + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. + * + * @returns VBox status code. + * @param pMsg The message which parameters to return to the guest. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters should be exactly two + */ +static int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + /* + * Assert sanity. + */ + AssertPtr(pMsg); + AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); + AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); + + Assert(pMsg->cParms == 2); + Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); + switch (pMsg->idMsg) + { + case VBOX_SHCL_HOST_MSG_READ_DATA: + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + break; + default: + AssertFailed(); + } + + /* + * Set the parameters. + */ + if (pMsg->cParms > 0) + paDstParms[0] = pMsg->aParms[0]; + if (pMsg->cParms > 1) + paDstParms[1] = pMsg->aParms[1]; + return VINF_SUCCESS; +} + +/** + * Adds a new message to a client'S message queue. + * + * @param pClient Pointer to the client data structure to add new message to. + * @param pMsg Pointer to message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + * + * @note Caller must enter critical section. + */ +void shClSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + + LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", + ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); + + if (fAppend) + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + else + RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); +} + + +/** + * Appends a message to the client's queue and wake it up. + * + * @returns VBox status code, though the message is consumed regardless of what + * is returned. + * @param pClient The client to queue the message on. + * @param pMsg The message to queue. Ownership is always + * transfered to the queue. + * + * @note Caller must enter critical section. + */ +int shClSvcMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + AssertPtr(pClient); + LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); + + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + return shClSvcClientWakeup(pClient); +} + +/** + * Initializes a Shared Clipboard client. + * + * @param pClient Client to initialize. + * @param uClientID HGCM client ID to assign client to. + */ +int shClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + /* Assign the client ID. */ + pClient->State.uClientID = uClientID; + + RTListInit(&pClient->MsgQueue); + pClient->cMsgAllocated = 0; + + RTListInit(&pClient->Legacy.lstCID); + pClient->Legacy.cCID = 0; + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + + int rc = RTCritSectInit(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + /* Create the client's own event source. */ + rc = ShClEventSourceCreate(&pClient->EventSrc, 0 /* ID, ignored */); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("[Client %RU32] Using event source %RU32\n", uClientID, pClient->EventSrc.uID)); + + /* Reset the client state. */ + shclSvcClientStateReset(&pClient->State); + + /* (Re-)initialize the client state. */ + rc = shClSvcClientStateInit(&pClient->State, uClientID); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxInit(&pClient->Transfers.Ctx); +#endif + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destroys a Shared Clipboard client. + * + * @param pClient Client to destroy. + */ +void shClSvcClientDestroy(PSHCLCLIENT pClient) +{ + AssertPtrReturnVoid(pClient); + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + + /* Make sure to send a quit message to the guest so that it can terminate gracefully. */ + RTCritSectEnter(&pClient->CritSect); + if (pClient->Pending.uType) + { + if (pClient->Pending.cParms > 1) + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_QUIT); + if (pClient->Pending.cParms > 2) + HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); + g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + pClient->Pending.uType = 0; + pClient->Pending.cParms = 0; + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + } + RTCritSectLeave(&pClient->CritSect); + + ShClEventSourceDestroy(&pClient->EventSrc); + + shClSvcClientStateDestroy(&pClient->State); + + PSHCLCLIENTLEGACYCID pCidIter, pCidIterNext; + RTListForEachSafe(&pClient->Legacy.lstCID, pCidIter, pCidIterNext, SHCLCLIENTLEGACYCID, Node) + { + RTMemFree(pCidIter); + } + + int rc2 = RTCritSectDelete(&pClient->CritSect); + AssertRC(rc2); + + ClipboardClientMap::iterator itClient = g_mapClients.find(pClient->State.uClientID); + if (itClient != g_mapClients.end()) + g_mapClients.erase(itClient); + else + AssertFailed(); + + LogFlowFuncLeave(); +} + +void shClSvcClientLock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectEnter(&pClient->CritSect); + AssertRC(rc2); +} + +void shClSvcClientUnlock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectLeave(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Resets a Shared Clipboard client. + * + * @param pClient Client to reset. + */ +void shClSvcClientReset(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + RTCritSectEnter(&pClient->CritSect); + + /* Reset message queue. */ + shClSvcMsgQueueReset(pClient); + + /* Reset event source. */ + ShClEventSourceReset(&pClient->EventSrc); + + /* Reset pending state. */ + RT_ZERO(pClient->Pending); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + + shclSvcClientStateReset(&pClient->State); + + RTCritSectLeave(&pClient->CritSect); +} + +static int shClSvcClientNegogiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_NEGOTIATE_CHUNK_SIZE, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbClientMaxChunkSize = paParms[0].u.uint32; + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbClientChunkSize = paParms[1].u.uint32; + + uint32_t const cbHostMaxChunkSize = VBOX_SHCL_MAX_CHUNK_SIZE; /** @todo Make this configurable. */ + + /* + * Do the work. + */ + if (cbClientChunkSize == 0) /* Does the client want us to choose? */ + { + paParms[0].u.uint32 = cbHostMaxChunkSize; /* Maximum */ + paParms[1].u.uint32 = RT_MIN(pClient->State.cbChunkSize, cbHostMaxChunkSize); /* Preferred */ + + } + else /* The client told us what it supports, so update and report back. */ + { + paParms[0].u.uint32 = RT_MIN(cbClientMaxChunkSize, cbHostMaxChunkSize); /* Maximum */ + paParms[1].u.uint32 = RT_MIN(cbClientMaxChunkSize, pClient->State.cbChunkSize); /* Preferred */ + } + + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + Log(("[Client %RU32] chunk size: %#RU32, max: %#RU32\n", + pClient->State.uClientID, paParms[1].u.uint32, paParms[0].u.uint32)); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_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. + */ +static int shClSvcClientReportFeatures(PSHCLCLIENT 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_SHCL_GF_1_MUST_BE_ONE, VERR_INVALID_PARAMETER); + + /* + * Do the work. + */ + paParms[0].u.uint64 = g_fHostFeatures0; + paParms[1].u.uint64 = 0; + + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + pClient->State.fGuestFeatures0 = fFeatures0; + pClient->State.fGuestFeatures1 = fFeatures1; + LogRel2(("Shared Clipboard: Guest reported the following features: %#RX64\n", + pClient->State.fGuestFeatures0)); /* Note: fFeatures1 not used yet. */ + if (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) + LogRel2(("Shared Clipboard: Guest supports file transfers\n")); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_QUERY_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +static int shClSvcClientQueryFeatures(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); + 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_fHostFeatures0; + paParms[1].u.uint64 = 0; + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_FAILURE(rc)) + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_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. + * + * @note Caller takes and leave the client's critical section. + */ +static int shClSvcClientMsgPeek(PSHCLCLIENT 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 = g_pHelpers->pfnGetVMMDevSessionId(g_pHelpers); + if (idRestoreCheck != idRestore) + { + paParms[0].u.uint64 = idRestore; + LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VERR_VM_RESTORED (%#RX64 -> %#RX64)\n", + pClient->State.uClientID, idRestoreCheck, idRestore)); + return VERR_VM_RESTORED; + } + Assert(!g_pHelpers->pfnIsCallRestored(hCall)); + } + + /* + * Return information about the first message if one is pending in the list. + */ + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + shClSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms); + LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%s (%u), cParms=%u)\n", + pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + return VINF_SUCCESS; + } + + /* + * If we cannot wait, fail the call. + */ + if (!fWait) + { + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT -> VERR_TRY_AGAIN\n", pClient->State.uClientID)); + return VERR_TRY_AGAIN; + } + + /* + * Wait for the host to queue a message for this client. + */ + ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n", + pClient->State.uClientID), VERR_RESOURCE_BUSY); + pClient->Pending.hHandle = hCall; + pClient->Pending.cParms = cParms; + pClient->Pending.paParms = paParms; + pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT; + LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID)); + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if a message was pending and is being returned. + * @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. + * + * @note Caller takes and leave the client's critical section. + */ +static int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate input. + */ + ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* id32Msg */ + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* f32Formats */ + + paParms[0].u.uint32 = 0; + paParms[1].u.uint32 = 0; + + /* + * If there is a message pending we can return immediately. + */ + int rc; + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + LogFlowFunc(("[Client %RU32] uMsg=%s (%RU32), cParms=%RU32\n", pClient->State.uClientID, + ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms); + AssertPtr(g_pHelpers); + rc = g_pHelpers->pfnCallComplete(hCall, rc); + if (rc != VERR_CANCELLED) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + + rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + } + /* + * Otherwise we must wait. + */ + else + { + ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n", pClient->State.uClientID), + VERR_RESOURCE_BUSY); + + pClient->Pending.hHandle = hCall; + pClient->Pending.cParms = cParms; + pClient->Pending.paParms = paParms; + pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT; + + rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + + LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID)); + } + + LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); + return rc; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_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. + * + * @note Called from within pClient->CritSect. + */ +static int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + 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. + */ + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + ASSERT_GUEST_MSG_RETURN(pFirstMsg->idMsg == idMsgExpected || idMsgExpected == UINT32_MAX, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + VERR_MISMATCH); + ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + VERR_WRONG_PARAMETER_COUNT); + + /* Check the parameter types. */ + for (uint32_t i = 0; i < cParms; i++) + ASSERT_GUEST_MSG_RETURN(pFirstMsg->aParms[i].type == paParms[i].type, + ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->aParms[i].type, + paParms[i].type, pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg)), + 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->aParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + paParms[i].u.uint32 = pFirstMsg->aParms[i].u.uint32; + break; + + case VBOX_HGCM_SVC_PARM_64BIT: + paParms[i].u.uint64 = pFirstMsg->aParms[i].u.uint64; + break; + + case VBOX_HGCM_SVC_PARM_PTR: + { + uint32_t const cbSrc = pFirstMsg->aParms[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->aParms[i].u.pointer.addr, cbSrc); + else + { + AssertMsgFailed(("#%u: cbSrc=%RU32 is bigger than cbDst=%RU32\n", i, cbSrc, cbDst)); + rc = VERR_BUFFER_OVERFLOW; + } + break; + } + + default: + AssertMsgFailed(("#%u: %u\n", i, pFirstMsg->aParms[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(g_pHelpers); + rc = g_pHelpers->pfnCallComplete(hCall, rc); + + LogFlowFunc(("[Client %RU32] pfnCallComplete -> %Rrc\n", pClient->State.uClientID, rc)); + + if (rc != VERR_CANCELLED) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + } + + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + + LogFlowFunc(("[Client %RU32] Returning %Rrc\n", pClient->State.uClientID, rc)); + return rc; + } + + paParms[0].u.uint32 = 0; + paParms[1].u.uint32 = 0; + LogFlowFunc(("[Client %RU32] -> VERR_TRY_AGAIN\n", pClient->State.uClientID)); + return VERR_TRY_AGAIN; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_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_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 cParms Number of parameters. + * + * @note Called from within pClient->CritSect. + */ +static int shClSvcClientMsgCancel(PSHCLCLIENT pClient, uint32_t cParms) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_MSG_RETURN(cParms == 0, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + + /* + * Execute. + */ + if (pClient->Pending.uType != 0) + { + LogFlowFunc(("[Client %RU32] Cancelling waiting thread, isPending=%d, pendingNumParms=%RU32, m_idSession=%x\n", + pClient->State.uClientID, pClient->Pending.uType, pClient->Pending.cParms, pClient->State.uSessionID)); + + /* + * The PEEK call is simple: At least two parameters, all set to zero before sleeping. + */ + int rcComplete; + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + { + Assert(pClient->Pending.cParms >= 2); + if (pClient->Pending.paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT) + HGCMSvcSetU64(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + else + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + rcComplete = VINF_TRY_AGAIN; + } + /* + * The MSG_OLD 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 + { + Assert(pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); + if (pClient->Pending.cParms > 0) + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + if (pClient->Pending.cParms > 1) + HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); + rcComplete = pClient->Pending.cParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; + } + + g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + return VINF_SUCCESS; + } + return VWRN_NOT_FOUND; +} + + +/** + * Wakes up a pending client (i.e. waiting for new messages). + * + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the client is not in pending mode. + * + * @param pClient Client to wake up. + * @note Caller must enter pClient->CritSect. + */ +int shClSvcClientWakeup(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + int rc = VINF_NO_CHANGE; + + if (pClient->Pending.uType != 0) + { + LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); + + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); + + LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", + pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ + shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else + AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); + + rc = g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + + if ( rc != VERR_CANCELLED + && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + } + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + } + else + LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); + + return rc; +} + +/** + * Requests to read clipboard data from the guest. + * + * @returns VBox status code. + * @param pClient Client to request to read data form. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppEvent Where to return the event for waiting for new data on success. Optional. + * Must be released by the caller with ShClEventRelease(). + */ +int ShClSvcGuestDataRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + int rc = VERR_NOT_SUPPORTED; + + /* Generate a separate message for every (valid) format we support. */ + while (fFormats) + { + /* Pick the next format to get from the mask: */ + /** @todo Make format reporting precedence configurable? */ + SHCLFORMAT fFormat; + if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else if (fFormats & VBOX_SHCL_FMT_BITMAP) + fFormat = VBOX_SHCL_FMT_BITMAP; + else if (fFormats & VBOX_SHCL_FMT_HTML) + fFormat = VBOX_SHCL_FMT_HTML; + else + AssertMsgFailedBreak(("%#x\n", fFormats)); + + /* Remove it from the mask. */ + fFormats &= ~fFormat; + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(fFormat); + AssertPtrReturn(pszFmt, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Requesting guest clipboard data in format '%s'\n", pszFmt)); + RTStrFree(pszFmt); +#endif + /* + * Allocate messages, one for each format. + */ + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, + pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, + 2); + if (pMsg) + { + /* + * Enter the critical section and generate an event. + */ + RTCritSectEnter(&pClient->CritSect); + + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); + + const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); + + rc = VINF_SUCCESS; + + /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + AssertStmt(pClient->Legacy.cCID < 4096, rc = VERR_TOO_MUCH_DATA); + if (RT_SUCCESS(rc)) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + if (pCID) + { + pCID->uCID = uCID; + pCID->enmType = 0; /* Not used yet. */ + pCID->uFormat = fFormat; + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + } + else + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Format the message. + */ + if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) + HGCMSvcSetU64(&pMsg->aParms[0], uCID); + else + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); + HGCMSvcSetU32(&pMsg->aParms[1], fFormat); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + /* Return event handle to the caller if requested. */ + if (ppEvent) + { + *ppEvent = pEvent; + } + + shClSvcClientWakeup(pClient); + } + + /* Remove event from list if caller did not request event handle or in case + * of failure (in this case caller should not release event). */ + if ( RT_FAILURE(rc) + || !ppEvent) + { + ShClEventRelease(pEvent); + } + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + RTCritSectLeave(&pClient->CritSect); + + if (RT_FAILURE(rc)) + shClSvcMsgFree(pClient, pMsg); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + break; + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Requesting data in formats %#x from guest failed with %Rrc\n", fFormats, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Signals the host that clipboard data from the guest has been received. + * + * @returns VBox status code. Returns VERR_NOT_FOUND when related event ID was not found. + * @param pClient Client the guest clipboard data was received from. + * @param pCmdCtx Client command context. + * @param uFormat Clipboard format of data received. + * @param pvData Pointer to clipboard data received. This can be + * NULL if @a cbData is zero. + * @param cbData Size (in bytes) of clipboard data received. + * This can be zero. + */ +int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + LogFlowFuncEnter(); + RT_NOREF(uFormat); + + /* + * Validate input. + */ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); + AssertMsgReturn(idEvent != NIL_SHCLEVENTID, ("NIL event in context ID %#RX64\n", pCmdCtx->uContextID), VERR_WRONG_ORDER); + + PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, idEvent); + AssertMsgReturn(pEvent != NULL, ("Event %#x not found\n", idEvent), VERR_NOT_FOUND); + + /* + * Make a copy of the data so we can attach it to the signal. + * + * Note! We still signal the waiter should we run out of memory, + * because otherwise it will be stuck waiting. + */ + int rc = VINF_SUCCESS; + PSHCLEVENTPAYLOAD pPayload = NULL; + if (cbData > 0) + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + + /* + * Signal the event. + */ + int rc2 = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc2)) + { + rc = rc2; + ShClPayloadFree(pPayload); + LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", rc)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports available VBox clipboard formats to the guest. + * + * @note Host backend callers must check if it's active (use + * ShClSvcIsBackendActive) before calling to prevent mixing up the + * VRDE clipboard. + * + * @returns VBox status code. + * @param pClient Client to report clipboard formats to. + * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero + * is okay (empty the clipboard). + */ +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. Otherwise, silently ignore reporting + * formats and return VINF_SUCCESS in order to do not trigger client + * termination in svcConnect(). + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + { /* likely */ } + else + return VINF_SUCCESS; + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* + * If transfer mode is set to disabled, don't report the URI list format to the guest. + */ + if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED)) + { + fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + LogRel2(("Shared Clipboard: File transfers are disabled, skipping reporting those to the guest\n")); + } +#endif + +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(fFormats); + AssertPtrReturn(pszFmts, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Reporting formats '%s' to guest\n", pszFmts)); + RTStrFree(pszFmts); +#endif + + /* + * Allocate a message, populate parameters and post it to the client. + */ + int rc; + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); + if (pMsg) + { + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&pMsg->aParms[1], fFormats); + + RTCritSectEnter(&pClient->CritSect); + shClSvcMsgAddAndWakeupClient(pClient, pMsg); + RTCritSectLeave(&pClient->CritSect); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* If we announce an URI list, create a transfer locally and also tell the guest to create + * a transfer on the guest side. */ + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + rc = shClSvcTransferStart(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + NULL /* pTransfer */); + if (RT_SUCCESS(rc)) + rc = shClSvcSetSource(pClient, SHCLSOURCE_LOCAL); + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Initializing host write transfer failed with %Rrc\n", rc)); + } + else +#endif + { + rc = VINF_SUCCESS; + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting formats %#x to guest failed with %Rrc\n", fFormats, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Handles the VBOX_SHCL_GUEST_FN_REPORT_FORMATS message from the guest. + */ +static int shClSvcClientReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + /* + * Digest parameters. + */ + ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS + || ( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + /* no defined value, so just ignore it */ + iParm++; + } + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const fFormats = paParms[iParm].u.uint32; + iParm++; + if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + Assert(iParm == cParms); + + /* + * Report the formats. + * + * We ignore empty reports if the guest isn't the clipboard owner, this + * prevents a freshly booted guest with an empty clibpoard from clearing + * the host clipboard on startup. Likewise, when a guest shutdown it will + * typically issue an empty report in case it's the owner, we don't want + * that to clear host content either. + */ + int rc; + if (!fFormats && pClient->State.enmSource != SHCLSOURCE_REMOTE) + rc = VINF_SUCCESS; + else + { + rc = shClSvcSetSource(pClient, SHCLSOURCE_REMOTE); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(&g_CritSect); + if (RT_SUCCESS(rc)) + { + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.uFormat = fFormats; + + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof(parms)); + } + else + { +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(fFormats); + if (pszFmts) + { + LogRel2(("Shared Clipboard: Guest reported formats '%s' to host\n", pszFmts)); + RTStrFree(pszFmts); + } +#endif + rc = ShClBackendReportFormats(&g_ShClBackend, pClient, fFormats); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); + } + + RTCritSectLeave(&g_CritSect); + } + else + LogRel2(("Shared Clipboard: Unable to take internal lock while receiving guest clipboard announcement: %Rrc\n", rc)); + } + } + + return rc; +} + +/** + * Called when the guest wants to read host clipboard data. + * Handles the VBOX_SHCL_GUEST_FN_DATA_READ message. + * + * @returns VBox status code. + * @retval VINF_BUFFER_OVERFLOW if the guest supplied a smaller buffer than needed in order to read the host clipboard data. + * @param pClient Client that wants to read host clipboard data. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + */ +static int shClSvcClientReadData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFuncEnter(); + + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + /* + * Digest parameters. + * + * We are dragging some legacy here from the 6.1 dev cycle, a 5 parameter + * variant which prepends a 64-bit context ID (RAZ as meaning not defined), + * a 32-bit flag (MBZ, no defined meaning) and switches the last two parameters. + */ + ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_DATA_READ + || ( cParms == VBOX_SHCL_CPARMS_DATA_READ_61B + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + SHCLCLIENTCMDCTX cmdCtx; + RT_ZERO(cmdCtx); + if (cParms == VBOX_SHCL_CPARMS_DATA_READ_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + /* This has no defined meaning and was never used, however the guest passed stuff, so ignore it and leave idContext=0. */ + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + + SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE; + uint32_t cbData = 0; + void *pvData = NULL; + + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uFormat = paParms[iParm].u.uint32; + iParm++; + if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/ + iParm++; + } + else + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/ + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + } + Assert(iParm == cParms); + + /* + * For some reason we need to do this (makes absolutely no sense to bird). + */ + /** @todo r=bird: I really don't get why you need the State.POD.uFormat + * member. I'm sure there is a reason. Incomplete code? */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) + pClient->State.POD.uFormat = uFormat; + } + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(uFormat); + AssertPtrReturn(pszFmt, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Guest wants to read %RU32 bytes host clipboard data in format '%s'\n", cbData, pszFmt)); + RTStrFree(pszFmt); +#endif + + /* + * Do the reading. + */ + uint32_t cbActual = 0; + + int rc = RTCritSectEnter(&g_CritSect); + AssertRCReturn(rc, rc); + + /* If there is a service extension active, try reading data from it first. */ + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + + parms.uFormat = uFormat; + parms.u.pvData = pvData; + parms.cbData = cbData; + + g_ExtState.fReadingData = true; + + /* Read clipboard data from the extension. */ + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); + + LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", + g_ExtState.fDelayedAnnouncement, g_ExtState.fDelayedFormats, cbData, parms.cbData, rc)); + + /* Did the extension send the clipboard formats yet? + * Otherwise, do this now. */ + if (g_ExtState.fDelayedAnnouncement) + { + int rc2 = ShClSvcHostReportFormats(pClient, g_ExtState.fDelayedFormats); + AssertRC(rc2); + + g_ExtState.fDelayedAnnouncement = false; + g_ExtState.fDelayedFormats = 0; + } + + g_ExtState.fReadingData = false; + + if (RT_SUCCESS(rc)) + cbActual = parms.cbData; + } + else + { + rc = ShClBackendReadData(&g_ShClBackend, pClient, &cmdCtx, uFormat, pvData, cbData, &cbActual); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, cbActual)); + else + LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); + } + + if (RT_SUCCESS(rc)) + { + /* Return the actual size required to fullfil the request. */ + if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B) + HGCMSvcSetU32(&paParms[2], cbActual); + else + HGCMSvcSetU32(&paParms[3], cbActual); + + /* If the data to return exceeds the buffer the guest supplies, tell it (and let it try again). */ + if (cbActual >= cbData) + rc = VINF_BUFFER_OVERFLOW; + } + + RTCritSectLeave(&g_CritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Called when the guest writes clipboard data to the host. + * Handles the VBOX_SHCL_GUEST_FN_DATA_WRITE message. + * + * @returns VBox status code. + * @param pClient Client that wants to read host clipboard data. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + */ +int shClSvcClientWriteData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFuncEnter(); + + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + const bool fReportsContextID = RT_BOOL(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID); + + /* + * Digest parameters. + * + * There are 3 different format here, formatunately no parameters have been + * switch around so it's plain sailing compared to the DATA_READ message. + */ + ASSERT_GUEST_RETURN(fReportsContextID + ? cParms == VBOX_SHCL_CPARMS_DATA_WRITE || cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B + : cParms == VBOX_SHCL_CPARMS_DATA_WRITE_OLD, + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + SHCLCLIENTCMDCTX cmdCtx; + RT_ZERO(cmdCtx); + if (cParms > VBOX_SHCL_CPARMS_DATA_WRITE_OLD) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + cmdCtx.uContextID = paParms[iParm].u.uint64; + iParm++; + } + else + { + /* Older Guest Additions (< 6.1) did not supply a context ID. + * We dig it out from our saved context ID list then a bit down below. */ + } + + if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + + SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE; + uint32_t cbData = 0; + void *pvData = NULL; + + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Format bit. */ + uFormat = paParms[iParm].u.uint32; + iParm++; + if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* "cbData" - duplicates buffer size. */ + iParm++; + } + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + Assert(iParm == cParms); + + /* + * Handle / check context ID. + */ + if (!fReportsContextID) /* Do we have to deal with old(er) GAs (< 6.1) which don't support context IDs? Dig out the context ID then. */ + { + PSHCLCLIENTLEGACYCID pCID = NULL; + PSHCLCLIENTLEGACYCID pCIDIter; + RTListForEach(&pClient->Legacy.lstCID, pCIDIter, SHCLCLIENTLEGACYCID, Node) /* Slow, but does the job for now. */ + { + if (pCIDIter->uFormat == uFormat) + { + pCID = pCIDIter; + break; + } + } + + ASSERT_GUEST_MSG_RETURN(pCID != NULL, ("Context ID for format %#x not found\n", uFormat), VERR_INVALID_CONTEXT); + cmdCtx.uContextID = pCID->uCID; + + /* Not needed anymore; clean up. */ + Assert(pClient->Legacy.cCID); + pClient->Legacy.cCID--; + RTListNodeRemove(&pCID->Node); + RTMemFree(pCID); + } + + uint64_t const idCtxExpected = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, + VBOX_SHCL_CONTEXTID_GET_EVENT(cmdCtx.uContextID)); + ASSERT_GUEST_MSG_RETURN(cmdCtx.uContextID == idCtxExpected, + ("Wrong context ID: %#RX64, expected %#RX64\n", cmdCtx.uContextID, idCtxExpected), + VERR_INVALID_CONTEXT); + + /* + * For some reason we need to do this (makes absolutely no sense to bird). + */ + /** @todo r=bird: I really don't get why you need the State.POD.uFormat + * member. I'm sure there is a reason. Incomplete code? */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) + pClient->State.POD.uFormat = uFormat; + } + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(uFormat); + if (pszFmt) + { + LogRel2(("Shared Clipboard: Guest writes %RU32 bytes clipboard data in format '%s' to host\n", cbData, pszFmt)); + RTStrFree(pszFmt); + } +#endif + + /* + * Write the data to the active host side clipboard. + */ + int rc = RTCritSectEnter(&g_CritSect); + AssertRCReturn(rc, rc); + + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.uFormat = uFormat; + parms.u.pvData = pvData; + parms.cbData = cbData; + + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); + rc = VINF_SUCCESS; + } + else + { + /* Let the backend implementation know. */ + rc = ShClBackendWriteData(&g_ShClBackend, pClient, &cmdCtx, uFormat, pvData, cbData); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); + + int rc2; /* Don't return internals back to the guest. */ + rc2 = ShClSvcGuestDataSignal(pClient, &cmdCtx, uFormat, pvData, cbData); /* To complete pending events, if any. */ + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); + AssertRC(rc2); + } + + RTCritSectLeave(&g_CritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets an error from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + * @param pRc Where to store the received error code. + */ +static int shClSvcClientError(uint32_t cParms, VBOXHGCMSVCPARM paParms[], int *pRc) +{ + AssertPtrReturn(paParms, VERR_INVALID_PARAMETER); + AssertPtrReturn(pRc, VERR_INVALID_PARAMETER); + + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ERROR) + { + rc = HGCMSvcGetU32(&paParms[1], (uint32_t *)pRc); /** @todo int vs. uint32_t !!! */ + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets the transfer source type of a Shared Clipboard client. + * + * @returns VBox status code. + * @param pClient Client to set transfer source type for. + * @param enmSource Source type to set. + */ +int shClSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource) +{ + if (!pClient) /* If no client connected (anymore), bail out. */ + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + if (ShClSvcLock()) + { + pClient->State.enmSource = enmSource; + + LogFlowFunc(("Source of client %RU32 is now %RU32\n", pClient->State.uClientID, pClient->State.enmSource)); + + ShClSvcUnlock(); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static int svcInit(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = RTCritSectInit(&g_CritSect); + + if (RT_SUCCESS(rc)) + { + shClSvcModeSet(VBOX_SHCL_MODE_OFF); + + rc = ShClBackendInit(ShClSvcGetBackend(), pTable); + + /* Clean up on failure, because 'svnUnload' will not be called + * if the 'svcInit' returns an error. + */ + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&g_CritSect); + } + } + + return rc; +} + +static DECLCALLBACK(int) svcUnload(void *) +{ + LogFlowFuncEnter(); + + ShClBackendDestroy(ShClSvcGetBackend()); + + RTCritSectDelete(&g_CritSect); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect(void *, uint32_t u32ClientID, void *pvClient) +{ + LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* In order to communicate with guest service, HGCM VRDP clipboard extension + * needs to know its connection client ID. Currently, in svcConnect() we always + * cache ID of the first ever connected client. When client disconnects, + * we need to forget its ID and let svcConnect() to pick up the next ID when a new + * connection will be requested by guest service (see #10115). */ + if (g_ExtState.uClientID == u32ClientID) + { + g_ExtState.uClientID = 0; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + + ShClBackendDisconnect(&g_ShClBackend, pClient); + + shClSvcClientDestroy(pClient); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcConnect(void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + RT_NOREF(fRequestor, fRestoring); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pvClient); + + int rc = shClSvcClientInit(pClient, u32ClientID); + if (RT_SUCCESS(rc)) + { + /* Assign weak pointer to client map. */ + /** @todo r=bird: The g_mapClients is only there for looking up + * g_ExtState.uClientID (unserialized btw), so why not use store the + * pClient value directly in g_ExtState instead of the ID? It cannot + * crash any worse that racing map insertion/removal. */ + g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */ + rc = ShClBackendConnect(&g_ShClBackend, pClient, ShClSvcGetHeadless()); + if (RT_SUCCESS(rc)) + { + /* Sync the host clipboard content with the client. */ + rc = ShClBackendSync(&g_ShClBackend, pClient); + if (RT_SUCCESS(rc)) + { + /* For now we ASSUME that the first client that connects is in charge for + communicating with the service extension. */ + /** @todo This isn't optimal, but only the guest really knows which client is in + * focus on the console. See @bugref{10115} for details. */ + if (g_ExtState.uClientID == 0) + g_ExtState.uClientID = u32ClientID; + + /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but + older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for + success. So just return VINF_SUCCESS here to not break older Guest Additions. */ + LogFunc(("Successfully connected client %#x%s\n", + u32ClientID, g_ExtState.uClientID == u32ClientID ? " - Use by ExtState too" : "")); + return VINF_SUCCESS; + } + + LogFunc(("ShClBackendSync failed: %Rrc\n", rc)); + ShClBackendDisconnect(&g_ShClBackend, pClient); + } + else + LogFunc(("ShClBackendConnect failed: %Rrc\n", rc)); + shClSvcClientDestroy(pClient); + } + else + LogFunc(("shClSvcClientInit failed: %Rrc\n", rc)); + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(void) svcCall(void *, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32ClientID, + void *pvClient, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[], + uint64_t tsArrival) +{ + RT_NOREF(u32ClientID, pvClient, tsArrival); + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + +#ifdef LOG_ENABLED + Log2Func(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", + u32ClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, paParms)); + for (uint32_t i = 0; i < cParms; i++) + { + switch (paParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + Log3Func((" paParms[%RU32]: type uint32_t - value %RU32\n", i, paParms[i].u.uint32)); + break; + case VBOX_HGCM_SVC_PARM_64BIT: + Log3Func((" paParms[%RU32]: type uint64_t - value %RU64\n", i, paParms[i].u.uint64)); + break; + case VBOX_HGCM_SVC_PARM_PTR: + Log3Func((" paParms[%RU32]: type ptr - value 0x%p (%RU32 bytes)\n", + i, paParms[i].u.pointer.addr, paParms[i].u.pointer.size)); + break; + case VBOX_HGCM_SVC_PARM_PAGES: + Log3Func((" paParms[%RU32]: type pages - cb=%RU32, cPages=%RU16\n", + i, paParms[i].u.Pages.cb, paParms[i].u.Pages.cPages)); + break; + default: + AssertFailed(); + } + } + Log2Func(("Client state: fFlags=0x%x, fGuestFeatures0=0x%x, fGuestFeatures1=0x%x\n", + pClient->State.fFlags, pClient->State.fGuestFeatures0, pClient->State.fGuestFeatures1)); +#endif + + int rc; + switch (u32Function) + { + case VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgOldGet(pClient, callHandle, cParms, paParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_CONNECT: + LogRel(("Shared Clipboard: 6.1.0 beta or rc Guest Additions detected. Please upgrade!\n")); + rc = VERR_NOT_IMPLEMENTED; + break; + + case VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE: + rc = shClSvcClientNegogiateChunkSize(pClient, callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_REPORT_FEATURES: + rc = shClSvcClientReportFeatures(pClient, callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_QUERY_FEATURES: + rc = shClSvcClientQueryFeatures(callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, false /*fWait*/); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, true /*fWait*/); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_GET: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgGet(pClient, callHandle, cParms, paParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_CANCEL: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgCancel(pClient, cParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_REPORT_FORMATS: + rc = shClSvcClientReportFormats(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_DATA_READ: + rc = shClSvcClientReadData(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_DATA_WRITE: + rc = shClSvcClientWriteData(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_ERROR: + { + int rcGuest; + rc = shClSvcClientError(cParms,paParms, &rcGuest); + if (RT_SUCCESS(rc)) + { + LogRel(("Shared Clipboard: Error reported from guest side: %Rrc\n", rcGuest)); + + shClSvcClientLock(pClient); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + shClSvcClientUnlock(pClient); + } + break; + } + + default: + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( u32Function <= VBOX_SHCL_GUEST_FN_LAST + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID) ) + { + if (g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED) + rc = shClSvcTransferHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival); + else + { + LogRel2(("Shared Clipboard: File transfers are disabled for this VM\n")); + rc = VERR_ACCESS_DENIED; + } + } + else +#endif + { + LogRel2(("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); + rc = VERR_NOT_IMPLEMENTED; + } + break; + } + } + + LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); + + if (rc != VINF_HGCM_ASYNC_EXECUTE) + g_pHelpers->pfnCallComplete(callHandle, rc); +} + +/** + * Initializes a Shared Clipboard service's client state. + * + * @returns VBox status code. + * @param pClientState Client state to initialize. + * @param uClientID Client ID (HGCM) to use for this client state. + */ +int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID) +{ + LogFlowFuncEnter(); + + shclSvcClientStateReset(pClientState); + + /* Register the client. */ + pClientState->uClientID = uClientID; + + return VINF_SUCCESS; +} + +/** + * Destroys a Shared Clipboard service's client state. + * + * @returns VBox status code. + * @param pClientState Client state to destroy. + */ +int shClSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +/** + * Resets a Shared Clipboard service's client state. + * + * @param pClientState Client state to reset. + */ +void shclSvcClientStateReset(PSHCLCLIENTSTATE pClientState) +{ + LogFlowFuncEnter(); + + pClientState->fGuestFeatures0 = VBOX_SHCL_GF_NONE; + pClientState->fGuestFeatures1 = VBOX_SHCL_GF_NONE; + + pClientState->cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ + pClientState->enmSource = SHCLSOURCE_INVALID; + pClientState->fFlags = SHCLCLIENTSTATE_FLAGS_NONE; + + pClientState->POD.enmDir = SHCLTRANSFERDIR_UNKNOWN; + pClientState->POD.uFormat = VBOX_SHCL_FMT_NONE; + pClientState->POD.cbToReadWriteTotal = 0; + pClientState->POD.cbReadWritten = 0; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pClientState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN; +#endif +} + +/* + * We differentiate between a function handler for the guest and one for the host. + */ +static DECLCALLBACK(int) svcHostCall(void *, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("u32Function=%RU32 (%s), cParms=%RU32, paParms=%p\n", + u32Function, ShClHostFunctionToStr(u32Function), cParms, paParms)); + + switch (u32Function) + { + case VBOX_SHCL_HOST_FN_SET_MODE: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Mode = VBOX_SHCL_MODE_OFF; + + rc = HGCMSvcGetU32(&paParms[0], &u32Mode); + if (RT_SUCCESS(rc)) + rc = shClSvcModeSet(u32Mode); + } + + break; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t fTransferMode; + rc = HGCMSvcGetU32(&paParms[0], &fTransferMode); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferModeSet(fTransferMode); + } + break; + } +#endif + case VBOX_SHCL_HOST_FN_SET_HEADLESS: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t uHeadless; + rc = HGCMSvcGetU32(&paParms[0], &uHeadless); + if (RT_SUCCESS(rc)) + { + g_fHeadless = RT_BOOL(uHeadless); + LogRel(("Shared Clipboard: Service running in %s mode\n", g_fHeadless ? "headless" : "normal")); + } + } + break; + } + + default: + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + rc = shClSvcTransferHostHandler(u32Function, cParms, paParms); +#else + rc = VERR_NOT_IMPLEMENTED; +#endif + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifndef UNIT_TEST + +/** + * SSM descriptor table for the SHCLCLIENTLEGACYCID structure. + * + * @note Saving the ListEntry attribute is not necessary, as this gets used on runtime only. + */ +static SSMFIELD const s_aShClSSMClientLegacyCID[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uCID), + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, enmType), + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uFormat), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTSTATE structure. + * + * @note Saving the session ID not necessary, as they're not persistent across + * state save/restore. + */ +static SSMFIELD const s_aShClSSMClientState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fFlags), + SSMFIELD_ENTRY_TERM() +}; + +/** + * VBox 6.1 Beta 1 version of s_aShClSSMClientState (no flags). + */ +static SSMFIELD const s_aShClSSMClientState61B1[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTPODSTATE structure. + */ +static SSMFIELD const s_aShClSSMClientPODState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, enmDir), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, uFormat), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbToReadWriteTotal), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbReadWritten), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, tsLastReadWrittenMs), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTURISTATE structure. + */ +static SSMFIELD const s_aShClSSMClientTransferState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTTRANSFERSTATE, enmTransferDir), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the header of the SHCLCLIENTMSG structure. + * The actual message parameters will be serialized separately. + */ +static SSMFIELD const s_aShClSSMClientMsgHdr[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTMSG, idMsg), + SSMFIELD_ENTRY(SHCLCLIENTMSG, cParms), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for what used to be the VBOXSHCLMSGCTX structure but is + * now part of SHCLCLIENTMSG. + */ +static SSMFIELD const s_aShClSSMClientMsgCtx[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTMSG, idCtx), + SSMFIELD_ENTRY_TERM() +}; +#endif /* !UNIT_TEST */ + +static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + LogFlowFuncEnter(); + +#ifndef UNIT_TEST + /* + * When the state will be restored, pending requests will be reissued + * by VMMDev. The service therefore must save state as if there were no + * pending request. + * Pending requests, if any, will be completed in svcDisconnect. + */ + RT_NOREF(u32ClientID); + LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* Write Shared Clipboard saved state version. */ + pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + + int rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL); + AssertRCReturn(rc, rc); + + /* Serialize the client's internal message queue. */ + rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->cMsgAllocated); + AssertRCReturn(rc, rc); + + PSHCLCLIENTMSG pMsg; + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + { + pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); + pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); + + for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++) + HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); + } + + rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->Legacy.cCID); + AssertRCReturn(rc, rc); + + PSHCLCLIENTLEGACYCID pCID; + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) + { + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, &s_aShClSSMClientLegacyCID[0], NULL); + AssertRCReturn(rc, rc); + } +#else /* UNIT_TEST */ + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); +#endif /* UNIT_TEST */ + return VINF_SUCCESS; +} + +#ifndef UNIT_TEST +static int svcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + RT_NOREF(u32ClientID, pvClient, pSSM, uVersion); + + uint32_t uMarker; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ + AssertRC(rc); + Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + + rc = pVMM->pfnSSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */ + AssertRCReturn(rc, rc); + + bool fValue; + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgQuit */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgReadData */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgFormats */ + AssertRCReturn(rc, rc); + + uint32_t fFormats; + rc = pVMM->pfnSSMR3GetU32(pSSM, &fFormats); /* u32RequestedFormat */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* End marker. */ + AssertRCReturn(rc, rc); + Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */); + + return VINF_SUCCESS; +} +#endif /* UNIT_TEST */ + +static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + LogFlowFuncEnter(); + +#ifndef UNIT_TEST + + RT_NOREF(u32ClientID, uVersion); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* Restore the client data. */ + uint32_t lenOrVer; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &lenOrVer); + AssertRCReturn(rc, rc); + + LogFunc(("u32ClientID=%RU32, lenOrVer=%#RX64\n", u32ClientID, lenOrVer)); + + if (lenOrVer == VBOX_SHCL_SAVED_STATE_VER_3_1) + return svcLoadStateV0(u32ClientID, pvClient, pSSM, pVMM, uVersion); + + if ( lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1B2 + && lenOrVer <= VBOX_SHCL_SAVED_STATE_VER_CURRENT) + { + if (lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1RC1) + { + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState[0], NULL); + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, + &s_aShClSSMClientPODState[0], NULL); + } + else + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState61B1[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /* fFlags */, + &s_aShClSSMClientTransferState[0], NULL); + AssertRCReturn(rc, rc); + + /* Load the client's internal message queue. */ + uint64_t cMsgs; + rc = pVMM->pfnSSMR3GetU64(pSSM, &cMsgs); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(cMsgs < _16K, ("Too many messages: %u (%x)\n", cMsgs, cMsgs), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + for (uint64_t i = 0; i < cMsgs; i++) + { + union + { + SHCLCLIENTMSG Msg; + uint8_t abPadding[RT_UOFFSETOF(SHCLCLIENTMSG, aParms) + sizeof(VBOXHGCMSVCPARM) * 2]; + } u; + + pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgCtx[0], NULL); + AssertRCReturn(rc, rc); + + AssertLogRelMsgReturn(u.Msg.cParms <= VMMDEV_MAX_HGCM_PARMS, + ("Too many HGCM message parameters: %u (%#x)\n", u.Msg.cParms, u.Msg.cParms), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, u.Msg.idMsg, u.Msg.cParms); + AssertReturn(pMsg, VERR_NO_MEMORY); + pMsg->idCtx = u.Msg.idCtx; + + for (uint32_t p = 0; p < pMsg->cParms; p++) + { + rc = HGCMSvcSSMR3Get(&pMsg->aParms[p], pSSM, pVMM); + AssertRCReturnStmt(rc, shClSvcMsgFree(pClient, pMsg), rc); + } + + RTCritSectEnter(&pClient->CritSect); + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + RTCritSectLeave(&pClient->CritSect); + } + + if (lenOrVer >= VBOX_SHCL_SAVED_STATE_LEGACY_CID) + { + uint64_t cCID; + rc = pVMM->pfnSSMR3GetU64(pSSM, &cCID); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(cCID < _16K, ("Too many context IDs: %u (%x)\n", cCID, cCID), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + for (uint64_t i = 0; i < cCID; i++) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + AssertPtrReturn(pCID, VERR_NO_MEMORY); + + pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, + &s_aShClSSMClientLegacyCID[0], NULL); + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + } + } + } + else + { + LogRel(("Shared Clipboard: Unsupported saved state version (%#x)\n", lenOrVer)); + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + } + + /* Actual host data are to be reported to guest (SYNC). */ + ShClBackendSync(&g_ShClBackend, pClient); + +#else /* UNIT_TEST */ + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); +#endif /* UNIT_TEST */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) extCallback(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData) +{ + RT_NOREF(pvData, cbData); + + LogFlowFunc(("u32Function=%RU32\n", u32Function)); + + int rc = VINF_SUCCESS; + + /* Figure out if the client in charge for the service extension still is connected. */ + ClipboardClientMap::const_iterator itClient = g_mapClients.find(g_ExtState.uClientID); + if (itClient != g_mapClients.end()) + { + PSHCLCLIENT pClient = itClient->second; + AssertPtr(pClient); + + switch (u32Function) + { + /* The service extension announces formats to the guest. */ + case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: + { + LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: g_ExtState.fReadingData=%RTbool\n", g_ExtState.fReadingData)); + if (!g_ExtState.fReadingData) + rc = ShClSvcHostReportFormats(pClient, u32Format); + else + { + g_ExtState.fDelayedAnnouncement = true; + g_ExtState.fDelayedFormats = u32Format; + rc = VINF_SUCCESS; + } + break; + } + + /* The service extension wants read data from the guest. */ + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + rc = ShClSvcGuestDataRequest(pClient, u32Format, NULL /* pidEvent */); + break; + + default: + /* Just skip other messages. */ + break; + } + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) svcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension) +{ + LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + + /* + * Reference counting for service extension registration is done a few + * layers up (in ConsoleVRDPServer::ClipboardCreate()). + */ + + int rc = RTCritSectEnter(&g_CritSect); + AssertLogRelRCReturn(rc, rc); + + if (pfnExtension) + { + /* Install extension. */ + g_ExtState.pfnExtension = pfnExtension; + g_ExtState.pvExtension = pvExtension; + + parms.u.pfnCallback = extCallback; + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + + LogRel2(("Shared Clipboard: registered service extension\n")); + } + else + { + if (g_ExtState.pfnExtension) + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + + /* Uninstall extension. */ + g_ExtState.pvExtension = NULL; + g_ExtState.pfnExtension = NULL; + + LogRel2(("Shared Clipboard: de-registered service extension\n")); + } + + RTCritSectLeave(&g_CritSect); + + return VINF_SUCCESS; +} + +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 + { + LogFunc(("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 + { + g_pHelpers = pTable->pHelpers; + + pTable->cbClient = sizeof(SHCLCLIENT); + + /* Map legacy clients to root. */ + pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; + + /* Limit the number of clients to 128 in each category (should be enough), + but set kernel clients to 1. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = 128; + pTable->acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = 1; + + /* Only 16 pending calls per client (1 should be enough). */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxCallsPerClient[i] = 16; + + pTable->pfnUnload = svcUnload; + pTable->pfnConnect = svcConnect; + pTable->pfnDisconnect = svcDisconnect; + pTable->pfnCall = svcCall; + pTable->pfnHostCall = svcHostCall; + pTable->pfnSaveState = svcSaveState; + pTable->pfnLoadState = svcLoadState; + pTable->pfnRegisterExtension = svcRegisterExtension; + pTable->pfnNotify = NULL; + pTable->pvService = NULL; + + /* Service specific initialization. */ + rc = svcInit(pTable); + } + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc new file mode 100644 index 00000000..7e17a0b5 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxSharedClipboardSvc.rc $ */ +/** @file + * Shared Clipboard Service - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Shared Clipboard Host Service\0" + VALUE "InternalName", "VBoxSharedClipboard\0" + VALUE "OriginalFilename", "VBoxSharedClipboard.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp new file mode 100644 index 00000000..290763be --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp @@ -0,0 +1,718 @@ +/* $Id: darwin-pasteboard.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Includes contributions from François Revol + * + * Copyright (C) 2008-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <Carbon/Carbon.h> + +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/errcore.h> +#include <iprt/utf16.h> + +#include <VBox/log.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/clipboard-helper.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define WITH_HTML_H2G 1 +#define WITH_HTML_G2H 1 + +/* For debugging */ +//#define SHOW_CLIPBOARD_CONTENT + + +/** + * Initialize the global pasteboard and return a reference to it. + * + * @param pPasteboardRef Reference to the global pasteboard. + * + * @returns IPRT status code. + */ +int initPasteboard(PasteboardRef *pPasteboardRef) +{ + int rc = VINF_SUCCESS; + + if (PasteboardCreate(kPasteboardClipboard, pPasteboardRef)) + rc = VERR_NOT_SUPPORTED; + + return rc; +} + +/** + * Release the reference to the global pasteboard. + * + * @param pPasteboardRef Reference to the global pasteboard. + */ +void destroyPasteboard(PasteboardRef *pPasteboardRef) +{ + CFRelease(*pPasteboardRef); + *pPasteboardRef = NULL; +} + +/** + * Inspect the global pasteboard for new content. Check if there is some type + * that is supported by vbox and return it. + * + * @param hPasteboard Reference to the global pasteboard. + * @param idOwnership Our ownership ID. + * @param hStrOwnershipFlavor The ownership flavor string reference returned + * by takePasteboardOwnership(). + * @param pfFormats Pointer for the bit combination of the + * supported types. + * @param pfChanged True if something has changed after the + * last call. + * + * @returns VINF_SUCCESS. + */ +int queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, + uint32_t *pfFormats, bool *pfChanged) +{ + OSStatus orc; + + *pfFormats = 0; + *pfChanged = true; + + PasteboardSyncFlags syncFlags; + /* Make sure all is in sync */ + syncFlags = PasteboardSynchronize(hPasteboard); + /* If nothing changed return */ + if (!(syncFlags & kPasteboardModified)) + { + *pfChanged = false; + Log2(("queryNewPasteboardFormats: no change\n")); + return VINF_SUCCESS; + } + + /* Are some items in the pasteboard? */ + ItemCount cItems = 0; + orc = PasteboardGetItemCount(hPasteboard, &cItems); + if (orc == 0) + { + if (cItems < 1) + Log(("queryNewPasteboardFormats: changed: No items on the pasteboard\n")); + else + { + /* The id of the first element in the pasteboard */ + PasteboardItemID idItem = 0; + orc = PasteboardGetItemIdentifier(hPasteboard, 1, &idItem); + if (orc == 0) + { + /* + * Retrieve all flavors on the pasteboard, maybe there + * is something we can use. Or maybe we're the owner. + */ + CFArrayRef hFlavors = 0; + orc = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if (orc == 0) + { + CFIndex cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + { + CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if ( idItem == (PasteboardItemID)idOwnership + && hStrOwnershipFlavor + && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) + { + /* We made the changes ourselves. */ + Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); + *pfChanged = false; + *pfFormats = 0; + break; + } + + if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) + { + Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_BITMAP; + } + else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) + || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) + { + Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; + } +#ifdef WITH_HTML_H2G + else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) + { + Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_HTML; + } +#endif +#ifdef LOG_ENABLED + else if (LogIs2Enabled()) + { + if (CFStringGetCharactersPtr(hStrFlavor)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", CFStringGetCharactersPtr(hStrFlavor))); + else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", + CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); + else + Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); + } +#endif + } + + CFRelease(hFlavors); + } + else + Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", orc, orc)); + } + else + Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orc, orc)); + + if (*pfChanged) + Log(("queryNewPasteboardFormats: changed: *pfFormats=%#x\n", *pfFormats)); + } + } + else + Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orc, orc)); + return VINF_SUCCESS; +} + +/** + * Read content from the host clipboard and write it to the internal clipboard + * structure for further processing. + * + * @param pPasteboard Reference to the global pasteboard. + * @param fFormat The format type which should be read. + * @param pv The destination buffer. + * @param cb The size of the destination buffer. + * @param pcbActual The size which is needed to transfer the content. + * + * @returns IPRT status code. + */ +int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual) +{ + Log(("readFromPasteboard: fFormat = %02X\n", fFormat)); + + /* Make sure all is in sync */ + PasteboardSynchronize(pPasteboard); + + /* Are some items in the pasteboard? */ + ItemCount cItems; + OSStatus orc = PasteboardGetItemCount(pPasteboard, &cItems); + if (cItems < 1) + return VINF_SUCCESS; + + /* + * Our default response... + */ + int rc = VERR_NOT_SUPPORTED; + + /* + * The id of the first element in the pasteboard + */ + PasteboardItemID idItem; + orc = PasteboardGetItemIdentifier(pPasteboard, 1, &idItem); + if (orc == 0) + { + CFDataRef hDataCopy = 0; + size_t cbDataCopy = 0; + + /* + * The guest request unicode + */ + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + PRTUTF16 pwszSrcFree = NULL; + PCRTUTF16 pwszSrc = NULL; + size_t cwcSrc = 0; + + /* First preference is plain UTF-16 text: */ + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeUTF16PlainText, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is utf-16 (%zu bytes)\n", cbDataCopy)); + pwszSrc = (PCRTUTF16)CFDataGetBytePtr(hDataCopy); + if (pwszSrc) + { + cwcSrc = RTUtf16NLen(pwszSrc, cbDataCopy / sizeof(RTUTF16)); + if (cwcSrc >= cbDataCopy / sizeof(RTUTF16)) + { + pwszSrcFree = RTUtf16Alloc((cwcSrc + 1) * sizeof(RTUTF16)); + if (pwszSrcFree) + { + memcpy(pwszSrcFree, pwszSrc, cwcSrc * sizeof(RTUTF16)); + pwszSrcFree[cwcSrc] = '\0'; + pwszSrc = pwszSrcFree; + } + else + { + rc = VERR_NO_UTF16_MEMORY; + pwszSrc = NULL; + } + } + } + else + rc = VERR_GENERAL_FAILURE; + } + /* Second preference is plain UTF-8 text: */ + else + { + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeUTF8PlainText, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("readFromPasteboard: clipboard content is utf-8 (%zu bytes)\n", cbDataCopy)); + const char *pszSrc = (const char *)CFDataGetBytePtr(hDataCopy); + if (pszSrc) + { + size_t cchSrc = RTStrNLen(pszSrc, cbDataCopy); + rc = RTStrToUtf16Ex(pszSrc, cchSrc, &pwszSrcFree, 0, &cwcSrc); + if (RT_SUCCESS(rc)) + pwszSrc = pwszSrcFree; + } + else + rc = VERR_GENERAL_FAILURE; + } + } + if (pwszSrc) + { + /* + * Convert to windows UTF-16. + */ + Assert(cwcSrc == RTUtf16Len(pwszSrc)); + size_t cwcDst = 0; + rc = ShClUtf16LFLenUtf8(pwszSrc, cwcSrc, &cwcDst); + if (RT_SUCCESS(rc)) + { + cwcDst++; /* Add space for terminator. */ + + *pcbActual = cwcDst * sizeof(RTUTF16); + if (*pcbActual <= cb) + { + rc = ShClConvUtf16LFToCRLF(pwszSrc, cwcSrc, (PRTUTF16)pv, cb / sizeof(RTUTF16)); + if (RT_SUCCESS(rc)) + { +#ifdef SHOW_CLIPBOARD_CONTENT + Log(("readFromPasteboard: clipboard content: %ls\n", (PCRTUTF16)pv)); +#endif + } + else + { + Log(("readFromPasteboard: ShClUtf16LinToWin failed - %Rrc!\n", rc)); + AssertRC(rc); + } + } + else + { + Log(("readFromPasteboard: Insufficient (text) buffer space: %#zx, need %#zx\n", cb, *pcbActual)); + rc = VINF_SUCCESS; + } + } + else + { + Log(("readFromPasteboard: ShClUtf16GetWinSize failed - %Rrc!\n", rc)); + AssertRC(rc); + } + RTUtf16Free(pwszSrcFree); + } + } + /* + * The guest request BITMAP + */ + else if (fFormat & VBOX_SHCL_FMT_BITMAP) + { + /* Get the BMP data from the pasteboard */ + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeBMP, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is BMP (%zu bytes)\n", cbDataCopy)); + const void *pvSrc = CFDataGetBytePtr(hDataCopy); + if (pvSrc) + { + /* + * Try get the device independent bitmap (DIB) bit from it. + */ + const void *pvDib; + size_t cbDib; + rc = ShClBmpGetDib(pvSrc, cbDataCopy, &pvDib, &cbDib); + if (RT_SUCCESS(rc)) + { + *pcbActual = cbDib; + if (*pcbActual <= cb) + { + memcpy(pv, pvDib, cbDib); +#ifdef SHOW_CLIPBOARD_CONTENT + Log(("readFromPasteboard: clipboard content bitmap %zx bytes\n", cbDib)); +#endif + } + else + Log(("readFromPasteboard: Insufficient (bitmap) buffer space: %#zx, need %#zx\n", cb, cbDib)); + rc = VINF_SUCCESS; + } + else + { + AssertRC(rc); + Log(("readFromPasteboard: ShClBmpGetDib failed - %Rrc - unknown bitmap format??\n", rc)); + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = VERR_GENERAL_FAILURE; + } + else + LogFlow(("readFromPasteboard: PasteboardCopyItemFlavorData/kUTTypeBMP -> %d (%#x)\n", orc, orc)); + } +#ifdef WITH_HTML_H2G + /* + * The guest request HTML. It expects a UTF-8 reply and we assume + * that's what's on the pasteboard too. + */ + else if (fFormat & VBOX_SHCL_FMT_HTML) + { + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeHTML, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is HTML (%zu bytes):\n", cbDataCopy)); + const char *pszSrc = (const char *)CFDataGetBytePtr(hDataCopy); + if (pszSrc) + { + Log3(("%.*Rhxd\n", cbDataCopy, pszSrc)); + rc = RTStrValidateEncodingEx(pszSrc, cbDataCopy, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + size_t cchSrc = RTStrNLen(pszSrc, cbDataCopy); + *pcbActual = cchSrc; + if (cchSrc <= cb) + memcpy(pv, pszSrc, cchSrc); + else + Log(("readFromPasteboard: Insufficient (HTML) buffer space: %#zx, need %#zx\n", cb, cchSrc)); + rc = VINF_SUCCESS; + } + else + { + Log(("readFromPasteboard: Invalid UTF-8 encoding on pasteboard: %Rrc\n", rc)); + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = VERR_GENERAL_FAILURE; + } + else + LogFlow(("readFromPasteboard: PasteboardCopyItemFlavorData/kUTTypeHTML -> %d (%#x)\n", orc, orc)); + } +#endif + else + { + Log2(("readFromPasteboard: Unsupported format: %#x\n", fFormat)); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Release the data copy, if we got one. There are no returns above! + */ + if (hDataCopy) + CFRelease(hDataCopy); + } + else + { + Log(("readFromPasteboard: PasteboardGetItemIdentifier failed: %u (%#x)\n", orc, orc)); + rc = VERR_NOT_SUPPORTED; + } + + Log(("readFromPasteboard: rc=%Rrc *pcbActual=%#zx\n", rc, *pcbActual)); + return rc; +} + +/** + * Takes the ownership of the pasteboard. + * + * This is called when the other end reports available formats. + * + * @returns VBox status code. + * @param hPasteboard The pastboard handle (reference). + * @param idOwnership The ownership ID to use now. + * @param pszOwnershipFlavor The ownership indicator flavor + * @param pszOwnershipValue The ownership value (stringified format mask). + * @param phStrOwnershipFlavor Pointer to a CFStringRef variable holding + * the current ownership flavor string. This + * will always be released, and set again on + * success. + * + * @todo Add fFormats so we can make promises about available formats at once + * without needing to request any data first. That might help on + * flavor priority. + */ +int takePasteboardOwnership(PasteboardRef hPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, + const char *pszOwnershipValue, void **phStrOwnershipFlavor) +{ + /* + * Release the old string. + */ + if (*phStrOwnershipFlavor) + { + CFStringRef hOldFlavor = (CFStringRef)*phStrOwnershipFlavor; + CFRelease(hOldFlavor); + *phStrOwnershipFlavor = NULL; + } + + /* + * Clear the pasteboard and take ownership over it. + */ + OSStatus orc = PasteboardClear(hPasteboard); + if (orc == 0) + { + /* For good measure. */ + PasteboardSynchronize(hPasteboard); + + /* + * Put the ownership flavor and value onto the clipboard. + */ + CFDataRef hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszOwnershipValue, strlen(pszOwnershipValue)); + if (hData) + { + CFStringRef hFlavor = CFStringCreateWithCString(kCFAllocatorDefault, pszOwnershipFlavor, kCFStringEncodingUTF8); + if (hFlavor) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + hFlavor, hData, kPasteboardFlavorNoFlags); + if (orc == 0) + { + *phStrOwnershipFlavor = (void *)hFlavor; + Log(("takePasteboardOwnership: idOwnership=%RX64 flavor=%s value=%s\n", + idOwnership, pszOwnershipFlavor, pszOwnershipValue)); + } + else + { + Log(("takePasteboardOwnership: PasteboardPutItemFlavor -> %d (%#x)!\n", orc, orc)); + CFRelease(hFlavor); + } + } + else + Log(("takePasteboardOwnership: CFStringCreateWithCString failed!\n")); + CFRelease(hData); + } + else + Log(("takePasteboardOwnership: CFDataCreate failed!\n")); + } + else + Log(("takePasteboardOwnership: PasteboardClear failed -> %d (%#x)\n", orc, orc)); + return orc == 0 ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +/** + * Write clipboard content to the host clipboard from the internal clipboard + * structure. + * + * @param hPasteboard Reference to the global pasteboard. + * @param idOwnership The ownership ID. + * @param pv The source buffer. + * @param cb The size of the source buffer. + * @param fFormat The format type which should be written. + * + * @returns IPRT status code. + */ +int writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnership, const void *pv, uint32_t cb, uint32_t fFormat) +{ + int rc; + OSStatus orc; + CFDataRef hData; + Log(("writeToPasteboard: fFormat=%#x\n", fFormat)); + + /* Make sure all is in sync */ + PasteboardSynchronize(hPasteboard); + + /* + * Handle the unicode text + */ + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + PCRTUTF16 const pwszSrc = (PCRTUTF16)pv; + size_t const cwcSrc = cb / sizeof(RTUTF16); + + /* + * If the other side is windows or OS/2, we may have to convert + * '\r\n' -> '\n' and the drop ending marker. + */ + + /* How long will the converted text be? */ + size_t cwcDst = 0; + rc = ShClUtf16CRLFLenUtf8(pwszSrc, cwcSrc, &cwcDst); + AssertMsgRCReturn(rc, ("ShClUtf16GetLinSize failed: %Rrc\n", rc), rc); + + /* Ignore empty strings? */ /** @todo r=andy Really? Why? */ + if (cwcDst == 0) + { + Log(("writeToPasteboard: received empty string from the guest; ignoreing it.\n")); + return VINF_SUCCESS; + } + + cwcDst++; /* Add space for terminator. */ + + /* Allocate the necessary memory and do the conversion. */ + PRTUTF16 pwszDst = (PRTUTF16)RTMemAlloc(cwcDst * sizeof(RTUTF16)); + AssertMsgReturn(pwszDst, ("cwcDst=%#zx\n", cwcDst), VERR_NO_UTF16_MEMORY); + + rc = ShClConvUtf16CRLFToLF(pwszSrc, cwcSrc, pwszDst, cwcDst); + if (RT_SUCCESS(rc)) + { + /* + * Create an immutable CFData object that we can place on the clipboard. + */ + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pwszDst, cwcDst * sizeof(RTUTF16)); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeUTF16PlainText, hData, kPasteboardFlavorNoFlags); + if (orc == 0) + rc = VINF_SUCCESS; + else + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeUTF16PlainText failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF16 failed!\n")); + rc = VERR_NO_MEMORY; + } + + /* + * Now for the UTF-8 version. + */ + char *pszDst; + int rc2 = RTUtf16ToUtf8(pwszDst, &pszDst); + if (RT_SUCCESS(rc2)) + { + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszDst, strlen(pszDst)); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeUTF8PlainText, hData, kPasteboardFlavorNoFlags); + if (orc != 0) + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeUTF8PlainText failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF8 failed!\n")); + rc = VERR_NO_MEMORY; + } + RTStrFree(pszDst); + } + else + rc = rc2; + } + else + Log(("writeToPasteboard: clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc)); + + RTMemFree(pwszDst); + } + /* + * Handle the bitmap. We convert the DIB to a bitmap and put it on + * the pasteboard using the BMP flavor. + */ + else if (fFormat & VBOX_SHCL_FMT_BITMAP) + { + /* Create a full BMP from it */ + void *pvBmp; + size_t cbBmp; + rc = ShClDibToBmp(pv, cb, &pvBmp, &cbBmp); + if (RT_SUCCESS(rc)) + { + hData = CFDataCreate(kCFAllocatorDefault, (UInt8 const *)pvBmp, cbBmp); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeBMP, hData, kPasteboardFlavorNoFlags); + if (orc != 0) + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeBMP failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF8 failed!\n")); + rc = VERR_NO_MEMORY; + } + RTMemFree(pvBmp); + } + } +#ifdef WITH_HTML_G2H + /* + * Handle HTML. Expect UTF-8, ignore line endings and just put it + * straigh up on the pasteboard for now. + */ + else if (fFormat & VBOX_SHCL_FMT_HTML) + { + const char *pszSrc = (const char *)pv; + size_t const cchSrc = RTStrNLen(pszSrc, cb); + rc = RTStrValidateEncodingEx(pszSrc, cchSrc, 0); + if (RT_SUCCESS(rc)) + { + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszSrc, cchSrc); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, kUTTypeHTML, + hData, kPasteboardFlavorNoFlags); + if (orc == 0) + rc = VINF_SUCCESS; + else + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeHTML failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/HTML failed!\n")); + rc = VERR_NO_MEMORY; + } + } + else + Log(("writeToPasteboard: HTML: Invalid UTF-8 encoding: %Rrc\n", rc)); + } +#endif + else + rc = VERR_NOT_IMPLEMENTED; + + Log(("writeToPasteboard: rc=%Rrc\n", rc)); + return rc; +} + diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h new file mode 100644 index 00000000..38238d0c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h @@ -0,0 +1,47 @@ +/* $Id: darwin-pasteboard.h $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Copyright (C) 2008-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h +#define VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +typedef struct OpaquePasteboardRef *PasteboardRef; + +int initPasteboard(PasteboardRef *pPasteboardRef); +void destroyPasteboard(PasteboardRef *pPasteboardRef); + +int queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, + uint32_t *pfFormats, bool *pfChanged); +int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual); +int takePasteboardOwnership(PasteboardRef pPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, + const char *pszOwnershipValue, void **phStrOwnershipFlavor); +int writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnership, void const *pv, uint32_t cb, uint32_t fFormat); + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings new file mode 100644 index 00000000..9fe67d17 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the host HGCM services. +# + +# +# Copyright (C) 2019-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 +# + +--filter-out-files *.txt +/*.h: --guard-relative-to-dir . diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk new file mode 100644 index 00000000..66160a26 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -0,0 +1,147 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service testcases. +# + +# +# 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 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + # + # Testcase which mocks HGCM to also test the VbglR3-side of Shared Clipboard. + # + # Goal is to use and test as much guest side code as possible as a self-contained + # binary on the host here. + # + # Note: No #ifdef TESTCASE hacks or similar allowed, has to run + # without #ifdef modifications to the core code! + # + tstClipboardMockHGCM_TEMPLATE = VBoxR3TstExe + tstClipboardMockHGCM_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD + tstClipboardMockHGCM_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardMockHGCM.cpp + tstClipboardMockHGCM_LIBS = $(LIB_RUNTIME) + + if1of ($(KBUILD_TARGET), linux solaris) + PROGRAMS += tstClipboardMockHGCM + tstClipboardMockHGCM_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ + ../VBoxSharedClipboardSvc-x11.cpp + tstClipboardMockHGCM_LIBPATH = \ + $(VBOX_LIBPATH_X11) + tstClipboardMockHGCM_LIBS += \ + Xt \ + X11 + endif + if1of ($(KBUILD_TARGET), win) + PROGRAMS += tstClipboardMockHGCM + tstClipboardMockHGCM_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ + ../VBoxSharedClipboardSvc-win.cpp + endif + + tstClipboardMockHGCM_CLEAN = $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + tstClipboardMockHGCM_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMockHGCM_SOURCES += \ + ../VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + endif + + if 0 # Enable this if you want automatic runs after compilation. + $$(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run: $$(tstClipboardMockHGCM_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardMockHGCM_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + OTHERS += $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run + endif + + # + # + # + PROGRAMS += tstClipboardServiceHost + tstClipboardServiceHost_TEMPLATE = VBoxR3TstExe + tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST + tstClipboardServiceHost_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardServiceHost.cpp + tstClipboardServiceHost_LIBS = $(LIB_RUNTIME) + tstClipboardServiceHost_CLEAN = $(tstClipboardServiceHost_0_OUTDIR)/tstClipboardServiceHost.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardServiceHost_SOURCES += \ + ../VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + endif + + # + # + # + PROGRAMS += tstClipboardServiceImpl + tstClipboardServiceImpl_TEMPLATE = VBoxR3TstExe + tstClipboardServiceImpl_DEFS = VBOX_WITH_HGCM UNIT_TEST + tstClipboardServiceImpl_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardServiceImpl.cpp + tstClipboardServiceImpl_SOURCES.win = \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp + tstClipboardServiceImpl_LIBS = $(LIB_RUNTIME) + tstClipboardServiceImpl_CLEAN = $(tstClipboardServiceImpl_0_OUTDIR)/tstClipboardServiceImpl.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + # + # + # + PROGRAMS += tstClipboardTransfers + tstClipboardTransfers_TEMPLATE = VBoxR3TstExe + tstClipboardTransfers_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardTransfers_SOURCES = \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + tstClipboardTransfers.cpp + endif +endif + +# +# List of above testcases that will be included in the ValKit. +# +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + if1of ($(KBUILD_TARGET), linux solaris win) + VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS += \ + tstClipboardMockHGCM + endif +endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h new file mode 100644 index 00000000..35791604 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h @@ -0,0 +1,165 @@ +/* $Id: VBoxOrgCfHtml1.h $ */ +/** @file + * Shared Clipboard host service test case C data file of VBoxOrgCfHtml1.txt. + */ + +/* + * Copyright (C) 2022-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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h +#define VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +const unsigned char g_abVBoxOrgCfHtml1[] = +{ + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x30, 0x2e, 0x39, 0x0d, 0x0a, 0x53, 0x74, 0x61, /* 0x00000000: Version:0.9..Sta */ + 0x72, 0x74, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, /* 0x00000010: rtHTML:000000014 */ + 0x34, 0x0d, 0x0a, 0x45, 0x6e, 0x64, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, /* 0x00000020: 4..EndHTML:00000 */ + 0x30, 0x31, 0x39, 0x36, 0x31, 0x0d, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, /* 0x00000030: 01961..StartFrag */ + 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0x30, 0x0d, /* 0x00000040: ment:0000000180. */ + 0x0a, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, /* 0x00000050: .EndFragment:000 */ + 0x30, 0x30, 0x30, 0x31, 0x39, 0x32, 0x35, 0x0d, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, /* 0x00000060: 0001925..SourceU */ + 0x52, 0x4c, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000070: RL:https:..www.v */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x0d, 0x0a, /* 0x00000080: irtualbox.org... */ + 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0d, 0x0a, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0d, 0x0a, /* 0x00000090: <html>..<body>.. */ + 0x3c, 0x21, 0x2d, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, /* 0x000000a0: <!--StartFragmen */ + 0x74, 0x2d, 0x2d, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, /* 0x000000b0: t--><span style= */ + 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, /* 0x000000c0: "color: rgb(0, 0 */ + 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, /* 0x000000d0: , 0); font-famil */ + 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, /* 0x000000e0: y: Verdana, &quo */ + 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, /* 0x000000f0: t;Bitstream Vera */ + 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, /* 0x00000100: Sans", san */ + 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, /* 0x00000110: s-serif; font-si */ + 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, /* 0x00000120: ze: 13px; font-s */ + 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000130: tyle: normal; fo */ + 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, /* 0x00000140: nt-variant-ligat */ + 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000150: ures: normal; fo */ + 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, /* 0x00000160: nt-variant-caps: */ + 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, /* 0x00000170: normal; font-we */ + 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, /* 0x00000180: ight: 400; lette */ + 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, /* 0x00000190: r-spacing: norma */ + 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, /* 0x000001a0: l; orphans: 2; t */ + 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, /* 0x000001b0: ext-align: start */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, /* 0x000001c0: ; text-indent: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, /* 0x000001d0: px; text-transfo */ + 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, /* 0x000001e0: rm: none; white- */ + 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, /* 0x000001f0: space: normal; w */ + 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, /* 0x00000200: idows: 2; word-s */ + 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, /* 0x00000210: pacing: 0px; -we */ + 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, /* 0x00000220: bkit-text-stroke */ + 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, /* 0x00000230: -width: 0px; bac */ + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000240: kground-color: r */ + 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, /* 0x00000250: gb(255, 255, 255 */ + 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x00000260: ); text-decorati */ + 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000270: on-thickness: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x00000280: itial; text-deco */ + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000290: ration-style: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x000002a0: itial; text-deco */ + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, /* 0x000002b0: ration-color: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, /* 0x000002c0: itial; display: */ + 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, /* 0x000002d0: inline !importan */ + 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, /* 0x000002e0: t; float: none;" */ + 0x3e, 0x53, 0x65, 0x65, 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, /* 0x000002f0: >See "<.span><a */ + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, /* 0x00000300: class="wiki" hre */ + 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000310: f="https:..www.v */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, /* 0x00000320: irtualbox.org.wi */ + 0x6b, 0x69, 0x2f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, /* 0x00000330: ki.VirtualBox" s */ + 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000340: tyle="text-decor */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, /* 0x00000350: ation: none; col */ + 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, /* 0x00000360: or: rgb(0, 0, 19 */ + 0x32, 0x29, 0x3b, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, /* 0x00000370: 2); border-botto */ + 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000380: m: none; font-fa */ + 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x00000390: mily: Verdana, & */ + 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000003a0: quot;Bitstream V */ + 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000003b0: era Sans", */ + 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000003c0: sans-serif; font */ + 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000003d0: -size: 13px; fon */ + 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000003e0: t-style: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x000003f0: font-variant-li */ + 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000400: gatures: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000410: font-variant-ca */ + 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000420: ps: normal; font */ + 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000430: -weight: 400; le */ + 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000440: tter-spacing: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000450: rmal; orphans: 2 */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000460: ; text-align: st */ + 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000470: art; text-indent */ + 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000480: : 0px; text-tran */ + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x00000490: sform: none; whi */ + 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000004a0: te-space: normal */ + 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000004b0: ; widows: 2; wor */ + 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004c0: d-spacing: 0px; */ + 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000004d0: -webkit-text-str */ + 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004e0: oke-width: 0px; */ + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x000004f0: background-color */ + 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000500: : rgb(255, 255, */ + 0x32, 0x35, 0x35, 0x29, 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, /* 0x00000510: 255);">About Vir */ + 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, /* 0x00000520: tualBox<.a><span */ + 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000530: style="color: r */ + 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000540: gb(0, 0, 0); fon */ + 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, /* 0x00000550: t-family: Verdan */ + 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, /* 0x00000560: a, "Bitstre */ + 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, /* 0x00000570: am Vera Sans&quo */ + 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, /* 0x00000580: t;, sans-serif; */ + 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, /* 0x00000590: font-size: 13px; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005a0: font-style: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005b0: mal; font-varian */ + 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005c0: t-ligatures: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005d0: mal; font-varian */ + 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, /* 0x000005e0: t-caps: normal; */ + 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, /* 0x000005f0: font-weight: 400 */ + 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, /* 0x00000600: ; letter-spacing */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, /* 0x00000610: : normal; orphan */ + 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, /* 0x00000620: s: 2; text-align */ + 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, /* 0x00000630: : start; text-in */ + 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x00000640: dent: 0px; text- */ + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, /* 0x00000650: transform: none; */ + 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000660: white-space: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, /* 0x00000670: rmal; widows: 2; */ + 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, /* 0x00000680: word-spacing: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, /* 0x00000690: px; -webkit-text */ + 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, /* 0x000006a0: -stroke-width: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, /* 0x000006b0: px; background-c */ + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, /* 0x000006c0: olor: rgb(255, 2 */ + 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x000006d0: 55, 255); text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, /* 0x000006e0: ecoration-thickn */ + 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x000006f0: ess: initial; te */ + 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, /* 0x00000700: xt-decoration-st */ + 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x00000710: yle: initial; te */ + 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, /* 0x00000720: xt-decoration-co */ + 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, /* 0x00000730: lor: initial; di */ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, /* 0x00000740: splay: inline !i */ + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, /* 0x00000750: mportant; float: */ + 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, /* 0x00000760: none;">" for an */ + 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, /* 0x00000770: introduction.<. */ + 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x21, 0x2d, 0x2d, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, /* 0x00000780: span><!--EndFrag */ + 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x2d, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, /* 0x00000790: ment-->..<.body> */ + 0x0d, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x00, /* 0x000007a0: ..<.html>. */ +}; + +const unsigned g_cbVBoxOrgCfHtml1 = sizeof(g_abVBoxOrgCfHtml1); + +#endif /* !VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt Binary files differnew file mode 100644 index 00000000..39a83822 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h new file mode 100644 index 00000000..a2a0fe41 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h @@ -0,0 +1,152 @@ +/* $Id: VBoxOrgMimeHtml1.h $ */ +/** @file + * Shared Clipboard host service test case C data file of VBoxOrgMimeHtml1.txt. + */ + +/* + * Copyright (C) 2022-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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h +#define VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +const unsigned char g_abVBoxOrgMimeHtml1[] = +{ + 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, /* 0x00000000: <span style="col */ + 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, /* 0x00000010: or: rgb(0, 0, 0) */ + 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, /* 0x00000020: ; font-family: V */ + 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, /* 0x00000030: erdana, "Bi */ + 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, /* 0x00000040: tstream Vera San */ + 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, /* 0x00000050: s", sans-se */ + 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, /* 0x00000060: rif; font-size: */ + 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000070: 13px; font-style */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x00000080: : normal; font-v */ + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, /* 0x00000090: ariant-ligatures */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x000000a0: : normal; font-v */ + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000000b0: ariant-caps: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, /* 0x000000c0: mal; font-weight */ + 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, /* 0x000000d0: : 400; letter-sp */ + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, /* 0x000000e0: acing: normal; o */ + 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x000000f0: rphans: 2; text- */ + 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, /* 0x00000100: align: start; te */ + 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x00000110: xt-indent: 0px; */ + 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, /* 0x00000120: text-transform: */ + 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, /* 0x00000130: none; white-spac */ + 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, /* 0x00000140: e: normal; widow */ + 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, /* 0x00000150: s: 2; word-spaci */ + 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, /* 0x00000160: ng: 0px; -webkit */ + 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, /* 0x00000170: -text-stroke-wid */ + 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, /* 0x00000180: th: 0px; backgro */ + 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, /* 0x00000190: und-color: rgb(2 */ + 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, /* 0x000001a0: 55, 255, 255); t */ + 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, /* 0x000001b0: ext-decoration-t */ + 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001c0: hickness: initia */ + 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001d0: l; text-decorati */ + 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001e0: on-style: initia */ + 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001f0: l; text-decorati */ + 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x00000200: on-color: initia */ + 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, /* 0x00000210: l; display: inli */ + 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, /* 0x00000220: ne !important; f */ + 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x53, 0x65, 0x65, /* 0x00000230: loat: none;">See */ + 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, /* 0x00000240: "<.span><a clas */ + 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3d, 0x22, 0x68, /* 0x00000250: s="wiki" href="h */ + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x72, 0x74, 0x75, /* 0x00000260: ttps:..www.virtu */ + 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, 0x6b, 0x69, 0x2f, 0x56, /* 0x00000270: albox.org.wiki.V */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000280: irtualBox" style */ + 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, /* 0x00000290: ="text-decoratio */ + 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, /* 0x000002a0: n: none; color: */ + 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, 0x32, 0x29, 0x3b, 0x20, /* 0x000002b0: rgb(0, 0, 192); */ + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x6e, /* 0x000002c0: border-bottom: n */ + 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, /* 0x000002d0: one; font-family */ + 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, /* 0x000002e0: : Verdana, " */ + 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, /* 0x000002f0: ;Bitstream Vera */ + 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, /* 0x00000300: Sans", sans */ + 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, /* 0x00000310: -serif; font-siz */ + 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, /* 0x00000320: e: 13px; font-st */ + 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000330: yle: normal; fon */ + 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, /* 0x00000340: t-variant-ligatu */ + 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000350: res: normal; fon */ + 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, /* 0x00000360: t-variant-caps: */ + 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, /* 0x00000370: normal; font-wei */ + 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, /* 0x00000380: ght: 400; letter */ + 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x00000390: -spacing: normal */ + 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, /* 0x000003a0: ; orphans: 2; te */ + 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, /* 0x000003b0: xt-align: start; */ + 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, /* 0x000003c0: text-indent: 0p */ + 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, /* 0x000003d0: x; text-transfor */ + 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, /* 0x000003e0: m: none; white-s */ + 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, /* 0x000003f0: pace: normal; wi */ + 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, /* 0x00000400: dows: 2; word-sp */ + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, /* 0x00000410: acing: 0px; -web */ + 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, /* 0x00000420: kit-text-stroke- */ + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, /* 0x00000430: width: 0px; back */ + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, /* 0x00000440: ground-color: rg */ + 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, /* 0x00000450: b(255, 255, 255) */ + 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, /* 0x00000460: ;">About Virtual */ + 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, /* 0x00000470: Box<.a><span sty */ + 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, /* 0x00000480: le="color: rgb(0 */ + 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000490: , 0, 0); font-fa */ + 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x000004a0: mily: Verdana, & */ + 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000004b0: quot;Bitstream V */ + 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000004c0: era Sans", */ + 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000004d0: sans-serif; font */ + 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000004e0: -size: 13px; fon */ + 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000004f0: t-style: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x00000500: font-variant-li */ + 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000510: gatures: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000520: font-variant-ca */ + 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000530: ps: normal; font */ + 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000540: -weight: 400; le */ + 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000550: tter-spacing: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000560: rmal; orphans: 2 */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000570: ; text-align: st */ + 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000580: art; text-indent */ + 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000590: : 0px; text-tran */ + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x000005a0: sform: none; whi */ + 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000005b0: te-space: normal */ + 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000005c0: ; widows: 2; wor */ + 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005d0: d-spacing: 0px; */ + 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000005e0: -webkit-text-str */ + 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005f0: oke-width: 0px; */ + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x00000600: background-color */ + 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000610: : rgb(255, 255, */ + 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000620: 255); text-decor */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, /* 0x00000630: ation-thickness: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000640: initial; text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, /* 0x00000650: ecoration-style: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000660: initial; text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, /* 0x00000670: ecoration-color: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, /* 0x00000680: initial; displa */ + 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, /* 0x00000690: y: inline !impor */ + 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, /* 0x000006a0: tant; float: non */ + 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x74, /* 0x000006b0: e;">" for an int */ + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, /* 0x000006c0: roduction.<.span */ + 0x3e, 0x00 /* 0x000006d0: > */ +}; + +const unsigned g_cbVBoxOrgMimeHtml1 = sizeof(g_abVBoxOrgMimeHtml1); + +#endif /* !VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt new file mode 100644 index 00000000..6d5b4901 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt @@ -0,0 +1 @@ +<span style="color: rgb(0, 0, 0); font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">See "</span><a class="wiki" href="https://www.virtualbox.org/wiki/VirtualBox" style="text-decoration: none; color: rgb(0, 0, 192); border-bottom: none; font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255);">About VirtualBox</a><span style="color: rgb(0, 0, 0); font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">" for an introduction.</span>
\ No newline at end of file diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp new file mode 100644 index 00000000..ec35905c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp @@ -0,0 +1,735 @@ +/* $Id: tstClipboardMockHGCM.cpp $ */ +/** @file + * Shared Clipboard host service test case. + */ + +/* + * 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 + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/VBoxGuestLib.h> +#ifdef RT_OS_LINUX +# include <VBox/GuestHost/SharedClipboard-x11.h> +#endif +#ifdef RT_OS_WINDOWS +# include <VBox/GuestHost/SharedClipboard-win.h> +#endif + +#include <VBox/GuestHost/HGCMMock.h> +#include <VBox/GuestHost/HGCMMockUtils.h> + +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Static globals * +*********************************************************************************************************************************/ +static RTTEST g_hTest; + + +/********************************************************************************************************************************* +* Shared Clipboard testing * +*********************************************************************************************************************************/ +struct CLIPBOARDTESTDESC; +/** Pointer to a test description. */ +typedef CLIPBOARDTESTDESC *PTESTDESC; + +struct CLIPBOARDTESTCTX; +/** Pointer to a test context. */ +typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX; + +/** Pointer a test descriptor. */ +typedef CLIPBOARDTESTDESC *PTESTDESC; + +typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)); +/** Pointer to a test setup callback. */ +typedef FNTESTSETUP *PFNTESTSETUP; + +typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); +/** Pointer to a test exec callback. */ +typedef FNTESTEXEC *PFNTESTEXEC; + +typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); +/** Pointer to a test destroy callback. */ +typedef FNTESTDESTROY *PFNTESTDESTROY; + + +/** + * Structure for keeping a clipboard test task. + */ +typedef struct CLIPBOARDTESTTASK +{ + SHCLFORMATS enmFmtHst; + SHCLFORMATS enmFmtGst; + /** For testing chunked reads / writes. */ + size_t cbChunk; + /** Data buffer to read / write for this task. + * Can be NULL if not needed. */ + void *pvData; + /** Size (in bytes) of \a pvData. */ + size_t cbData; + /** Number of bytes read / written from / to \a pvData. */ + size_t cbProcessed; +} CLIPBOARDTESTTASK; +typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK; + +/** + * Structure for keeping a clipboard test context. + */ +typedef struct CLIPBOARDTESTCTX +{ + /** The HGCM Mock utils context. */ + TSTHGCMUTILSCTX HGCM; + /** Clipboard-specific task data. */ + CLIPBOARDTESTTASK Task; + struct + { + /** The VbglR3 Shared Clipboard context to work on. */ + VBGLR3SHCLCMDCTX CmdCtx; + } Guest; +} CLIPBOARDTESTCTX; + +/** The one and only clipboard test context. One at a time. */ +CLIPBOARDTESTCTX g_TstCtx; + +/** + * Structure for keeping a clipboard test description. + */ +typedef struct CLIPBOARDTESTDESC +{ + /** The setup callback. */ + PFNTESTSETUP pfnSetup; + /** The exec callback. */ + PFNTESTEXEC pfnExec; + /** The destruction callback. */ + PFNTESTDESTROY pfnDestroy; +} CLIPBOARDTESTDESC; + +typedef struct SHCLCONTEXT +{ +} SHCLCONTEXT; + + +static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected) +{ + VBOXHGCMSVCPARM aParms[2]; + HGCMSvcSetU32(&aParms[0], uMode); + int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms); + RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2); + if (RT_SUCCESS(rcExpected)) + { + uint32_t const uModeRet = ShClSvcGetMode(); + RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE); + } + return rc2; +} + +static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode) +{ + return tstSetModeRc(pSvc, uMode, VINF_SUCCESS); +} + +static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected) +{ + RT_NOREF(pSvc); + RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false); + return true; +} + +static void tstOperationModes(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + uint32_t u32Mode; + int rc; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU64(&parms[0], 99); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST); + tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED); + tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +static void testSetTransferMode(void) +{ + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Invalid parameter. */ + VBOXHGCMSVCPARM parms[2]; + HGCMSvcSetU64(&parms[0], 99); + int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + /* Invalid mode. */ + HGCMSvcSetU32(&parms[0], 99); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + + /* Enable transfers. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* Disable transfers again. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +static void testGuestSimple(void) +{ + RTTestISub("Testing client (guest) API - Simple"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Preparations. */ + VBGLR3SHCLCMDCTX Ctx; + RT_ZERO(Ctx); + + /* + * Multiple connects / disconnects. + */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); + /* Report bogus guest features while connecting. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef)); + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); + + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); + + /* + * Feature tests. + */ + + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */)); + /* Report bogus features to the host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */)); + + /* + * Access denied tests. + */ + + /* Try reading data from host. */ + uint8_t abData[32]; uint32_t cbIgnored; + RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, + abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED); + /* Try writing data without reporting formats before (legacy). */ + RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); + /* Try writing data without reporting formats before. */ + RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); + /* Report bogus formats to the host. */ + RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED); + /* Report supported formats to host. */ + RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, + VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML), + VERR_ACCESS_DENIED); + /* + * Access allowed tests. + */ + tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); + + /* Try writing data without reporting formats before. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData))); + /* Try reading data from host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, + abData, sizeof(abData), &cbIgnored)); + /* Report bogus formats to the host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f)); + /* Report supported formats to host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, + VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML)); + /* Tear down. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); +} + +static RTUTF16 tstGetRandUtf8(void) +{ + return RTRandU32Ex(0x20, 0x7A); +} + +static char *tstGenerateUtf8StringA(uint32_t uCch) +{ + char * pszRand = (char *)RTMemAlloc(uCch + 1); + for (uint32_t i = 0; i < uCch; i++) + pszRand[i] = tstGetRandUtf8(); + pszRand[uCch] = 0; + return pszRand; +} + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +static RTUTF16 tstGetRandUtf16(void) +{ + RTUTF16 wc; + do + { + wc = (RTUTF16)RTRandU32Ex(1, 0xfffd); + } while (wc >= 0xd800 && wc <= 0xdfff); + return wc; +} + +static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch) +{ + PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16)); + for (uint32_t i = 0; i < uCch; i++) + pwszRand[i] = tstGetRandUtf16(); + pwszRand[uCch] = 0; + return pwszRand; +} +#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */ + +static void testSetHeadless(void) +{ + RTTestISub("Testing HOST_FN_SET_HEADLESS"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + VBOXHGCMSVCPARM parms[2]; + HGCMSvcSetU32(&parms[0], false); + int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + bool fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU64(&parms[0], 99); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&parms[0], true); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + HGCMSvcSetU32(&parms[0], 99); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); +} + +static void testHostCall(void) +{ + tstOperationModes(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + testSetTransferMode(); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + testSetHeadless(); +} + + +/********************************************************************************************************************************* + * Test: Guest reading from host * + ********************************************************************************************************************************/ +#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) +/* Called from SHCLX11 thread. */ +static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pCtx, fFormats, pvUser); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats); + return VINF_SUCCESS; +} + +/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */ +static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx, + SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser) +{ + RT_NOREF(pCtx, uFmt, pvUser); + + PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser; + + void *pvData = NULL; + size_t cbData = pTask->cbData - pTask->cbProcessed; + if (cbData) + { + pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData); + AssertPtr(pvData); + } + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData); + + *ppv = pvData; + *pcb = cbData; + + return VINF_SUCCESS; +} +#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ + +typedef struct TSTUSERMOCK +{ +#if defined(RT_OS_LINUX) + SHCLX11CTX X11Ctx; +#endif + PSHCLCONTEXT pCtx; +} TSTUSERMOCK; +typedef TSTUSERMOCK *PTSTUSERMOCK; + +static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName) +{ +#if defined(RT_OS_LINUX) + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; + Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; + + pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + AssertPtrReturnVoid(pUsrMock->pCtx); + + ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false); + ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */); + /* Give the clipboard time to synchronise. */ + RTThreadSleep(500); +#else + RT_NOREF(pUsrMock, pszName); +#endif /* RT_OS_LINUX */ +} + +static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock) +{ +#if defined(RT_OS_LINUX) + ShClX11ThreadStop(&pUsrMock->X11Ctx); + ShClX11Destroy(&pUsrMock->X11Ctx); + RTMemFree(pUsrMock->pCtx); +#else + RT_NOREF(pUsrMock); +#endif +} + +static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask) +{ + size_t cbDst = RT_MAX(_64K, pTask->cbData); + uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst); + AssertPtrReturn(pabDst, VERR_NO_MEMORY); + + AssertPtr(pTask->pvData); /* Racing condition with host thread? */ + Assert(pTask->cbChunk); /* Buggy test? */ + Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */ + + size_t cbToRead = pTask->cbData; + switch (pTask->enmFmtGst) + { + case VBOX_SHCL_FMT_UNICODETEXT: +#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ + cbToRead *= sizeof(RTUTF16); +#endif + break; + + default: + break; + } + + PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx; + + /* Do random chunked reads. */ + uint32_t const cChunkedReads = RTRandU32Ex(1, 16); + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads); + for (uint32_t i = 0; i < cChunkedReads; i++) + { + /* Note! VbglR3ClipboardReadData() currently does not support chunked reads! + * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */ + + uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads)); + uint32_t cbRead = 0; + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk); + int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead); + if ( vrc2 == VINF_SUCCESS + && cbRead == 0) /* No data there yet? */ + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n"); + RTThreadSleep(10); + continue; + } + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying reading host clipboard data with a %RU32 buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead); + RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2)); + } + + /* Last read: Read the data with a buffer big enough. This must succeed. */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData); + uint32_t cbRead = 0; + int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead); + RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2)); + RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead)); + + if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT) + RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n")); + if (cbRead == cbToRead) + { +#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ + PRTUTF16 pwszSrc = NULL; + RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc))); + RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); + RTUtf16Free(pwszSrc); +#else + RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); +#endif + } + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst); + + RTMemFree(pabDst); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx) +{ + RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ + + PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx; + AssertPtr(pTstCtx); + + RT_ZERO(pTstCtx->Guest.CmdCtx); + RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID)); + + RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ + + PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser; + AssertPtr(pTstTask); + tstTestReadFromHost_DoIt(pTstCtx, pTstTask); + + /* Signal that the task ended. */ + TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS); + + RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient, + void *pvUser) +{ + RT_NOREF(pCtx, pClient); + + PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser; + AssertPtr(pTstCtx); RT_NOREF(pTstCtx); + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx) +{ + RT_NOREF(ppvCtx); + + /* Set the right clipboard mode, so that the guest can read from the host. */ + tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL); + + /* Start the host thread first, so that the guest thread can connect to it later. */ + TSTHGCMUTILSHOSTCALLBACKS HostCallbacks; + RT_ZERO(HostCallbacks); + HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback; + TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */); + + PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; + AssertPtr(pTask); + pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT; + pTask->enmFmtHst = pTask->enmFmtGst; + pTask->cbChunk = RTRandU32Ex(1, 512); + pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2); + Assert(pTask->cbData % sizeof(RTUTF16) == 0); +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) + pTask->pvData = tstGenerateUtf8StringA(pTask->cbData); + pTask->cbData++; /* Add terminating zero. */ +#else + pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */)); + pTask->cbData *= sizeof(RTUTF16); + pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */ +#endif + pTask->cbProcessed = 0; + + int rc = VINF_SUCCESS; + +#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) + /* Initialize the Shared Clipboard backend callbacks. */ + PSHCLBACKEND pBackend = ShClSvcGetBackend(); + + SHCLCALLBACKS ShClCallbacks; + RT_ZERO(ShClCallbacks); + ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; + ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; + ShClBackendSetCallbacks(pBackend, &ShClCallbacks); +#elif defined (RT_OS_WINDOWS) + rc = SharedClipboardWinOpen(GetDesktopWindow()); + if (RT_SUCCESS(rc)) + { + rc = SharedClipboardWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData); + SharedClipboardWinClose(); + } +#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData); + return rc; +} + +static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) +{ + RT_NOREF(pvCtx); + + TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx); + + PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM); + + bool fUseMock = false; + TSTUSERMOCK UsrMock; + if (fUseMock) + tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst"); + + /* Wait until the task has been finished. */ + TstHGCMUtilsTaskWait(pTask, RT_MS_30SEC); + + if (fUseMock) + tstTestReadFromHost_MockDestroy(&UsrMock); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) +{ + RT_NOREF(pvCtx); + + int vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM); + AssertRC(vrc); + vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM); + AssertRC(vrc); + + return vrc; +} + + +/********************************************************************************************************************************* + * Main * + ********************************************************************************************************************************/ + +/** Test definition table. */ +CLIPBOARDTESTDESC g_aTests[] = +{ + /* Tests guest reading clipboard data from the host. */ + { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy } +}; +/** Number of tests defined. */ +unsigned g_cTests = RT_ELEMENTS(g_aTests); + +static int tstOne(PTESTDESC pTstDesc) +{ + PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx; + + void *pvCtx; + int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx); + if (RT_SUCCESS(rc)) + { + rc = pTstDesc->pfnExec(pTstCtx, pvCtx); + + int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + +#ifndef DEBUG_andy + /* Don't let assertions in the host service panic (core dump) the test cases. */ + RTAssertSetMayPanic(false); +#endif + + PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); + TstHgcmMockSvcCreate(pSvc, sizeof(SHCLCLIENT)); + TstHgcmMockSvcStart(pSvc); + + /* + * Run the tests. + */ + if (0) + { + testGuestSimple(); + testHostCall(); + } + + RT_ZERO(g_TstCtx); + + PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM; + TstHGCMUtilsCtxInit(pCtx, pSvc); + + PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx); + TstHGCMUtilsTaskInit(pTask); + pTask->pvUser = &g_TstCtx.Task; + + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + tstOne(&g_aTests[i]); + + TstHGCMUtilsTaskDestroy(pTask); + + TstHgcmMockSvcStop(pSvc); + TstHgcmMockSvcDestroy(pSvc); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp new file mode 100644 index 00000000..28130531 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -0,0 +1,336 @@ +/* $Id: tstClipboardServiceHost.cpp $ */ +/** @file + * Shared Clipboard host service test case. + */ + +/* + * 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 + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/test.h> + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); + +static SHCLCLIENT g_Client; +static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static int setupTable(VBOXHGCMSVCFNTABLE *pTable) +{ + pTable->cbSize = sizeof(*pTable); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + g_Helpers.pfnCallComplete = callComplete; + pTable->pHelpers = &g_Helpers; + return VBoxHGCMSvcLoad(pTable); +} + +static void testSetMode(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + uint32_t u32Mode; + int rc; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_HOST_TO_GUEST); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_HOST_TO_GUEST, ("u32Mode=%u\n", (unsigned) u32Mode)); + + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); + + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + table.pfnUnload(NULL); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +static void testSetTransferMode(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); + int rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + /* Invalid parameter. */ + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + /* Invalid mode. */ + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + + /* Enable transfers. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* Disable transfers again. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/* Adds a host data read request message to the client's message queue. */ +static void testMsgAddReadData(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + int rc = ShClSvcGuestDataRequest(pClient, fFormats, NULL /* pidEvent */); + RTTESTI_CHECK_RC_OK(rc); +} + +/* Does testing of VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, needed for providing compatibility to older Guest Additions clients. */ +static void testGetHostMsgOld(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + VBOXHGCMCALLHANDLE_TYPEDEF call; + int rc; + + RTTestISub("Setting up VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT test"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Unless we are bidirectional the host message requests will be dropped. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + + + RTTestISub("Testing one format, waiting guest call."); + RT_ZERO(g_Client); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing one format, no waiting guest calls."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_HTML); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing two formats, waiting guest call."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing two formats, no waiting guest calls."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + table.pfnUnload(NULL); +} + +static void testSetHeadless(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + bool fHeadless; + int rc; + + RTTestISub("Testing HOST_FN_SET_HEADLESS"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], false); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&parms[0], true); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + table.pfnUnload(NULL); +} + +static void testHostCall(void) +{ + testSetMode(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + testSetTransferMode(); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + testSetHeadless(); +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + /* Don't let assertions in the host service panic (core dump) the test cases. */ + RTAssertSetMayPanic(false); + + /* + * Run the tests. + */ + testHostCall(); + testGetHostMsgOld(); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + +int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } +void ShClBackendDestroy(PSHCLBACKEND) { } +int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } +int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClBackendTransferCreate(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClBackendTransferDestroy(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClBackendTransferGetRoots(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp new file mode 100644 index 00000000..cfb4b665 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp @@ -0,0 +1,205 @@ +/* $Id: tstClipboardServiceImpl.cpp $ */ +/** @file + * Shared Clipboard host service implementation (backend) test case. + */ + +/* + * Copyright (C) 2020-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 + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#ifdef RT_OS_WINDOWS +# include <VBox/GuestHost/SharedClipboard-win.h> +#endif + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/test.h> + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable); + +static SHCLCLIENT g_Client; +static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static int setupTable(VBOXHGCMSVCFNTABLE *pTable) +{ + pTable->cbSize = sizeof(*pTable); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + g_Helpers.pfnCallComplete = callComplete; + pTable->pHelpers = &g_Helpers; + return VBoxHGCMSvcLoad(pTable); +} + +int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } +void ShClBackendDestroy(PSHCLBACKEND) { } +int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } +int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } + +static void testAnnounceAndReadData(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + int rc; + + RTTestISub("Setting up client ..."); + RTTestIDisableAssertions(); + + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Unless we are bidirectional the host message requests will be dropped. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + rc = shClSvcClientInit(&g_Client, 1 /* clientId */); + RTTESTI_CHECK_RC_OK(rc); + + RTTestIRestoreAssertions(); +} + +#ifdef RT_OS_WINDOWS +# include "VBoxOrgCfHtml1.h" /* From chrome 97.0.4692.71 */ +# include "VBoxOrgMimeHtml1.h" + +static void testHtmlCf(void) +{ + RTTestISub("CF_HTML"); + + char *pszOutput = NULL; + uint32_t cbOutput = UINT32_MAX/2; + RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME("", 0, &pszOutput, &cbOutput), VERR_INVALID_PARAMETER); + RTTestIRestoreAssertions(); + + pszOutput = NULL; + cbOutput = UINT32_MAX/2; + RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME((char *)&g_abVBoxOrgCfHtml1[0], g_cbVBoxOrgCfHtml1, + &pszOutput, &cbOutput), VINF_SUCCESS); + RTTESTI_CHECK(cbOutput == g_cbVBoxOrgMimeHtml1); + RTTESTI_CHECK(memcmp(pszOutput, g_abVBoxOrgMimeHtml1, cbOutput) == 0); + RTMemFree(pszOutput); + + + static RTSTRTUPLE const s_aRoundTrips[] = + { + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE("1") }, + { RT_STR_TUPLE("12") }, + { RT_STR_TUPLE("123") }, + { RT_STR_TUPLE("1234") }, + { RT_STR_TUPLE("12345") }, + { RT_STR_TUPLE("123456") }, + { RT_STR_TUPLE("1234567") }, + { RT_STR_TUPLE("12345678") }, + { RT_STR_TUPLE("123456789") }, + { RT_STR_TUPLE("1234567890") }, + { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>") }, + { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") }, + { (const char *)g_abVBoxOrgMimeHtml1, sizeof(g_abVBoxOrgMimeHtml1) }, + }; + + for (size_t i = 0; i < RT_ELEMENTS(s_aRoundTrips); i++) + { + int rc; + char *pszCfHtml = NULL; + uint32_t cbCfHtml = UINT32_MAX/2; + rc = SharedClipboardWinConvertMIMEToCFHTML(s_aRoundTrips[i].psz, s_aRoundTrips[i].cch + 1, &pszCfHtml, &cbCfHtml); + if (rc == VINF_SUCCESS) + { + if (strlen(pszCfHtml) + 1 != cbCfHtml) + RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, cbCfHtml, strlen(pszCfHtml) + 1); + + char *pszHtml = NULL; + uint32_t cbHtml = UINT32_MAX/4; + rc = SharedClipboardWinConvertCFHTMLToMIME(pszCfHtml, (uint32_t)strlen(pszCfHtml), &pszHtml, &cbHtml); + if (rc == VINF_SUCCESS) + { + if (strlen(pszHtml) + 1 != cbHtml) + RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", + i, pszHtml, strlen(pszHtml), cbHtml, strlen(pszHtml) + 1); + if (strcmp(pszHtml, s_aRoundTrips[i].psz) != 0) + RTTestIFailed("#%u: roundtrip for '%s' LB %#zx failed, ended up with '%s'", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, pszHtml); + RTMemFree(pszHtml); + } + else + RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", + i, pszCfHtml, strlen(pszCfHtml), rc); + RTMemFree(pszCfHtml); + } + else + RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, rc); + } +} + +#endif /* RT_OS_WINDOWS */ + + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + /* + * Run the tests. + */ + testAnnounceAndReadData(); +#ifdef RT_OS_WINDOWS + testHtmlCf(); +#endif + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp new file mode 100644 index 00000000..dfe363ee --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp @@ -0,0 +1,389 @@ +/* $Id: tstClipboardTransfers.cpp $ */ +/** @file + * Shared Clipboard transfers test case. + */ + +/* + * Copyright (C) 2019-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 + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir) +{ + char szTempDir[RTPATH_MAX]; + int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirCreate(szTempDir, 0700, 0); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXX"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirCreateTemp(szTempDir, 0700); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathJoin(pszTempDir, cbTempDir, szTempDir, pszTestcase); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir); + + return rc; +} + +static int testRemoveTempDir(RTTEST hTest) +{ + char szTempDir[RTPATH_MAX]; + int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Removed temporary directory: %s\n", szTempDir); + + return rc; +} + +static int testCreateDir(RTTEST hTest, const char *pszPathToCreate) +{ + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating directory: %s\n", pszPathToCreate); + + int rc = RTDirCreateFullPath(pszPathToCreate, 0700); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + return rc; +} + +static int testCreateFile(RTTEST hTest, const char *pszTempDir, const char *pszFileName, uint32_t fOpen, size_t cbSize, + char **ppszFilePathAbs) +{ + char szFilePath[RTPATH_MAX]; + + int rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszTempDir); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + rc = RTPathAppend(szFilePath, sizeof(szFilePath), pszFileName); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + char *pszDirToCreate = RTStrDup(szFilePath); + RTTESTI_CHECK_RET(pszDirToCreate, VERR_NO_MEMORY); + + RTPathStripFilename(pszDirToCreate); + + rc = testCreateDir(hTest, pszDirToCreate); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + RTStrFree(pszDirToCreate); + pszDirToCreate = NULL; + + if (!fOpen) + fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE; + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating file: %s\n", szFilePath); + + RTFILE hFile; + rc = RTFileOpen(&hFile, szFilePath, fOpen); + if (RT_SUCCESS(rc)) + { + if (cbSize) + { + /** @todo Fill in some random stuff. */ + } + + rc = RTFileClose(hFile); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + } + + if (ppszFilePathAbs) + *ppszFilePathAbs = RTStrDup(szFilePath); + + return rc; +} + +typedef struct TESTTRANSFERROOTENTRY +{ + TESTTRANSFERROOTENTRY(const RTCString &a_strPath) + : strPath(a_strPath) { } + + RTCString strPath; +} TESTTRANSFERROOTENTRY; + +static int testAddRootEntry(RTTEST hTest, const char *pszTempDir, + const TESTTRANSFERROOTENTRY &rootEntry, char **ppszRoots) +{ + char *pszRoots = NULL; + + const char *pszPath = rootEntry.strPath.c_str(); + + char *pszPathAbs; + int rc = testCreateFile(hTest, pszTempDir, pszPath, 0, 0, &pszPathAbs); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + rc = RTStrAAppend(&pszRoots, pszPathAbs); + RTTESTI_CHECK_RC_OK(rc); + + rc = RTStrAAppend(&pszRoots, "\r\n"); + RTTESTI_CHECK_RC_OK(rc); + + RTStrFree(pszPathAbs); + + *ppszRoots = pszRoots; + + return rc; +} + +static int testAddRootEntries(RTTEST hTest, const char *pszTempDir, + RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend, + char **ppszRoots) +{ + int rc = VINF_SUCCESS; + + char *pszRoots = NULL; + + for (size_t i = 0; i < lstBase.size(); ++i) + { + char *pszEntry = NULL; + rc = testAddRootEntry(hTest, pszTempDir, lstBase.at(i), &pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + rc = RTStrAAppend(&pszRoots, pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + RTStrFree(pszEntry); + } + + for (size_t i = 0; i < lstToExtend.size(); ++i) + { + char *pszEntry = NULL; + rc = testAddRootEntry(hTest, pszTempDir, lstToExtend.at(i), &pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + rc = RTStrAAppend(&pszRoots, pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + RTStrFree(pszEntry); + } + + if (RT_SUCCESS(rc)) + *ppszRoots = pszRoots; + + return rc; +} + +static void testTransferRootsSetSingle(RTTEST hTest, + RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend, + int rcExpected) +{ + PSHCLTRANSFER pTransfer; + int rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + + char szTestTransferRootsSetDir[RTPATH_MAX]; + rc = testCreateTempDir(hTest, "testTransferRootsSet", szTestTransferRootsSetDir, sizeof(szTestTransferRootsSetDir)); + RTTESTI_CHECK_RC_OK_RETV(rc); + + /* This is the file we're trying to access (but not supposed to). */ + rc = testCreateFile(hTest, szTestTransferRootsSetDir, "must-not-access-this", 0, 0, NULL); + RTTESTI_CHECK_RC_OK(rc); + + char *pszRoots; + rc = testAddRootEntries(hTest, szTestTransferRootsSetDir, lstBase, lstToExtend, &pszRoots); + RTTESTI_CHECK_RC_OK_RETV(rc); + + rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1); + RTTESTI_CHECK_RC(rc, rcExpected); + + RTStrFree(pszRoots); + + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferObjOpenSingle(RTTEST hTest, + RTCList<TESTTRANSFERROOTENTRY> &lstRoots, const char *pszObjPath, int rcExpected) +{ + RT_NOREF(hTest); + + PSHCLTRANSFER pTransfer; + int rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + + rc = ShClTransferInit(pTransfer, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_LOCAL); + RTTESTI_CHECK_RC_OK(rc); + + char szTestTransferObjOpenDir[RTPATH_MAX]; + rc = testCreateTempDir(hTest, "testTransferObjOpen", szTestTransferObjOpenDir, sizeof(szTestTransferObjOpenDir)); + RTTESTI_CHECK_RC_OK_RETV(rc); + + /* This is the file we're trying to access (but not supposed to). */ + rc = testCreateFile(hTest, szTestTransferObjOpenDir, "file1.txt", 0, 0, NULL); + RTTESTI_CHECK_RC_OK(rc); + + RTCList<TESTTRANSFERROOTENTRY> lstToExtendEmpty; + + char *pszRoots; + rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); + RTTESTI_CHECK_RC_OK_RETV(rc); + + rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1); + RTTESTI_CHECK_RC_OK(rc); + + RTStrFree(pszRoots); + + SHCLOBJOPENCREATEPARMS openCreateParms; + rc = ShClTransferObjOpenParmsInit(&openCreateParms); + RTTESTI_CHECK_RC_OK(rc); + + rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, pszObjPath); + RTTESTI_CHECK_RC_OK(rc); + + SHCLOBJHANDLE hObj; + rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); + RTTESTI_CHECK_RC(rc, rcExpected); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferObjClose(pTransfer, hObj); + RTTESTI_CHECK_RC_OK(rc); + } + + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferBasics(RTTEST hTest) +{ + RT_NOREF(hTest); + + RTTestISub("Testing transfer basics"); + + SHCLEVENTSOURCE Source; + int rc = ShClEventSourceCreate(&Source, 0); + RTTESTI_CHECK_RC_OK(rc); + rc = ShClEventSourceDestroy(&Source); + RTTESTI_CHECK_RC_OK(rc); + PSHCLTRANSFER pTransfer; + rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferRootsSet(RTTEST hTest) +{ + RTTestISub("Testing setting transfer roots"); + + /* Define the (valid) transfer root set. */ + RTCList<TESTTRANSFERROOTENTRY> lstBase; + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); + + RTCList<TESTTRANSFERROOTENTRY> lstBreakout; + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VINF_SUCCESS); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("does-not-exist/file1.txt")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/./../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("../does-not-exist")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); +} + +static void testTransferObjOpen(RTTEST hTest) +{ + RTTestISub("Testing setting transfer object open"); + + /* Define the (valid) transfer root set. */ + RTCList<TESTTRANSFERROOTENTRY> lstRoots; + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); + + testTransferObjOpenSingle(hTest, lstRoots, "file1.txt", VINF_SUCCESS); + testTransferObjOpenSingle(hTest, lstRoots, "does-not-exist.txt", VERR_PATH_NOT_FOUND); + testTransferObjOpenSingle(hTest, lstRoots, "dir1/does-not-exist.txt", VERR_PATH_NOT_FOUND); + testTransferObjOpenSingle(hTest, lstRoots, "../must-not-access-this.txt", VERR_INVALID_PARAMETER); + testTransferObjOpenSingle(hTest, lstRoots, "dir1/../../must-not-access-this.txt", VERR_INVALID_PARAMETER); +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + testTransferBasics(hTest); + testTransferRootsSet(hTest); + testTransferObjOpen(hTest); + + int rc = testRemoveTempDir(hTest); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/HostServices/SharedFolders/Makefile.kmk b/src/VBox/HostServices/SharedFolders/Makefile.kmk new file mode 100644 index 00000000..fc0ff6c5 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/Makefile.kmk @@ -0,0 +1,62 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Folders Host Service. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The shared folder service DLL. +# +DLLS += VBoxSharedFolders +VBoxSharedFolders_TEMPLATE = VBoxR3Dll +VBoxSharedFolders_NAME.os2 = VBoxSFld +VBoxSharedFolders_DEFS = VBOX_WITH_HGCM RTSHFL +VBoxSharedFolders_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxSharedFolders_LDFLAGS.darwin = \ + -framework Carbon \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedFolders.dylib + +VBoxSharedFolders_SOURCES = \ + VBoxSharedFoldersSvc.cpp \ + shflhandle.cpp \ + vbsf.cpp \ + vbsfpath.cpp \ + vbsfpathabs.cpp \ + mappings.cpp +VBoxSharedFolders_SOURCES.win = \ + VBoxSharedFoldersSvc.rc + +VBoxSharedFolders_LIBS = \ + $(LIB_RUNTIME) + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp new file mode 100644 index 00000000..3486709d --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp @@ -0,0 +1,1971 @@ +/* $Id: VBoxSharedFoldersSvc.cpp $ */ +/** @file + * Shared Folders - Host service entry points. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include <VBox/shflsvc.h> + +#include "shfl.h" +#include "mappings.h" +#include "shflhandle.h" +#include "vbsf.h" +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <VBox/AssertGuest.h> +#include <VBox/vmm/ssm.h> +#include <VBox/vmm/pdmifs.h> +#include <VBox/vmm/vmmr3vtable.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16 2 +#define SHFL_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT 3 +#define SHFL_SAVED_STATE_VERSION_PRE_ERROR_STYLE 4 +#define SHFL_SAVED_STATE_VERSION 5 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +PVBOXHGCMSVCHELPERS g_pHelpers; +static PPDMLED g_pStatusLed = NULL; + +/** @name Shared folder statistics. + * @{ */ +static STAMPROFILE g_StatQueryMappings; +static STAMPROFILE g_StatQueryMappingsFail; +static STAMPROFILE g_StatQueryMapName; +static STAMPROFILE g_StatCreate; +static STAMPROFILE g_StatCreateFail; +static STAMPROFILE g_StatLookup; +static STAMPROFILE g_StatLookupFail; +static STAMPROFILE g_StatClose; +static STAMPROFILE g_StatCloseFail; +static STAMPROFILE g_StatRead; +static STAMPROFILE g_StatReadFail; +static STAMPROFILE g_StatWrite; +static STAMPROFILE g_StatWriteFail; +static STAMPROFILE g_StatLock; +static STAMPROFILE g_StatLockFail; +static STAMPROFILE g_StatList; +static STAMPROFILE g_StatListFail; +static STAMPROFILE g_StatReadLink; +static STAMPROFILE g_StatReadLinkFail; +static STAMPROFILE g_StatMapFolderOld; +static STAMPROFILE g_StatMapFolder; +static STAMPROFILE g_StatMapFolderFail; +static STAMPROFILE g_StatUnmapFolder; +static STAMPROFILE g_StatUnmapFolderFail; +static STAMPROFILE g_StatInformationFail; +static STAMPROFILE g_StatInformationSetFile; +static STAMPROFILE g_StatInformationSetFileFail; +static STAMPROFILE g_StatInformationSetSize; +static STAMPROFILE g_StatInformationSetSizeFail; +static STAMPROFILE g_StatInformationGetFile; +static STAMPROFILE g_StatInformationGetFileFail; +static STAMPROFILE g_StatInformationGetVolume; +static STAMPROFILE g_StatInformationGetVolumeFail; +static STAMPROFILE g_StatRemove; +static STAMPROFILE g_StatRemoveFail; +static STAMPROFILE g_StatCloseAndRemove; +static STAMPROFILE g_StatCloseAndRemoveFail; +static STAMPROFILE g_StatRename; +static STAMPROFILE g_StatRenameFail; +static STAMPROFILE g_StatFlush; +static STAMPROFILE g_StatFlushFail; +static STAMPROFILE g_StatSetErrorStyle; +static STAMPROFILE g_StatSetUtf8; +static STAMPROFILE g_StatSetFileSize; +static STAMPROFILE g_StatSetFileSizeFail; +static STAMPROFILE g_StatSymlink; +static STAMPROFILE g_StatSymlinkFail; +static STAMPROFILE g_StatSetSymlinks; +static STAMPROFILE g_StatQueryMapInfo; +static STAMPROFILE g_StatQueryFeatures; +static STAMPROFILE g_StatCopyFile; +static STAMPROFILE g_StatCopyFileFail; +static STAMPROFILE g_StatCopyFilePart; +static STAMPROFILE g_StatCopyFilePartFail; +static STAMPROFILE g_StatWaitForMappingsChanges; +static STAMPROFILE g_StatWaitForMappingsChangesFail; +static STAMPROFILE g_StatCancelMappingsChangesWait; +static STAMPROFILE g_StatUnknown; +static STAMPROFILE g_StatMsgStage1; +/** @} */ + + +/** @page pg_shfl_svc Shared Folders Host Service + * + * Shared Folders map a host file system to guest logical filesystem. + * A mapping represents 'host name'<->'guest name' translation and a root + * identifier to be used to access this mapping. + * Examples: "C:\WINNT"<->"F:", "C:\WINNT\System32"<->"/mnt/host/system32". + * + * Therefore, host name and guest name are strings interpreted + * only by host service and guest client respectively. Host name is + * passed to guest only for informational purpose. Guest may for example + * display the string or construct volume label out of the string. + * + * Root identifiers are unique for whole guest life, + * that is until next guest reset/fresh start. + * 32 bit value incremented for each new mapping is used. + * + * Mapping strings are taken from VM XML configuration on VM startup. + * The service DLL takes mappings during initialization. There is + * also API for changing mappings at runtime. + * + * Current mappings and root identifiers are saved when VM is saved. + * + * Guest may use any of these mappings. Full path information + * about an object on a mapping consists of the root identifier and + * a full path of object. + * + * Guest IFS connects to the service and calls SHFL_FN_QUERY_MAP + * function which returns current mappings. For guest convenience, + * removed mappings also returned with REMOVED flag and new mappings + * are marked with NEW flag. + * + * To access host file system guest just forwards file system calls + * to the service, and specifies full paths or handles for objects. + * + * + */ + + + +static DECLCALLBACK(int) svcUnload (void *) +{ + int rc = VINF_SUCCESS; + + Log(("svcUnload\n")); + vbsfFreeHandleTable(); + + if (g_pHelpers) + HGCMSvcHlpStamDeregister(g_pHelpers, "/HGCM/VBoxSharedFolders/*"); + return rc; +} + +static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + RT_NOREF(u32ClientID, fRequestor, fRestoring); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + Log(("SharedFolders host service: connected, u32ClientID = %u\n", u32ClientID)); + + pClient->fHasMappingCounts = true; + pClient->enmErrorStyle = SHFLERRORSTYLE_NATIVE; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect (void *, uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF1(u32ClientID); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + /* When a client disconnects, make sure that outstanding change waits are being canceled. + * + * Usually this will be done actively by VBoxService on the guest side when shutting down, + * but the VM could be reset without having VBoxService the chance of cancelling those waits. + * + * This in turn will eat up the call completion handle restrictions on the HGCM host side, throwing assertions. */ + int rc = vbsfMappingsCancelChangesWaits(pClient); + + Log(("SharedFolders host service: disconnected, u32ClientID = %u, rc = %Rrc\n", u32ClientID, rc)); + + vbsfDisconnect(pClient); + return rc; +} + +/** @note We only save as much state as required to access the shared folder again after restore. + * All I/O requests pending at the time of saving will never be completed or result in errors. + * (file handles no longer valid etc) + * This works as designed at the moment. A full state save would be difficult and not always possible + * as the contents of a shared folder might change in between save and restore. + */ +static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ +#ifndef UNITTEST /* Read this as not yet tested */ + RT_NOREF1(u32ClientID); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + Log(("SharedFolders host service: saving state, u32ClientID = %u\n", u32ClientID)); + + int rc = pVMM->pfnSSMR3PutU32(pSSM, SHFL_SAVED_STATE_VERSION); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutU32(pSSM, SHFL_MAX_MAPPINGS); + AssertRCReturn(rc, rc); + + /* Save client structure length & contents */ + rc = pVMM->pfnSSMR3PutU32(pSSM, sizeof(*pClient)); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutMem(pSSM, pClient, sizeof(*pClient)); + AssertRCReturn(rc, rc); + + /* Save all the active mappings. */ + for (int i=0;i<SHFL_MAX_MAPPINGS;i++) + { + /* Mapping are saved in the order of increasing root handle values. */ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(i); + + rc = pVMM->pfnSSMR3PutU32(pSSM, pFolderMapping? pFolderMapping->cMappings: 0); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping? pFolderMapping->fValid: false); + AssertRCReturn(rc, rc); + + if (pFolderMapping && pFolderMapping->fValid) + { + uint32_t len = (uint32_t)strlen(pFolderMapping->pszFolderName); + pVMM->pfnSSMR3PutU32(pSSM, len); + pVMM->pfnSSMR3PutStrZ(pSSM, pFolderMapping->pszFolderName); + + len = ShflStringSizeOfBuffer(pFolderMapping->pMapName); + pVMM->pfnSSMR3PutU32(pSSM, len); + pVMM->pfnSSMR3PutMem(pSSM, pFolderMapping->pMapName, len); + + pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping->fHostCaseSensitive); + + pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping->fGuestCaseSensitive); + + len = ShflStringSizeOfBuffer(pFolderMapping->pAutoMountPoint); + pVMM->pfnSSMR3PutU32(pSSM, len); + rc = pVMM->pfnSSMR3PutMem(pSSM, pFolderMapping->pAutoMountPoint, len); + AssertRCReturn(rc, rc); + } + } + +#else + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); +#endif + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ +#ifndef UNITTEST /* Read this as not yet tested */ + RT_NOREF(u32ClientID, uVersion); + uint32_t nrMappings; + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + uint32_t len; + + Log(("SharedFolders host service: loading state, u32ClientID = %u\n", u32ClientID)); + + uint32_t uShfVersion = 0; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &uShfVersion); + AssertRCReturn(rc, rc); + + if ( uShfVersion > SHFL_SAVED_STATE_VERSION + || uShfVersion < SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION, RT_SRC_POS, + "Unknown shared folders state version %u!", uShfVersion); + + rc = pVMM->pfnSSMR3GetU32(pSSM, &nrMappings); + AssertRCReturn(rc, rc); + if (nrMappings != SHFL_MAX_MAPPINGS) + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + + /* Restore the client data (flags + path delimiter + mapping counts (new) at the moment) */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &len); + AssertRCReturn(rc, rc); + + if (len == RT_UOFFSETOF(SHFLCLIENTDATA, acMappings)) + pClient->fHasMappingCounts = false; + else if (len != sizeof(*pClient)) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Saved SHFLCLIENTDATA size %u differs from current %u!", len, sizeof(*pClient)); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pClient, len); + AssertRCReturn(rc, rc); + + /* For older saved state, use the default native error style, otherwise + check that the restored value makes sense to us. */ + if (uShfVersion <= SHFL_SAVED_STATE_VERSION_PRE_ERROR_STYLE) + pClient->enmErrorStyle = SHFLERRORSTYLE_NATIVE; + else if ( pClient->enmErrorStyle <= kShflErrorStyle_Invalid + || pClient->enmErrorStyle >= kShflErrorStyle_End) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Saved SHFLCLIENTDATA enmErrorStyle value %d is not known/valid!", pClient->enmErrorStyle); + + /* Drop the root IDs of all configured mappings before restoring: */ + vbsfMappingLoadingStart(); + + /* We don't actually (fully) restore the state; we simply check if the current state is as we it expect it to be. */ + for (SHFLROOT i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + /* Load the saved mapping description and try to find it in the mappings. */ + MAPPING mapping; + RT_ZERO(mapping); + + /* restore the folder mapping counter. */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &mapping.cMappings); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fValid); + AssertRCReturn(rc, rc); + + if (mapping.fValid) + { + /* Load the host path name. */ + uint32_t cb; + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + + char *pszFolderName; + if (uShfVersion == SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16) /* (See version range check above.) */ + { + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad folder name size: %#x", cb)); + PSHFLSTRING pFolderName = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pFolderName != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pFolderName, cb); + AssertRCReturn(rc, rc); + AssertReturn(pFolderName->u16Size < cb && pFolderName->u16Length < pFolderName->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad folder name string: %#x/%#x cb=%#x", + pFolderName->u16Size, pFolderName->u16Length, cb)); + + rc = RTUtf16ToUtf8(pFolderName->String.ucs2, &pszFolderName); + RTMemFree(pFolderName); + AssertRCReturn(rc, rc); + } + else + { + pszFolderName = (char *)RTStrAlloc(cb + 1); + AssertReturn(pszFolderName, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetStrZ(pSSM, pszFolderName, cb + 1); + AssertRCReturn(rc, rc); + mapping.pszFolderName = pszFolderName; + } + + /* Load the map name. */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad map name size: %#x", cb)); + + PSHFLSTRING pMapName = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pMapName != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pMapName, cb); + AssertRCReturn(rc, rc); + AssertReturn(pMapName->u16Size < cb && pMapName->u16Length < pMapName->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad map name string: %#x/%#x cb=%#x", + pMapName->u16Size, pMapName->u16Length, cb)); + + /* Load case sensitivity config. */ + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fHostCaseSensitive); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fGuestCaseSensitive); + AssertRCReturn(rc, rc); + + /* Load the auto mount point. */ + PSHFLSTRING pAutoMountPoint; + if (uShfVersion > SHFL_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT) + { + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad auto mount point size: %#x", cb)); + + pAutoMountPoint = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pAutoMountPoint != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pAutoMountPoint, cb); + AssertRCReturn(rc, rc); + AssertReturn(pAutoMountPoint->u16Size < cb && pAutoMountPoint->u16Length < pAutoMountPoint->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad auto mount point string: %#x/%#x cb=%#x", + pAutoMountPoint->u16Size, pAutoMountPoint->u16Length, cb)); + + } + else + { + pAutoMountPoint = ShflStringDupUtf8(""); + AssertReturn(pAutoMountPoint, VERR_NO_MEMORY); + } + + mapping.pszFolderName = pszFolderName; + mapping.pMapName = pMapName; + mapping.pAutoMountPoint = pAutoMountPoint; + + /* 'i' is the root handle of the saved mapping. */ + rc = vbsfMappingLoaded(&mapping, i); + if (RT_FAILURE(rc)) + { + LogRel(("SharedFolders host service: %Rrc loading %d [%ls] -> [%s]\n", + rc, i, pMapName->String.ucs2, pszFolderName)); + } + + RTMemFree(pAutoMountPoint); + RTMemFree(pMapName); + RTStrFree(pszFolderName); + + AssertRCReturn(rc, rc); + } + } + + /* Make sure all mappings have root IDs (global folders changes, VM + config changes (paranoia)): */ + vbsfMappingLoadingDone(); + + Log(("SharedFolders host service: successfully loaded state\n")); +#else + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); +#endif + return VINF_SUCCESS; +} + +static DECLCALLBACK(void) svcCall (void *, VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + RT_NOREF(u32ClientID, tsArrival); +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS + uint64_t tsStart; + STAM_GET_TS(tsStart); + STAM_REL_PROFILE_ADD_PERIOD(&g_StatMsgStage1, tsStart - tsArrival); +#endif + Log(("SharedFolders host service: svcCall: u32ClientID = %u, fn = %u, cParms = %u, pparms = %p\n", u32ClientID, u32Function, cParms, paParms)); + + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + bool fAsynchronousProcessing = false; + +#ifdef LOG_ENABLED + for (uint32_t i = 0; i < cParms; i++) + { + /** @todo parameters other than 32 bit */ + Log((" pparms[%d]: type %u, value %u\n", i, paParms[i].type, paParms[i].u.uint32)); + } +#endif + + int rc = VINF_SUCCESS; + PSTAMPROFILE pStat, pStatFail; + switch (u32Function) + { + case SHFL_FN_QUERY_MAPPINGS: + { + pStat = &g_StatQueryMappings; + pStatFail = &g_StatQueryMappingsFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_QUERY_MAPPINGS\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_QUERY_MAPPINGS) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* numberOfMappings */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* mappings */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + uint32_t fu32Flags = paParms[0].u.uint32; + uint32_t cMappings = paParms[1].u.uint32; + SHFLMAPPING *pMappings = (SHFLMAPPING *)paParms[2].u.pointer.addr; + uint32_t cbMappings = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if ( (fu32Flags & ~SHFL_MF_MASK) != 0 + || cbMappings / sizeof (SHFLMAPPING) != cMappings + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + if (fu32Flags & SHFL_MF_UTF8) + pClient->fu32Flags |= SHFL_CF_UTF8; + /// @todo r=bird: Someone please explain this amusing code (r63916): + //if (fu32Flags & SHFL_MF_AUTOMOUNT) + // pClient->fu32Flags |= SHFL_MF_AUTOMOUNT; + // + //rc = vbsfMappingsQuery(pClient, pMappings, &cMappings); + + rc = vbsfMappingsQuery(pClient, RT_BOOL(fu32Flags & SHFL_MF_AUTOMOUNT), pMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + /* Report that there are more mappings to get if + * handed in buffer is too small. */ + if (paParms[1].u.uint32 < cMappings) + rc = VINF_BUFFER_OVERFLOW; + + /* Update parameters. */ + paParms[1].u.uint32 = cMappings; + } + } + } + + + } break; + + case SHFL_FN_QUERY_MAP_NAME: + { + pStatFail = pStat = &g_StatQueryMapName; + Log(("SharedFolders host service: svcCall: SHFL_FN_QUERY_MAP_NAME\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_QUERY_MAP_NAME) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* Root. */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* Name. */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pString = (SHFLSTRING *)paParms[1].u.pointer.addr; + + /* Verify parameters values. */ + if (!ShflStringIsValidOut(pString, paParms[1].u.pointer.size)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMappingsQueryName(pClient, root, pString); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* None. */ + } + } + } + + } break; + + case SHFL_FN_CREATE: + { + pStat = &g_StatCreate; + pStatFail = &g_StatCreateFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CREATE\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_CREATE) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* parms */ + ) + { + Log(("SharedFolders host service: Invalid parameters types\n")); + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t cbPath = paParms[1].u.pointer.size; + SHFLCREATEPARMS *pParms = (SHFLCREATEPARMS *)paParms[2].u.pointer.addr; + uint32_t cbParms = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pPath, cbPath, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || (cbParms != sizeof (SHFLCREATEPARMS)) + ) + { + AssertMsgFailed (("Invalid parameters cbPath or cbParms (%x, %x - expected >=%x, %x)\n", + cbPath, cbParms, sizeof(SHFLSTRING), sizeof (SHFLCREATEPARMS))); + rc = VERR_INVALID_PARAMETER; + } + else + { + if (pParms->CreateFlags & SHFL_CF_LOOKUP) + { + pStat = &g_StatLookup; + pStatFail = &g_StatLookupFail; + } + + /* Execute the function. */ + rc = vbsfCreate (pClient, root, pPath, cbPath, pParms); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + } + + case SHFL_FN_CLOSE: + { + pStat = &g_StatClose; + pStatFail = &g_StatCloseFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CLOSE\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_CLOSE) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else + { + /* Execute the function. */ + rc = vbsfClose (pClient, root, Handle); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + + } + + /* Read object content. */ + case SHFL_FN_READ: + { + pStat = &g_StatRead; + pStatFail = &g_StatReadFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_READ\n")); + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_READ, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* offset */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* count */ + ASSERT_GUEST_STMT_BREAK( paParms[4].type == VBOX_HGCM_SVC_PARM_PTR + || paParms[4].type == VBOX_HGCM_SVC_PARM_PAGES, rc = VERR_WRONG_PARAMETER_TYPE); /* buffer */ + + /* Fetch parameters. */ + SHFLROOT const idRoot = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE const hFile = paParms[1].u.uint64; + uint64_t const offFile = paParms[2].u.uint64; + uint32_t cbRead = paParms[3].u.uint32; + + /* Verify parameters values. */ + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_ROOT, rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_NIL, rc = VERR_INVALID_HANDLE); + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + ASSERT_GUEST_STMT_BREAK(cbRead <= paParms[4].u.pointer.size, rc = VERR_INVALID_HANDLE); + else + ASSERT_GUEST_STMT_BREAK(cbRead <= paParms[4].u.Pages.cb, rc = VERR_OUT_OF_RANGE); + + /* Execute the function. */ + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fReading = g_pStatusLed->Actual.s.fReading = 1; + } + + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + rc = vbsfRead(pClient, idRoot, hFile, offFile, &cbRead, (uint8_t *)paParms[4].u.pointer.addr); + else + rc = vbsfReadPages(pClient, idRoot, hFile, offFile, &cbRead, &paParms[4].u.Pages); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fReading = 0; + + /* Update parameters.*/ + paParms[3].u.uint32 = RT_SUCCESS(rc) ? cbRead : 0 /* nothing read */; + break; + } + + /* Write new object content. */ + case SHFL_FN_WRITE: + { + pStat = &g_StatWrite; + pStatFail = &g_StatWriteFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_WRITE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_WRITE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* offset */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* count */ + ASSERT_GUEST_STMT_BREAK( paParms[4].type == VBOX_HGCM_SVC_PARM_PTR + || paParms[4].type == VBOX_HGCM_SVC_PARM_PAGES, rc = VERR_WRONG_PARAMETER_TYPE); /* buffer */ + /* Fetch parameters. */ + SHFLROOT const idRoot = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE const hFile = paParms[1].u.uint64; + uint64_t offFile = paParms[2].u.uint64; + uint32_t cbWrite = paParms[3].u.uint32; + + /* Verify parameters values. */ + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_ROOT, rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_NIL, rc = VERR_INVALID_HANDLE); + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + ASSERT_GUEST_STMT_BREAK(cbWrite <= paParms[4].u.pointer.size, rc = VERR_INVALID_HANDLE); + else + ASSERT_GUEST_STMT_BREAK(cbWrite <= paParms[4].u.Pages.cb, rc = VERR_OUT_OF_RANGE); + + /* Execute the function. */ + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fWriting = g_pStatusLed->Actual.s.fWriting = 1; + } + + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + rc = vbsfWrite(pClient, idRoot, hFile, &offFile, &cbWrite, (uint8_t *)paParms[4].u.pointer.addr); + else + rc = vbsfWritePages(pClient, idRoot, hFile, &offFile, &cbWrite, &paParms[4].u.Pages); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fWriting = 0; + + /* Update parameters.*/ + if (RT_SUCCESS(rc)) + { + paParms[3].u.uint32 = cbWrite; + paParms[2].u.uint64 = offFile; + } + else + paParms[3].u.uint32 = 0; + break; + } + + /* Lock/unlock a range in the object. */ + case SHFL_FN_LOCK: + pStat = &g_StatLock; + pStatFail = &g_StatLockFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_LOCK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_LOCK) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_64BIT /* offset */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_64BIT /* length */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint64_t offset = paParms[2].u.uint64; + uint64_t length = paParms[3].u.uint64; + uint32_t flags = paParms[4].u.uint32; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else if (flags & SHFL_LOCK_WAIT) + { + /** @todo This should be properly implemented by the shared folders service. + * The service thread must never block. If an operation requires + * blocking, it must be processed by another thread and when it is + * completed, the another thread must call + * + * g_pHelpers->pfnCallComplete (callHandle, rc); + * + * The operation is async. + * fAsynchronousProcessing = true; + */ + + /* Here the operation must be posted to another thread. At the moment it is not implemented. + * Until it is implemented, try to perform the operation without waiting. + */ + flags &= ~SHFL_LOCK_WAIT; + + /* Execute the function. */ + if ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + rc = vbsfUnlock(pClient, root, Handle, offset, length, flags); + else + rc = vbsfLock(pClient, root, Handle, offset, length, flags); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* none */ + } + } + else + { + /* Execute the function. */ + if ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + rc = vbsfUnlock(pClient, root, Handle, offset, length, flags); + else + rc = vbsfLock(pClient, root, Handle, offset, length, flags); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* none */ + } + } + } + break; + + /* List object content. */ + case SHFL_FN_LIST: + { + pStat = &g_StatList; + pStatFail = &g_StatListFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_LIST\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_LIST) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* cb */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_PTR /* pPath */ + || paParms[5].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + || paParms[6].type != VBOX_HGCM_SVC_PARM_32BIT /* resumePoint */ + || paParms[7].type != VBOX_HGCM_SVC_PARM_32BIT /* cFiles (out) */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint32_t flags = paParms[2].u.uint32; + uint32_t length = paParms[3].u.uint32; + SHFLSTRING *pPath = (paParms[4].u.pointer.size == 0) ? 0 : (SHFLSTRING *)paParms[4].u.pointer.addr; + uint8_t *pBuffer = (uint8_t *)paParms[5].u.pointer.addr; + uint32_t resumePoint = paParms[6].u.uint32; + uint32_t cFiles = 0; + + /* Verify parameters values. */ + if ( (length < sizeof (SHFLDIRINFO)) + || length > paParms[5].u.pointer.size + || !ShflStringIsValidOrNullIn(pPath, paParms[4].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fReading = g_pStatusLed->Actual.s.fReading = 1; + } + + /* Execute the function. */ + rc = vbsfDirList (pClient, root, Handle, pPath, flags, &length, pBuffer, &resumePoint, &cFiles); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fReading = 0; + + if (rc == VERR_NO_MORE_FILES && cFiles != 0) + rc = VINF_SUCCESS; /* Successfully return these files. */ + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[3].u.uint32 = length; + paParms[6].u.uint32 = resumePoint; + paParms[7].u.uint32 = cFiles; + } + else + { + paParms[3].u.uint32 = 0; /* nothing read */ + paParms[6].u.uint32 = 0; + paParms[7].u.uint32 = cFiles; + } + } + } + break; + } + + /* Read symlink destination */ + case SHFL_FN_READLINK: + { + pStat = &g_StatReadLink; + pStatFail = &g_StatReadLinkFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_READLINK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_READLINK) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t cbPath = paParms[1].u.pointer.size; + uint8_t *pBuffer = (uint8_t *)paParms[2].u.pointer.addr; + uint32_t cbBuffer = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if (!ShflStringIsValidOrNullIn(pPath, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfReadLink (pClient, root, pPath, cbPath, pBuffer, cbBuffer); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + + break; + } + + /* Legacy interface */ + case SHFL_FN_MAP_FOLDER_OLD: + { + pStatFail = pStat = &g_StatMapFolderOld; + Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER_OLD\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_MAP_FOLDER_OLD) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* delimiter */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PSHFLSTRING pszMapName = (PSHFLSTRING)paParms[0].u.pointer.addr; + SHFLROOT root = (SHFLROOT)paParms[1].u.uint32; + RTUTF16 delimiter = (RTUTF16)paParms[2].u.uint32; + + /* Verify parameters values. */ + if (!ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMapFolder (pClient, pszMapName, delimiter, false, &root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[1].u.uint32 = root; + } + } + } + break; + } + + case SHFL_FN_MAP_FOLDER: + { + pStat = &g_StatMapFolder; + pStatFail = &g_StatMapFolderFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER\n")); + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + Log(("SharedFolders host service: request to map folder '%s'\n", + ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf8)); + else + Log(("SharedFolders host service: request to map folder '%ls'\n", + ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.ucs2)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_MAP_FOLDER) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* delimiter */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* fCaseSensitive */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PSHFLSTRING pszMapName = (PSHFLSTRING)paParms[0].u.pointer.addr; + SHFLROOT root = (SHFLROOT)paParms[1].u.uint32; + RTUTF16 delimiter = (RTUTF16)paParms[2].u.uint32; + bool fCaseSensitive = !!paParms[3].u.uint32; + + /* Verify parameters values. */ + if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VINF_SUCCESS; + } + else + { + rc = VERR_INVALID_PARAMETER; + + /* Fudge for windows GAs getting the length wrong by one char. */ + if ( !(pClient->fu32Flags & SHFL_CF_UTF8) + && paParms[0].u.pointer.size >= sizeof(SHFLSTRING) + && pszMapName->u16Length >= 2 + && pszMapName->String.ucs2[pszMapName->u16Length / 2 - 1] == 0x0000) + { + pszMapName->u16Length -= 2; + if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) + rc = VINF_SUCCESS; + else + pszMapName->u16Length += 2; + } + } + + /* Execute the function. */ + if (RT_SUCCESS(rc)) + rc = vbsfMapFolder (pClient, pszMapName, delimiter, fCaseSensitive, &root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[1].u.uint32 = root; + } + } + Log(("SharedFolders host service: map operation result %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + Log(("SharedFolders host service: mapped to handle %d\n", paParms[1].u.uint32)); + break; + } + + case SHFL_FN_UNMAP_FOLDER: + { + pStat = &g_StatUnmapFolder; + pStatFail = &g_StatUnmapFolderFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_UNMAP_FOLDER\n")); + Log(("SharedFolders host service: request to unmap folder handle %u\n", + paParms[0].u.uint32)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_UNMAP_FOLDER) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + + /* Execute the function. */ + rc = vbsfUnmapFolder (pClient, root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* nothing */ + } + } + Log(("SharedFolders host service: unmap operation result %Rrc\n", rc)); + break; + } + + /* Query/set object information. */ + case SHFL_FN_INFORMATION: + { + pStatFail = pStat = &g_StatInformationFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_INFORMATION\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_INFORMATION) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* cb */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint32_t flags = paParms[2].u.uint32; + uint32_t length = paParms[3].u.uint32; + uint8_t *pBuffer = (uint8_t *)paParms[4].u.pointer.addr; + + /* Verify parameters values. */ + if (length > paParms[4].u.pointer.size) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + if (flags & SHFL_INFO_SET) + { + rc = vbsfSetFSInfo (pClient, root, Handle, flags, &length, pBuffer); + + if (flags & SHFL_INFO_FILE) + { + pStat = &g_StatInformationSetFile; + pStatFail = &g_StatInformationSetFileFail; + } + else if (flags & SHFL_INFO_SIZE) + { + pStat = &g_StatInformationSetSize; + pStatFail = &g_StatInformationSetSizeFail; + } + } + else /* SHFL_INFO_GET */ + { + rc = vbsfQueryFSInfo (pClient, root, Handle, flags, &length, pBuffer); + + if (flags & SHFL_INFO_FILE) + { + pStat = &g_StatInformationGetFile; + pStatFail = &g_StatInformationGetFileFail; + } + else if (flags & SHFL_INFO_VOLUME) + { + pStat = &g_StatInformationGetVolume; + pStatFail = &g_StatInformationGetVolumeFail; + } + } + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[3].u.uint32 = length; + } + else + { + paParms[3].u.uint32 = 0; /* nothing read */ + } + } + } + break; + } + + /* Remove or rename object */ + case SHFL_FN_REMOVE: + { + pStat = &g_StatRemove; + pStatFail = &g_StatRemoveFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_REMOVE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_REMOVE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* path */ + PCSHFLSTRING pStrPath = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPath, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* flags */ + uint32_t const fFlags = paParms[2].u.uint32; + ASSERT_GUEST_STMT_BREAK(!(fFlags & ~(SHFL_REMOVE_FILE | SHFL_REMOVE_DIR | SHFL_REMOVE_SYMLINK)), + rc = VERR_INVALID_FLAGS); + + /* Execute the function. */ + rc = vbsfRemove(pClient, paParms[0].u.uint32, pStrPath, paParms[1].u.pointer.size, fFlags, SHFL_HANDLE_NIL); + break; + } + + case SHFL_FN_CLOSE_AND_REMOVE: + { + pStat = &g_StatCloseAndRemove; + pStatFail = &g_StatCloseAndRemoveFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CLOSE_AND_REMOVE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_CLOSE_AND_REMOVE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* path */ + PCSHFLSTRING pStrPath = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPath, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* flags */ + uint32_t const fFlags = paParms[2].u.uint32; + ASSERT_GUEST_STMT_BREAK(!(fFlags & ~(SHFL_REMOVE_FILE | SHFL_REMOVE_DIR | SHFL_REMOVE_SYMLINK)), + rc = VERR_INVALID_FLAGS); + SHFLHANDLE const hToClose = paParms[3].u.uint64; + ASSERT_GUEST_STMT_BREAK(hToClose != SHFL_HANDLE_ROOT, rc = VERR_INVALID_HANDLE); + + /* Execute the function. */ + rc = vbsfRemove(pClient, paParms[0].u.uint32, pStrPath, paParms[1].u.pointer.size, fFlags, hToClose); + break; + } + + case SHFL_FN_RENAME: + { + pStat = &g_StatRename; + pStatFail = &g_StatRenameFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_RENAME\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_RENAME) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* src */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* dest */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pSrc = (SHFLSTRING *)paParms[1].u.pointer.addr; + SHFLSTRING *pDest = (SHFLSTRING *)paParms[2].u.pointer.addr; + uint32_t flags = paParms[3].u.uint32; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pSrc, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || !ShflStringIsValidIn(pDest, paParms[2].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfRename (pClient, root, pSrc, pDest, flags); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + } + + case SHFL_FN_FLUSH: + { + pStat = &g_StatFlush; + pStatFail = &g_StatFlushFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_FLUSH\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_FLUSH) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else + { + /* Execute the function. */ + + rc = vbsfFlush (pClient, root, Handle); + + if (RT_SUCCESS(rc)) + { + /* Nothing to do */ + } + } + } + } break; + + case SHFL_FN_SET_UTF8: + { + pStatFail = pStat = &g_StatSetUtf8; + + pClient->fu32Flags |= SHFL_CF_UTF8; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_SYMLINK: + { + pStat = &g_StatSymlink; + pStatFail = &g_StatSymlinkFail; + Log(("SharedFolders host service: svnCall: SHFL_FN_SYMLINK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_SYMLINK) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* newPath */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* oldPath */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* info */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pNewPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + SHFLSTRING *pOldPath = (SHFLSTRING *)paParms[2].u.pointer.addr; + SHFLFSOBJINFO *pInfo = (SHFLFSOBJINFO *)paParms[3].u.pointer.addr; + uint32_t cbInfo = paParms[3].u.pointer.size; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pNewPath, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || !ShflStringIsValidIn(pOldPath, paParms[2].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || (cbInfo != sizeof(SHFLFSOBJINFO)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfSymlink (pClient, root, pNewPath, pOldPath, pInfo); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + } + break; + + case SHFL_FN_SET_SYMLINKS: + { + pStatFail = pStat = &g_StatSetSymlinks; + + pClient->fu32Flags |= SHFL_CF_SYMLINKS; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_QUERY_MAP_INFO: + { + pStatFail = pStat = &g_StatQueryMapInfo; + Log(("SharedFolders host service: svnCall: SHFL_FN_QUERY_MAP_INFO\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_QUERY_MAP_INFO); + ASSERT_GUEST_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); /* root */ + ASSERT_GUEST_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR); /* name */ + PSHFLSTRING pNameBuf = (PSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_BREAK(ShflStringIsValidOut(pNameBuf, paParms[1].u.pointer.size)); + ASSERT_GUEST_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_PTR); /* mountPoint */ + PSHFLSTRING pMntPtBuf = (PSHFLSTRING)paParms[2].u.pointer.addr; + ASSERT_GUEST_BREAK(ShflStringIsValidOut(pMntPtBuf, paParms[2].u.pointer.size)); + ASSERT_GUEST_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_64BIT); /* flags */ + ASSERT_GUEST_BREAK(!(paParms[3].u.uint64 & ~(SHFL_MIQF_DRIVE_LETTER | SHFL_MIQF_PATH))); /* flags */ + ASSERT_GUEST_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_32BIT); /* version */ + + /* Execute the function: */ + rc = vbsfMappingsQueryInfo(pClient, paParms[0].u.uint32, pNameBuf, pMntPtBuf, + &paParms[3].u.uint64, &paParms[4].u.uint32); + break; + } + + case SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES: + { + pStat = &g_StatWaitForMappingsChanges; + pStatFail = &g_StatWaitForMappingsChangesFail; + Log(("SharedFolders host service: svnCall: SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_WAIT_FOR_MAPPINGS_CHANGES); + ASSERT_GUEST_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); /* uFolderMappingsVersion */ + + /* Execute the function: */ + rc = vbsfMappingsWaitForChanges(pClient, callHandle, paParms, g_pHelpers->pfnIsCallRestored(callHandle)); + fAsynchronousProcessing = rc == VINF_HGCM_ASYNC_EXECUTE; + break; + } + + case SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS: + { + pStatFail = pStat = &g_StatCancelMappingsChangesWait; + Log(("SharedFolders host service: svnCall: SHFL_FN_CANCEL_WAIT_FOR_CHANGES\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_CANCEL_MAPPINGS_CHANGES_WAITS); + + /* Execute the function: */ + rc = vbsfMappingsCancelChangesWaits(pClient); + break; + } + + case SHFL_FN_SET_FILE_SIZE: + { + pStat = &g_StatSetFileSize; + pStatFail = &g_StatSetFileSizeFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_SET_FILE_SIZE\n")); + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_SET_FILE_SIZE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* id32Root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64Handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* cb64NewSize */ + + /* Execute the function: */ + rc = vbsfSetFileSize(pClient, paParms[0].u.uint32, paParms[1].u.uint64, paParms[2].u.uint64); + break; + } + + case SHFL_FN_QUERY_FEATURES: + { + pStat = pStatFail = &g_StatQueryFeatures; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_QUERY_FEATURES, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f64Features */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u32LastFunction */ + + /* Execute the function: */ + paParms[0].u.uint64 = SHFL_FEATURE_WRITE_UPDATES_OFFSET; + paParms[1].u.uint32 = SHFL_FN_LAST; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_COPY_FILE: + { + pStat = &g_StatCopyFile; + pStatFail = &g_StatCopyFileFail; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_COPY_FILE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* pStrPathSrc */ + PCSHFLSTRING pStrPathSrc = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPathSrc, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootDst */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* pStrPathDst */ + PCSHFLSTRING pStrPathDst = (PCSHFLSTRING)paParms[3].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPathDst, paParms[3].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f32Flags */ + ASSERT_GUEST_STMT_BREAK(paParms[4].u.uint32 == 0, rc = VERR_INVALID_FLAGS); + + /* Execute the function: */ + rc = vbsfCopyFile(pClient, paParms[0].u.uint32, pStrPathSrc, paParms[2].u.uint64, pStrPathDst, paParms[3].u.uint32); + break; + } + + + case SHFL_FN_COPY_FILE_PART: + { + pStat = &g_StatCopyFilePart; + pStatFail = &g_StatCopyFilePartFail; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_COPY_FILE_PART, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64HandleSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* off64Src */ + ASSERT_GUEST_STMT_BREAK((int64_t)paParms[2].u.uint64 >= 0, rc = VERR_NEGATIVE_SEEK); + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootDst */ + ASSERT_GUEST_STMT_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64HandleDst */ + ASSERT_GUEST_STMT_BREAK(paParms[5].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* off64Dst */ + ASSERT_GUEST_STMT_BREAK((int64_t)paParms[5].u.uint64 >= 0, rc = VERR_NEGATIVE_SEEK); + ASSERT_GUEST_STMT_BREAK(paParms[6].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* cb64ToCopy */ + ASSERT_GUEST_STMT_BREAK(paParms[6].u.uint64 < _1E, rc = VERR_OUT_OF_RANGE); + ASSERT_GUEST_STMT_BREAK(paParms[7].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f32Flags */ + ASSERT_GUEST_STMT_BREAK(paParms[7].u.uint32 == 0, rc = VERR_INVALID_FLAGS); + + /* Execute the function: */ + rc = vbsfCopyFilePart(pClient, + paParms[0].u.uint32, paParms[1].u.uint64, paParms[2].u.uint64, + paParms[3].u.uint32, paParms[4].u.uint64, paParms[5].u.uint64, + &paParms[6].u.uint64, paParms[7].u.uint64); + break; + } + + case SHFL_FN_SET_ERROR_STYLE: + { + pStatFail = pStat = &g_StatSetErrorStyle; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_SET_ERROR_STYLE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* enm32Style */ + ASSERT_GUEST_STMT_BREAK( paParms[0].u.uint32 > (uint32_t)kShflErrorStyle_Invalid + && paParms[0].u.uint32 < (uint32_t)kShflErrorStyle_End, rc = VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u32Reserved */ + ASSERT_GUEST_STMT_BREAK(paParms[1].u.uint32 == 0, rc = VERR_WRONG_PARAMETER_TYPE); + + /* Do the work: */ + pClient->enmErrorStyle = (uint8_t)paParms[0].u.uint32; + rc = VINF_SUCCESS; + break; + } + + default: + { + pStatFail = pStat = &g_StatUnknown; + rc = VERR_NOT_IMPLEMENTED; + break; + } + } + + LogFlow(("SharedFolders host service: svcCall: rc=%Rrc\n", rc)); + + if ( !fAsynchronousProcessing + || RT_FAILURE (rc)) + { + /* Complete the operation if it was unsuccessful or + * it was processed synchronously. + */ + g_pHelpers->pfnCallComplete (callHandle, rc); + } + +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS + /* Statistics: */ + uint64_t cTicks; + STAM_GET_TS(cTicks); + cTicks -= tsStart; + if (RT_SUCCESS(rc)) + STAM_REL_PROFILE_ADD_PERIOD(pStat, cTicks); + else + STAM_REL_PROFILE_ADD_PERIOD(pStatFail, cTicks); +#endif + + LogFlow(("\n")); /* Add a new line to differentiate between calls more easily. */ +} + +/* + * We differentiate between a function handler for the guest (svcCall) and one + * for the host. The guest is not allowed to add or remove mappings for obvious + * security reasons. + */ +static DECLCALLBACK(int) svcHostCall (void *, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + + Log(("svcHostCall: fn = %d, cParms = %d, pparms = %d\n", u32Function, cParms, paParms)); + +#ifdef DEBUG + uint32_t i; + + for (i = 0; i < cParms; i++) + { + /** @todo parameters other than 32 bit */ + Log((" pparms[%d]: type %d value %d\n", i, paParms[i].type, paParms[i].u.uint32)); + } +#endif + + switch (u32Function) + { + case SHFL_FN_ADD_MAPPING: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_ADD_MAPPING\n")); + LogRel(("SharedFolders host service: Adding host mapping\n")); + /* Verify parameter count and types. */ + if ( (cParms != SHFL_CPARMS_ADD_MAPPING) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* host folder path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* map name */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* fFlags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* auto mount point */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLSTRING *pHostPath = (SHFLSTRING *)paParms[0].u.pointer.addr; + SHFLSTRING *pMapName = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t fFlags = paParms[2].u.uint32; + SHFLSTRING *pAutoMountPoint = (SHFLSTRING *)paParms[3].u.pointer.addr; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pHostPath, paParms[0].u.pointer.size, false /*fUtf8Not16*/) + || !ShflStringIsValidIn(pMapName, paParms[1].u.pointer.size, false /*fUtf8Not16*/) + || !ShflStringIsValidIn(pAutoMountPoint, paParms[3].u.pointer.size, false /*fUtf8Not16*/) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + LogRel((" Host path '%ls', map name '%ls', %s, automount=%s, automntpnt=%ls, create_symlinks=%s, missing=%s\n", + pHostPath->String.utf16, pMapName->String.utf16, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_WRITABLE) ? "writable" : "read-only", + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_AUTOMOUNT) ? "true" : "false", + pAutoMountPoint->String.utf16, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_CREATE_SYMLINKS) ? "true" : "false", + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_MISSING) ? "true" : "false")); + + char *pszHostPath; + rc = RTUtf16ToUtf8(pHostPath->String.ucs2, &pszHostPath); + if (RT_SUCCESS(rc)) + { + /* Execute the function. */ + rc = vbsfMappingsAdd(pszHostPath, pMapName, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_WRITABLE), + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_AUTOMOUNT), + pAutoMountPoint, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_CREATE_SYMLINKS), + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_MISSING), + /* fPlaceholder = */ false); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + RTStrFree(pszHostPath); + } + } + } + if (RT_FAILURE(rc)) + LogRel(("SharedFolders host service: Adding host mapping failed with rc=%Rrc\n", rc)); + break; + } + + case SHFL_FN_REMOVE_MAPPING: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_REMOVE_MAPPING\n")); + LogRel(("SharedFolders host service: Removing host mapping '%ls'\n", + ((SHFLSTRING *)paParms[0].u.pointer.addr)->String.ucs2)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_REMOVE_MAPPING) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* folder name */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLSTRING *pString = (SHFLSTRING *)paParms[0].u.pointer.addr; + + /* Verify parameters values. */ + if (!ShflStringIsValidIn(pString, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMappingsRemove (pString); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + if (RT_FAILURE(rc)) + LogRel(("SharedFolders host service: Removing host mapping failed with rc=%Rrc\n", rc)); + break; + } + + case SHFL_FN_SET_STATUS_LED: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_SET_STATUS_LED\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_SET_STATUS_LED) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* folder name */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PPDMLED pLed = (PPDMLED)paParms[0].u.pointer.addr; + uint32_t cbLed = paParms[0].u.pointer.size; + + /* Verify parameters values. */ + if ( (cbLed != sizeof (PDMLED)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + g_pStatusLed = pLed; + rc = VINF_SUCCESS; + } + } + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlow(("SharedFolders host service: svcHostCall ended with rc=%Rrc\n", rc)); + return rc; +} + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable) +{ + int rc = VINF_SUCCESS; + + Log(("SharedFolders host service: VBoxHGCMSvcLoad: ptable = %p\n", ptable)); + + if (!RT_VALID_PTR(ptable)) + { + LogRelFunc(("SharedFolders host service: Bad value of ptable (%p)\n", ptable)); + rc = VERR_INVALID_PARAMETER; + } + else + { + Log(("SharedFolders host service: VBoxHGCMSvcLoad: ptable->cbSize = %u, ptable->u32Version = 0x%08X\n", + ptable->cbSize, ptable->u32Version)); + + if ( ptable->cbSize != sizeof (VBOXHGCMSVCFNTABLE) + || ptable->u32Version != VBOX_HGCM_SVC_VERSION) + { + LogRelFunc(("SharedFolders host service: Version mismatch while loading: ptable->cbSize = %u (should be %u), ptable->u32Version = 0x%08X (should be 0x%08X)\n", + ptable->cbSize, sizeof (VBOXHGCMSVCFNTABLE), ptable->u32Version, VBOX_HGCM_SVC_VERSION)); + rc = VERR_VERSION_MISMATCH; + } + else + { + g_pHelpers = ptable->pHelpers; + + ptable->cbClient = sizeof (SHFLCLIENTDATA); + + /* Map legacy clients to the kernel category. */ + ptable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL; + + /* Only 64K pending calls per kernel client, root gets 16K and regular users 1K. */ + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] = _64K; + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] = _16K; + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER] = _1K; + + /* Reduce the number of clients to SHFL_MAX_MAPPINGS + 2 in each category, + so the increased calls-per-client value causes less trouble. + ((64 + 2) * 3 * 65536 = 12 976 128) */ + for (uintptr_t i = 0; i < RT_ELEMENTS(ptable->acMaxClients); i++) + ptable->acMaxClients[i] = SHFL_MAX_MAPPINGS + 2; + + ptable->pfnUnload = svcUnload; + ptable->pfnConnect = svcConnect; + ptable->pfnDisconnect = svcDisconnect; + ptable->pfnCall = svcCall; + ptable->pfnHostCall = svcHostCall; + ptable->pfnSaveState = svcSaveState; + ptable->pfnLoadState = svcLoadState; + ptable->pfnNotify = NULL; + ptable->pvService = NULL; + } + + /* Init handle table */ + rc = vbsfInitHandleTable(); + AssertRC(rc); + + vbsfMappingInit(); + + /* Finally, register statistics if everything went well: */ + if (RT_SUCCESS(rc)) + { + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMappings, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAPPINGS successes", "/HGCM/VBoxSharedFolders/FnQueryMappings"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMappingsFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAPPINGS failures", "/HGCM/VBoxSharedFolders/FnQueryMappingsFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMapName, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAP_NAME", "/HGCM/VBoxSharedFolders/FnQueryMapName"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCreate, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/CREATE successes", "/HGCM/VBoxSharedFolders/FnCreate"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCreateFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/CREATE failures", "/HGCM/VBoxSharedFolders/FnCreateFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLookup, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/LOOKUP successes", "/HGCM/VBoxSharedFolders/FnLookup"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLookupFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/LOOKUP failures", "/HGCM/VBoxSharedFolders/FnLookupFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatClose, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE successes", "/HGCM/VBoxSharedFolders/FnClose"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE failures", "/HGCM/VBoxSharedFolders/FnCloseFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRead, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READ successes", "/HGCM/VBoxSharedFolders/FnRead"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READ failures", "/HGCM/VBoxSharedFolders/FnReadFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWrite, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WRITE successes", "/HGCM/VBoxSharedFolders/FnWrite"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWriteFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WRITE failures", "/HGCM/VBoxSharedFolders/FnWriteFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLock, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LOCK successes", "/HGCM/VBoxSharedFolders/FnLock"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLockFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LOCK failures", "/HGCM/VBoxSharedFolders/FnLockFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatList, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LIST successes", "/HGCM/VBoxSharedFolders/FnList"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatListFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LIST failures", "/HGCM/VBoxSharedFolders/FnListFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadLink, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READLINK successes", "/HGCM/VBoxSharedFolders/FnReadLink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadLinkFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READLINK failures", "/HGCM/VBoxSharedFolders/FnReadLinkFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolderOld, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER_OLD", "/HGCM/VBoxSharedFolders/FnMapFolderOld"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolder, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER successes", "/HGCM/VBoxSharedFolders/FnMapFolder"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolderFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER failures", "/HGCM/VBoxSharedFolders/FnMapFolderFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnmapFolder, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_UNMAP_FOLDER successes", "/HGCM/VBoxSharedFolders/FnUnmapFolder"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnmapFolderFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_UNMAP_FOLDER failures", "/HGCM/VBoxSharedFolders/FnUnmapFolderFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION early failures", "/HGCM/VBoxSharedFolders/FnInformationFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/FILE successes", "/HGCM/VBoxSharedFolders/FnInformationSetFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/FILE failures", "/HGCM/VBoxSharedFolders/FnInformationSetFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetSize, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/SIZE successes", "/HGCM/VBoxSharedFolders/FnInformationSetSize"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetSizeFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/SIZE failures", "/HGCM/VBoxSharedFolders/FnInformationSetSizeFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/FILE successes", "/HGCM/VBoxSharedFolders/FnInformationGetFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/FILE failures", "/HGCM/VBoxSharedFolders/FnInformationGetFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetVolume, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/VOLUME successes", "/HGCM/VBoxSharedFolders/FnInformationGetVolume"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetVolumeFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/VOLUME failures", "/HGCM/VBoxSharedFolders/FnInformationGetVolumeFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRemove, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_REMOVE successes", "/HGCM/VBoxSharedFolders/FnRemove"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRemoveFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_REMOVE failures", "/HGCM/VBoxSharedFolders/FnRemoveFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseAndRemove, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE_AND_REMOVE successes", "/HGCM/VBoxSharedFolders/FnCloseAndRemove"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseAndRemoveFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE_AND_REMOVE failures", "/HGCM/VBoxSharedFolders/FnCloseAndRemoveFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRename, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_RENAME successes", "/HGCM/VBoxSharedFolders/FnRename"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRenameFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_RENAME failures", "/HGCM/VBoxSharedFolders/FnRenameFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatFlush, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_FLUSH successes", "/HGCM/VBoxSharedFolders/FnFlush"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatFlushFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_FLUSH failures", "/HGCM/VBoxSharedFolders/FnFlushFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetErrorStyle, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_ERROR_STYLE", "/HGCM/VBoxSharedFolders/FnSetErrorStyle"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetUtf8, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_UTF8", "/HGCM/VBoxSharedFolders/FnSetUtf8"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSymlink, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SYMLINK successes", "/HGCM/VBoxSharedFolders/FnSymlink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSymlinkFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SYMLINK failures", "/HGCM/VBoxSharedFolders/FnSymlinkFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetSymlinks, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_SYMLINKS", "/HGCM/VBoxSharedFolders/FnSetSymlink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMapInfo, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAP_INFO", "/HGCM/VBoxSharedFolders/FnQueryMapInfo"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryFeatures, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_FEATURES", "/HGCM/VBoxSharedFolders/FnQueryFeatures"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE successes", "/HGCM/VBoxSharedFolders/FnCopyFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE failures", "/HGCM/VBoxSharedFolders/FnCopyFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFilePart, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE_PART successes", "/HGCM/VBoxSharedFolders/FnCopyFilePart"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFilePartFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE_PART failures", "/HGCM/VBoxSharedFolders/FnCopyFilePartFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWaitForMappingsChanges, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES successes", "/HGCM/VBoxSharedFolders/FnWaitForMappingsChanges"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWaitForMappingsChangesFail,STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES failures","/HGCM/VBoxSharedFolders/FnWaitForMappingsChangesFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCancelMappingsChangesWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS", "/HGCM/VBoxSharedFolders/FnCancelMappingsChangesWaits"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnknown, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_???", "/HGCM/VBoxSharedFolders/FnUnknown"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMsgStage1, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Time from VMMDev arrival to worker thread.","/HGCM/VBoxSharedFolders/MsgStage1"); + } + } + + return rc; +} + diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc new file mode 100644 index 00000000..05a89bc8 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxSharedFoldersSvc.rc $ */ +/** @file + * VBoxSharedFolders - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Shared Folders Host Service\0" + VALUE "InternalName", "VBoxSharedFolders\0" + VALUE "OriginalFilename", "VBoxSharedFolders.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/SharedFolders/mappings.cpp b/src/VBox/HostServices/SharedFolders/mappings.cpp new file mode 100644 index 00000000..6a5b80f0 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/mappings.cpp @@ -0,0 +1,1024 @@ +/* $Id: mappings.cpp $ */ +/** @file + * Shared Folders Service - Mappings support. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "mappings.h" +#include "vbsfpath.h" +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/list.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <VBox/AssertGuest.h> + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern PVBOXHGCMSVCHELPERS g_pHelpers; /* service.cpp */ + + +/* Shared folders order in the saved state and in the g_FolderMapping can differ. + * So a translation array of root handle is needed. + */ + +static MAPPING g_FolderMapping[SHFL_MAX_MAPPINGS]; +static SHFLROOT g_aIndexFromRoot[SHFL_MAX_MAPPINGS]; +/**< Array running parallel to g_aIndexFromRoot and which entries are increased + * as an root handle is added or removed. + * + * This helps the guest figuring out that a mapping may have been reconfigured + * or that saved state has been restored. Entry reuse is very likely given that + * vbsfRootHandleAdd() always starts searching at the start for an unused entry. + */ +static uint32_t g_auRootHandleVersions[SHFL_MAX_MAPPINGS]; +/** Version number that is increased for every change made. + * This is used by the automount guest service to wait for changes. + * @note This does not need saving, the guest should be woken up and refresh + * its sate when restored. */ +static uint32_t volatile g_uFolderMappingsVersion = 0; + + +/** For recording async vbsfMappingsWaitForChanges calls. */ +typedef struct SHFLMAPPINGSWAIT +{ + RTLISTNODE ListEntry; /**< List entry. */ + PSHFLCLIENTDATA pClient; /**< The client that's waiting. */ + VBOXHGCMCALLHANDLE hCall; /**< The call handle to signal completion with. */ + PVBOXHGCMSVCPARM pParm; /**< The 32-bit unsigned parameter to stuff g_uFolderMappingsVersion into. */ +} SHFLMAPPINGSWAIT; +/** Pointer to async mappings change wait. */ +typedef SHFLMAPPINGSWAIT *PSHFLMAPPINGSWAIT; +/** List head for clients waiting on mapping changes (SHFLMAPPINGSWAIT). */ +static RTLISTANCHOR g_MappingsChangeWaiters; +/** Number of clients waiting on mapping changes. + * We use this to limit the number of waiting calls the clients can make. */ +static uint32_t g_cMappingChangeWaiters = 0; +static void vbsfMappingsWakeupAllWaiters(void); + + +void vbsfMappingInit(void) +{ + unsigned root; + + for (root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + g_aIndexFromRoot[root] = SHFL_ROOT_NIL; + } + + RTListInit(&g_MappingsChangeWaiters); +} + +/** + * Called before loading mappings from saved state to drop the root IDs. + */ +void vbsfMappingLoadingStart(void) +{ + for (SHFLROOT idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + g_aIndexFromRoot[idRoot] = SHFL_ROOT_NIL; + + for (SHFLROOT i = 0; i < RT_ELEMENTS(g_FolderMapping); i++) + g_FolderMapping[i].fLoadedRootId = false; +} + +/** + * Called when a mapping is loaded to restore the root ID and make sure it + * exists. + * + * @returns VBox status code. + */ +int vbsfMappingLoaded(const MAPPING *pLoadedMapping, SHFLROOT root) +{ + /* Mapping loaded from the saved state with the 'root' index. Which means + * the guest uses the 'root' as root handle for this folder. + * Check whether there is the same mapping in g_FolderMapping and + * update the g_aIndexFromRoot. + * + * Also update the mapping properties, which were lost: cMappings. + */ + if (root >= SHFL_MAX_MAPPINGS) + { + return VERR_INVALID_PARAMETER; + } + + SHFLROOT i; + for (i = 0; i < RT_ELEMENTS(g_FolderMapping); i++) + { + MAPPING *pMapping = &g_FolderMapping[i]; + + /* Equal? */ + if ( pLoadedMapping->fValid == pMapping->fValid + && ShflStringSizeOfBuffer(pLoadedMapping->pMapName) == ShflStringSizeOfBuffer(pMapping->pMapName) + && memcmp(pLoadedMapping->pMapName, pMapping->pMapName, ShflStringSizeOfBuffer(pMapping->pMapName)) == 0) + { + Log(("vbsfMappingLoaded: root=%u i=%u (was %u) (%ls)\n", + root, i, g_aIndexFromRoot[root], pLoadedMapping->pMapName->String.utf16)); + + if (!pMapping->fLoadedRootId) + { + /* First encounter. */ + pMapping->fLoadedRootId = true; + + /* Update the mapping properties. */ + pMapping->cMappings = pLoadedMapping->cMappings; + } + else + { + /* When pMapping->fLoadedRootId is already true it means that another HGCM client uses the same mapping. */ + Assert(pMapping->cMappings > 1); + } + + /* Actual index is i. Remember that when the guest uses 'root' it is actually 'i'. */ + AssertLogRelMsg(g_aIndexFromRoot[root] == SHFL_ROOT_NIL, + ("idRoot=%u: current %u ([%s]), new %u (%ls [%s])\n", + root, g_aIndexFromRoot[root], g_FolderMapping[g_aIndexFromRoot[root]].pszFolderName, + i, pLoadedMapping->pMapName->String.utf16, pLoadedMapping->pszFolderName)); + g_aIndexFromRoot[root] = i; + + /* The mapping is known to the host and is used by the guest. + * No need for a 'placeholder'. + */ + return VINF_SUCCESS; + } + } + + /* No corresponding mapping on the host but the guest still uses it. + * Add a 'placeholder' mapping. + */ + LogRel2(("SharedFolders: mapping a placeholder for '%ls' -> '%s'\n", + pLoadedMapping->pMapName->String.ucs2, pLoadedMapping->pszFolderName)); + return vbsfMappingsAdd(pLoadedMapping->pszFolderName, pLoadedMapping->pMapName, + pLoadedMapping->fWritable, pLoadedMapping->fAutoMount, pLoadedMapping->pAutoMountPoint, + pLoadedMapping->fSymlinksCreate, /* fMissing = */ true, /* fPlaceholder = */ true); +} + +/** + * Called after loading mappings from saved state to make sure every mapping has + * a root ID. + */ +void vbsfMappingLoadingDone(void) +{ + for (SHFLROOT iMapping = 0; iMapping < RT_ELEMENTS(g_FolderMapping); iMapping++) + if (g_FolderMapping[iMapping].fValid) + { + AssertLogRel(g_FolderMapping[iMapping].pMapName); + AssertLogRel(g_FolderMapping[iMapping].pszFolderName); + + SHFLROOT idRoot; + for (idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + if (g_aIndexFromRoot[idRoot] == iMapping) + break; + if (idRoot >= RT_ELEMENTS(g_aIndexFromRoot)) + { + for (idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + if (g_aIndexFromRoot[idRoot] == SHFL_ROOT_NIL) + break; + if (idRoot < RT_ELEMENTS(g_aIndexFromRoot)) + g_aIndexFromRoot[idRoot] = iMapping; + else + LogRel(("SharedFolders: Warning! No free root ID entry for mapping #%u: %ls [%s]\n", iMapping, + g_FolderMapping[iMapping].pMapName->String.ucs2, g_FolderMapping[iMapping].pszFolderName)); + } + } + + /* Log the root ID mappings: */ + if (LogRelIs2Enabled()) + for (SHFLROOT idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + { + SHFLROOT const iMapping = g_aIndexFromRoot[idRoot]; + if (iMapping != SHFL_ROOT_NIL) + LogRel2(("SharedFolders: idRoot %u: iMapping #%u: %ls [%s]\n", idRoot, iMapping, + g_FolderMapping[iMapping].pMapName->String.ucs2, g_FolderMapping[iMapping].pszFolderName)); + } +} + + +MAPPING *vbsfMappingGetByRoot(SHFLROOT root) +{ + if (root < RT_ELEMENTS(g_aIndexFromRoot)) + { + SHFLROOT iMapping = g_aIndexFromRoot[root]; + + if ( iMapping != SHFL_ROOT_NIL + && iMapping < RT_ELEMENTS(g_FolderMapping)) + { + return &g_FolderMapping[iMapping]; + } + } + + return NULL; +} + +static SHFLROOT vbsfMappingGetRootFromIndex(SHFLROOT iMapping) +{ + unsigned root; + + for (root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (iMapping == g_aIndexFromRoot[root]) + { + return root; + } + } + + return SHFL_ROOT_NIL; +} + +static MAPPING *vbsfMappingGetByName(PRTUTF16 pwszName, SHFLROOT *pRoot) +{ + for (unsigned i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if ( g_FolderMapping[i].fValid + && !g_FolderMapping[i].fPlaceholder) /* Don't allow mapping placeholders. */ + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pwszName)) + { + SHFLROOT root = vbsfMappingGetRootFromIndex(i); + + if (root != SHFL_ROOT_NIL) + { + if (pRoot) + { + *pRoot = root; + } + return &g_FolderMapping[i]; + } + AssertFailed(); + } + } + } + return NULL; +} + +static void vbsfRootHandleAdd(SHFLROOT iMapping) +{ + for (unsigned root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (g_aIndexFromRoot[root] == SHFL_ROOT_NIL) + { + g_aIndexFromRoot[root] = iMapping; + g_auRootHandleVersions[root] += 1; + return; + } + } + + AssertFailed(); +} + +static void vbsfRootHandleRemove(SHFLROOT iMapping) +{ + unsigned cFound = 0; + + for (unsigned root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (g_aIndexFromRoot[root] == iMapping) + { + g_aIndexFromRoot[root] = SHFL_ROOT_NIL; + g_auRootHandleVersions[root] += 1; + Log(("vbsfRootHandleRemove: Removed root=%u (iMapping=%u)\n", root, iMapping)); + + /* Note! Do not stop here as g_aIndexFromRoot may (at least it could + prior to the introduction of fLoadedRootId) contain + duplicates after restoring save state. */ + cFound++; + } + } + + Assert(cFound > 0); RT_NOREF(cFound); +} + + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_ADD_MAPPING API. Located here as a form of API + * documentation. */ +void testMappingsAdd(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testMappingsAddBadParameters(hTest); + /* Add tests as required... */ +} +#endif +/* + * We are always executed from one specific HGCM thread. So thread safe. + */ +int vbsfMappingsAdd(const char *pszFolderName, PSHFLSTRING pMapName, bool fWritable, + bool fAutoMount, PSHFLSTRING pAutoMountPoint, bool fSymlinksCreate, bool fMissing, bool fPlaceholder) +{ + unsigned i; + + Assert(pszFolderName && pMapName); + + Log(("vbsfMappingsAdd %ls\n", pMapName->String.ucs2)); + + /* Check for duplicates, ignoring placeholders to give the GUI to change stuff at runtime. */ + /** @todo bird: Not entirely sure about ignoring placeholders, but you cannot + * trigger auto-umounting without ignoring them. */ + if (!fPlaceholder) + { + for (i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if ( g_FolderMapping[i].fValid + && !g_FolderMapping[i].fPlaceholder) + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pMapName->String.ucs2)) + { + AssertMsgFailed(("vbsfMappingsAdd: %ls mapping already exists!!\n", pMapName->String.ucs2)); + return VERR_ALREADY_EXISTS; + } + } + } + } + + for (i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if (g_FolderMapping[i].fValid == false) + { + /* Make sure the folder name is an absolute path, otherwise we're + likely to get into trouble with buffer sizes in vbsfPathGuestToHost. */ + char szAbsFolderName[RTPATH_MAX]; + int rc = vbsfPathAbs(NULL, pszFolderName, szAbsFolderName, sizeof(szAbsFolderName)); + AssertRCReturn(rc, rc); + + g_FolderMapping[i].pszFolderName = RTStrDup(szAbsFolderName); + g_FolderMapping[i].pMapName = ShflStringDup(pMapName); + g_FolderMapping[i].pAutoMountPoint = ShflStringDup(pAutoMountPoint); + if ( !g_FolderMapping[i].pszFolderName + || !g_FolderMapping[i].pMapName + || !g_FolderMapping[i].pAutoMountPoint) + { + RTStrFree(g_FolderMapping[i].pszFolderName); + RTMemFree(g_FolderMapping[i].pMapName); + RTMemFree(g_FolderMapping[i].pAutoMountPoint); + return VERR_NO_MEMORY; + } + + g_FolderMapping[i].fValid = true; + g_FolderMapping[i].cMappings = 0; + g_FolderMapping[i].fWritable = fWritable; + g_FolderMapping[i].fAutoMount = fAutoMount; + g_FolderMapping[i].fSymlinksCreate = fSymlinksCreate; + g_FolderMapping[i].fMissing = fMissing; + g_FolderMapping[i].fPlaceholder = fPlaceholder; + g_FolderMapping[i].fLoadedRootId = false; + + /* Check if the host file system is case sensitive */ + RTFSPROPERTIES prop; + prop.fCaseSensitive = false; /* Shut up MSC. */ + rc = RTFsQueryProperties(g_FolderMapping[i].pszFolderName, &prop); +#ifndef DEBUG_bird /* very annoying */ + AssertRC(rc); +#endif + g_FolderMapping[i].fHostCaseSensitive = RT_SUCCESS(rc) ? prop.fCaseSensitive : false; + vbsfRootHandleAdd(i); + vbsfMappingsWakeupAllWaiters(); + break; + } + } + if (i == SHFL_MAX_MAPPINGS) + { + AssertLogRelMsgFailed(("vbsfMappingsAdd: no more room to add mapping %s to %ls!!\n", pszFolderName, pMapName->String.ucs2)); + return VERR_TOO_MUCH_DATA; + } + + Log(("vbsfMappingsAdd: added mapping %s to %ls (slot %u, root %u)\n", + pszFolderName, pMapName->String.ucs2, i, vbsfMappingGetRootFromIndex(i))); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_REMOVE_MAPPING API. Located here as a form of API + * documentation. */ +void testMappingsRemove(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testMappingsRemoveBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfMappingsRemove(PSHFLSTRING pMapName) +{ + Assert(pMapName); + Log(("vbsfMappingsRemove %ls\n", pMapName->String.ucs2)); + + /* + * We must iterate thru the whole table as may have 0+ placeholder entries + * and 0-1 regular entries with the same name. Also, it is good to kick + * the guest automounter into action wrt to evicting placeholders. + */ + int rc = VERR_FILE_NOT_FOUND; + for (unsigned i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if (g_FolderMapping[i].fValid == true) + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pMapName->String.ucs2)) + { + if (g_FolderMapping[i].cMappings != 0) + { + LogRel2(("SharedFolders: removing '%ls' -> '%s'%s, which is still used by the guest\n", pMapName->String.ucs2, + g_FolderMapping[i].pszFolderName, g_FolderMapping[i].fPlaceholder ? " (again)" : "")); + g_FolderMapping[i].fMissing = true; + g_FolderMapping[i].fPlaceholder = true; + vbsfMappingsWakeupAllWaiters(); + rc = VINF_PERMISSION_DENIED; + } + else + { + /* pMapName can be the same as g_FolderMapping[i].pMapName when + * called from vbsfUnmapFolder, log it before deallocating the memory. */ + Log(("vbsfMappingsRemove: mapping %ls removed\n", pMapName->String.ucs2)); + bool fSame = g_FolderMapping[i].pMapName == pMapName; + + RTStrFree(g_FolderMapping[i].pszFolderName); + RTMemFree(g_FolderMapping[i].pMapName); + RTMemFree(g_FolderMapping[i].pAutoMountPoint); + g_FolderMapping[i].pszFolderName = NULL; + g_FolderMapping[i].pMapName = NULL; + g_FolderMapping[i].pAutoMountPoint = NULL; + g_FolderMapping[i].fValid = false; + vbsfRootHandleRemove(i); + vbsfMappingsWakeupAllWaiters(); + if (rc == VERR_FILE_NOT_FOUND) + rc = VINF_SUCCESS; + if (fSame) + break; + } + } + } + } + + return rc; +} + +const char* vbsfMappingsQueryHostRoot(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, NULL); + if (pFolderMapping->fMissing) + return NULL; + return pFolderMapping->pszFolderName; +} + +int vbsfMappingsQueryHostRootEx(SHFLROOT hRoot, const char **ppszRoot, uint32_t *pcbRootLen) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(hRoot); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + if (pFolderMapping->fMissing) + return VERR_NOT_FOUND; + if ( pFolderMapping->pszFolderName == NULL + || pFolderMapping->pszFolderName[0] == 0) + return VERR_NOT_FOUND; + *ppszRoot = pFolderMapping->pszFolderName; + *pcbRootLen = (uint32_t)strlen(pFolderMapping->pszFolderName); + return VINF_SUCCESS; +} + +bool vbsfIsGuestMappingCaseSensitive(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, false); + return pFolderMapping->fGuestCaseSensitive; +} + +bool vbsfIsHostMappingCaseSensitive(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, false); + return pFolderMapping->fHostCaseSensitive; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_QUERY_MAPPINGS API. Located here as a form of API + * documentation (or should it better be inline in include/VBox/shflsvc.h?) */ +void testMappingsQuery(RTTEST hTest) +{ + /* The API should return all mappings if we provide enough buffers. */ + testMappingsQuerySimple(hTest); + /* If we provide too few buffers that should be signalled correctly. */ + testMappingsQueryTooFewBuffers(hTest); + /* The SHFL_MF_AUTOMOUNT flag means return only auto-mounted mappings. */ + testMappingsQueryAutoMount(hTest); + /* The mappings return array must have numberOfMappings entries. */ + testMappingsQueryArrayWrongSize(hTest); +} +#endif +/** + * @note If pMappings / *pcMappings is smaller than the actual amount of + * mappings that *could* have been returned *pcMappings contains the + * required buffer size so that the caller can retry the operation if + * wanted. + */ +int vbsfMappingsQuery(PSHFLCLIENTDATA pClient, bool fOnlyAutoMounts, PSHFLMAPPING pMappings, uint32_t *pcMappings) +{ + LogFlow(("vbsfMappingsQuery: pClient = %p, pMappings = %p, pcMappings = %p, *pcMappings = %d\n", + pClient, pMappings, pcMappings, *pcMappings)); + + uint32_t const cMaxMappings = *pcMappings; + uint32_t idx = 0; + for (uint32_t i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + MAPPING *pFolderMapping = vbsfMappingGetByRoot(i); + if ( pFolderMapping != NULL + && pFolderMapping->fValid + && ( !fOnlyAutoMounts + || (pFolderMapping->fAutoMount && !pFolderMapping->fPlaceholder)) ) + { + if (idx < cMaxMappings) + { + pMappings[idx].u32Status = SHFL_MS_NEW; + pMappings[idx].root = i; + } + idx++; + } + } + + /* Return actual number of mappings, regardless whether the handed in + * mapping buffer was big enough. */ + /** @todo r=bird: This is non-standard interface behaviour. We return + * VERR_BUFFER_OVERFLOW or at least a VINF_BUFFER_OVERFLOW here. + * + * Guess this goes well along with ORing SHFL_MF_AUTOMOUNT into + * pClient->fu32Flags rather than passing it as fOnlyAutoMounts... + * Not amused by this. */ + *pcMappings = idx; + + RT_NOREF_PV(pClient); + LogFlow(("vbsfMappingsQuery: returns VINF_SUCCESS (idx=%u, cMaxMappings=%u)\n", idx, cMaxMappings)); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_QUERY_MAP_NAME API. Located here as a form of API + * documentation. */ +void testMappingsQueryName(RTTEST hTest) +{ + /* If we query an valid mapping it should be returned. */ + testMappingsQueryNameValid(hTest); + /* If we query an invalid mapping that should be signalled. */ + testMappingsQueryNameInvalid(hTest); + /* If we pass in a bad string buffer that should be detected. */ + testMappingsQueryNameBadBuffer(hTest); +} +#endif +int vbsfMappingsQueryName(PSHFLCLIENTDATA pClient, SHFLROOT root, SHFLSTRING *pString) +{ + LogFlow(("vbsfMappingsQuery: pClient = %p, root = %d, *pString = %p\n", pClient, root, pString)); + + int rc; + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping) + { + if (pFolderMapping->fValid) + { + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + rc = ShflStringCopyUtf16BufAsUtf8(pString, pFolderMapping->pMapName); + else + { + /* Not using ShlfStringCopy here as behaviour shouldn't change... */ + if (pString->u16Size < pFolderMapping->pMapName->u16Size) + { + Log(("vbsfMappingsQuery: passed string too short (%d < %d bytes)!\n", + pString->u16Size, pFolderMapping->pMapName->u16Size)); + rc = VERR_INVALID_PARAMETER; + } + else + { + pString->u16Length = pFolderMapping->pMapName->u16Length; + memcpy(pString->String.ucs2, pFolderMapping->pMapName->String.ucs2, + pFolderMapping->pMapName->u16Size); + rc = VINF_SUCCESS; + } + } + } + else + rc = VERR_FILE_NOT_FOUND; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlow(("vbsfMappingsQuery:Name return rc = %Rrc\n", rc)); + return rc; +} + +/** Queries fWritable flag for the given root. Returns error if the root is not accessible. + */ +int vbsfMappingsQueryWritable(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fWritable) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryWritable: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if ( pFolderMapping->fValid + && !pFolderMapping->fMissing) + *fWritable = pFolderMapping->fWritable; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQuery:Writable return rc = %Rrc\n", rc)); + + return rc; +} + +int vbsfMappingsQueryAutoMount(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fAutoMount) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryAutoMount: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if (pFolderMapping->fValid == true) + *fAutoMount = pFolderMapping->fAutoMount; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQueryAutoMount:Writable return rc = %Rrc\n", rc)); + + return rc; +} + +int vbsfMappingsQuerySymlinksCreate(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fSymlinksCreate) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryAutoMount: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if (pFolderMapping->fValid == true) + *fSymlinksCreate = pFolderMapping->fSymlinksCreate; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQueryAutoMount:SymlinksCreate return rc = %Rrc\n", rc)); + + return rc; +} + +/** + * Implements SHFL_FN_QUERY_MAP_INFO. + * @since VBox 6.0 + */ +int vbsfMappingsQueryInfo(PSHFLCLIENTDATA pClient, SHFLROOT root, PSHFLSTRING pNameBuf, PSHFLSTRING pMntPtBuf, + uint64_t *pfFlags, uint32_t *puVersion) +{ + LogFlow(("vbsfMappingsQueryInfo: pClient=%p root=%d\n", pClient, root)); + + /* Resolve the root handle. */ + int rc; + PMAPPING pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping) + { + if (pFolderMapping->fValid) + { + /* + * Produce the output. + */ + *puVersion = g_auRootHandleVersions[root]; + + *pfFlags = 0; + if (pFolderMapping->fWritable) + *pfFlags |= SHFL_MIF_WRITABLE; + if (pFolderMapping->fAutoMount) + *pfFlags |= SHFL_MIF_AUTO_MOUNT; + if (pFolderMapping->fHostCaseSensitive) + *pfFlags |= SHFL_MIF_HOST_ICASE; + if (pFolderMapping->fGuestCaseSensitive) + *pfFlags |= SHFL_MIF_GUEST_ICASE; + if (pFolderMapping->fSymlinksCreate) + *pfFlags |= SHFL_MIF_SYMLINK_CREATION; + + int rc2; + if (pClient->fu32Flags & SHFL_CF_UTF8) + { + rc = ShflStringCopyUtf16BufAsUtf8(pNameBuf, pFolderMapping->pMapName); + rc2 = ShflStringCopyUtf16BufAsUtf8(pMntPtBuf, pFolderMapping->pAutoMountPoint); + } + else + { + rc = ShflStringCopy(pNameBuf, pFolderMapping->pMapName, sizeof(RTUTF16)); + rc2 = ShflStringCopy(pMntPtBuf, pFolderMapping->pAutoMountPoint, sizeof(RTUTF16)); + } + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + rc = VERR_FILE_NOT_FOUND; + } + else + rc = VERR_INVALID_PARAMETER; + LogFlow(("vbsfMappingsQueryInfo: returns %Rrc\n", rc)); + return rc; +} + + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_MAP_FOLDER API. Located here as a form of API + * documentation. */ +void testMapFolder(RTTEST hTest) +{ + /* If we try to map a valid name we should get the root. */ + testMapFolderValid(hTest); + /* If we try to map a valid name we should get VERR_FILE_NOT_FOUND. */ + testMapFolderInvalid(hTest); + /* If we map a folder twice we can unmap it twice. + * Currently unmapping too often is only asserted but not signalled. */ + testMapFolderTwice(hTest); + /* The delimiter should be converted in e.g. file delete operations. */ + testMapFolderDelimiter(hTest); + /* Test case sensitive mapping by opening a file with the wrong case. */ + testMapFolderCaseSensitive(hTest); + /* Test case insensitive mapping by opening a file with the wrong case. */ + testMapFolderCaseInsensitive(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testMapFolderBadParameters(hTest); +} +#endif +int vbsfMapFolder(PSHFLCLIENTDATA pClient, PSHFLSTRING pszMapName, + RTUTF16 wcDelimiter, bool fCaseSensitive, SHFLROOT *pRoot) +{ + MAPPING *pFolderMapping = NULL; + + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + Log(("vbsfMapFolder %s\n", pszMapName->String.utf8)); + } + else + { + Log(("vbsfMapFolder %ls\n", pszMapName->String.ucs2)); + } + + AssertMsgReturn(wcDelimiter == '/' || wcDelimiter == '\\', + ("Invalid path delimiter: %#x\n", wcDelimiter), + VERR_INVALID_PARAMETER); + if (pClient->PathDelimiter == 0) + { + pClient->PathDelimiter = wcDelimiter; + } + else + { + AssertMsgReturn(wcDelimiter == pClient->PathDelimiter, + ("wcDelimiter=%#x PathDelimiter=%#x", wcDelimiter, pClient->PathDelimiter), + VERR_INVALID_PARAMETER); + } + + SHFLROOT RootTmp; + if (!pRoot) + pRoot = &RootTmp; + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + int rc; + PRTUTF16 utf16Name; + + rc = RTStrToUtf16((const char *) pszMapName->String.utf8, &utf16Name); + if (RT_FAILURE (rc)) + return rc; + + pFolderMapping = vbsfMappingGetByName(utf16Name, pRoot); + RTUtf16Free(utf16Name); + } + else + { + pFolderMapping = vbsfMappingGetByName(pszMapName->String.ucs2, pRoot); + } + + if (!pFolderMapping) + { + return VERR_FILE_NOT_FOUND; + } + + /* + * Check for reference count overflows and settings compatibility. + * For paranoid reasons, we don't allow modifying the case sensitivity + * setting while there are other mappings of a folder. + */ + AssertLogRelReturn(*pRoot < RT_ELEMENTS(pClient->acMappings), VERR_INTERNAL_ERROR); + AssertLogRelReturn(!pClient->fHasMappingCounts || pClient->acMappings[*pRoot] < _32K, VERR_TOO_MANY_OPENS); + ASSERT_GUEST_LOGREL_MSG_RETURN( pFolderMapping->cMappings == 0 + || pFolderMapping->fGuestCaseSensitive == fCaseSensitive, + ("Incompatible case sensitivity setting: %s: %u mappings, %ssenitive, requested %ssenitive!\n", + pFolderMapping->pszFolderName, pFolderMapping->cMappings, + pFolderMapping->fGuestCaseSensitive ? "" : "in", fCaseSensitive ? "" : "in"), + VERR_INCOMPATIBLE_CONFIG); + + /* + * Go ahead and map it. + */ + if (pClient->fHasMappingCounts) + pClient->acMappings[*pRoot] += 1; + pFolderMapping->cMappings++; + pFolderMapping->fGuestCaseSensitive = fCaseSensitive; + Log(("vbsfMmapFolder (cMappings=%u, acMappings[%u]=%u)\n", pFolderMapping->cMappings, *pRoot, pClient->acMappings[*pRoot])); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_UNMAP_FOLDER API. Located here as a form of API + * documentation. */ +void testUnmapFolder(RTTEST hTest) +{ + /* Unmapping a mapped folder should succeed. + * If the folder is not mapped this is only asserted, not signalled. */ + testUnmapFolderValid(hTest); + /* Unmapping a non-existant root should fail. */ + testUnmapFolderInvalid(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testUnmapFolderBadParameters(hTest); +} +#endif +int vbsfUnmapFolder(PSHFLCLIENTDATA pClient, SHFLROOT root) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping == NULL) + { + AssertFailed(); + return VERR_FILE_NOT_FOUND; + } + Assert(pFolderMapping->fValid == true && pFolderMapping->cMappings > 0); + + AssertLogRelReturn(root < RT_ELEMENTS(pClient->acMappings), VERR_INTERNAL_ERROR); + AssertLogRelReturn(!pClient->fHasMappingCounts || pClient->acMappings[root] > 0, VERR_INVALID_HANDLE); + + if (pClient->fHasMappingCounts) + pClient->acMappings[root] -= 1; + + if (pFolderMapping->cMappings > 0) + pFolderMapping->cMappings--; + + uint32_t const cMappings = pFolderMapping->cMappings; + if ( cMappings == 0 + && pFolderMapping->fPlaceholder) + { + /* Automatically remove, it is not used by the guest anymore. */ + Assert(pFolderMapping->fMissing); + LogRel2(("SharedFolders: unmapping placeholder '%ls' -> '%s'\n", + pFolderMapping->pMapName->String.ucs2, pFolderMapping->pszFolderName)); + vbsfMappingsRemove(pFolderMapping->pMapName); + } + + Log(("vbsfUnmapFolder (cMappings=%u, acMappings[%u]=%u)\n", cMappings, root, pClient->acMappings[root])); + return rc; +} + +/** + * SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES implementation. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on change. + * @retval VINF_TRY_AGAIN on resume. + * @retval VINF_HGCM_ASYNC_EXECUTE if waiting. + * @retval VERR_CANCELLED if cancelled. + * @retval VERR_OUT_OF_RESOURCES if there are too many pending waits. + * + * @param pClient The calling client. + * @param hCall The call handle. + * @param pParm The parameter (32-bit). + * @param fRestored Set if this is a call restored & resubmitted from saved + * state. + * @since VBox 6.0 + */ +int vbsfMappingsWaitForChanges(PSHFLCLIENTDATA pClient, VBOXHGCMCALLHANDLE hCall, PVBOXHGCMSVCPARM pParm, bool fRestored) +{ + /* + * Return immediately if the fodler mappings have changed since last call + * or if we got restored from saved state (adding of global folders, etc). + */ + uint32_t uCurVersion = g_uFolderMappingsVersion; + if ( pParm->u.uint32 != uCurVersion + || fRestored + || (pClient->fu32Flags & SHFL_CF_CANCEL_NEXT_WAIT) ) + { + int rc = VINF_SUCCESS; + if (pClient->fu32Flags & SHFL_CF_CANCEL_NEXT_WAIT) + { + pClient->fu32Flags &= ~SHFL_CF_CANCEL_NEXT_WAIT; + rc = VERR_CANCELLED; + } + else if (fRestored) + { + rc = VINF_TRY_AGAIN; + if (pParm->u.uint32 == uCurVersion) + uCurVersion = uCurVersion != UINT32_C(0x55555555) ? UINT32_C(0x55555555) : UINT32_C(0x99999999); + } + Log(("vbsfMappingsWaitForChanges: Version %#x -> %#x, returning %Rrc immediately.\n", pParm->u.uint32, uCurVersion, rc)); + pParm->u.uint32 = uCurVersion; + return rc; + } + + /* + * Setup a wait if we can. + */ + if (g_cMappingChangeWaiters < 64) + { + PSHFLMAPPINGSWAIT pWait = (PSHFLMAPPINGSWAIT)RTMemAlloc(sizeof(*pWait)); + if (pWait) + { + pWait->pClient = pClient; + pWait->hCall = hCall; + pWait->pParm = pParm; + + RTListAppend(&g_MappingsChangeWaiters, &pWait->ListEntry); + g_cMappingChangeWaiters += 1; + return VINF_HGCM_ASYNC_EXECUTE; + } + return VERR_NO_MEMORY; + } + LogRelMax(32, ("vbsfMappingsWaitForChanges: Too many threads waiting for changes!\n")); + return VERR_OUT_OF_RESOURCES; +} + +/** + * SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS implementation. + * + * @returns VINF_SUCCESS + * @param pClient The calling client to cancel all waits for. + * @since VBox 6.0 + */ +int vbsfMappingsCancelChangesWaits(PSHFLCLIENTDATA pClient) +{ + uint32_t const uCurVersion = g_uFolderMappingsVersion; + + PSHFLMAPPINGSWAIT pCur, pNext; + RTListForEachSafe(&g_MappingsChangeWaiters, pCur, pNext, SHFLMAPPINGSWAIT, ListEntry) + { + if (pCur->pClient == pClient) + { + RTListNodeRemove(&pCur->ListEntry); + pCur->pParm->u.uint32 = uCurVersion; + g_pHelpers->pfnCallComplete(pCur->hCall, VERR_CANCELLED); + RTMemFree(pCur); + } + } + + /* Set a flag to make sure the next SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES doesn't block. + This should help deal with races between this call and a thread about to do a wait. */ + pClient->fu32Flags |= SHFL_CF_CANCEL_NEXT_WAIT; + + return VINF_SUCCESS; +} + +/** + * Wakes up all clients waiting on + */ +static void vbsfMappingsWakeupAllWaiters(void) +{ + uint32_t const uCurVersion = ++g_uFolderMappingsVersion; + + PSHFLMAPPINGSWAIT pCur, pNext; + RTListForEachSafe(&g_MappingsChangeWaiters, pCur, pNext, SHFLMAPPINGSWAIT, ListEntry) + { + RTListNodeRemove(&pCur->ListEntry); + pCur->pParm->u.uint32 = uCurVersion; + g_pHelpers->pfnCallComplete(pCur->hCall, VERR_CANCELLED); + RTMemFree(pCur); + } +} + diff --git a/src/VBox/HostServices/SharedFolders/mappings.h b/src/VBox/HostServices/SharedFolders/mappings.h new file mode 100644 index 00000000..abeafb35 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/mappings.h @@ -0,0 +1,92 @@ +/* $Id: mappings.h $ */ +/** @file + * Shared folders service - Mappings header. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_mappings_h +#define VBOX_INCLUDED_SRC_SharedFolders_mappings_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include <VBox/shflsvc.h> + +typedef struct +{ + char *pszFolderName; /**< Directory at the host to share with the guest. */ + PSHFLSTRING pMapName; /**< Share name for the guest. */ + uint32_t cMappings; /**< Number of mappings. */ + bool fValid; /**< Mapping entry is used/valid. */ + bool fHostCaseSensitive; /**< Host file name space is case-sensitive. */ + bool fGuestCaseSensitive; /**< Guest file name space is case-sensitive. */ + bool fWritable; /**< Folder is writable for the guest. */ + PSHFLSTRING pAutoMountPoint; /**< Where the guest should try auto-mount the folder. */ + bool fAutoMount; /**< Folder will be auto-mounted by the guest. */ + bool fSymlinksCreate; /**< Guest is able to create symlinks. */ + bool fMissing; /**< Mapping not invalid but host path does not exist. + Any guest operation on such a folder fails! */ + bool fPlaceholder; /**< Mapping does not exist in the VM settings but the guest + still has. fMissing is always true for this mapping. */ + bool fLoadedRootId; /**< Set if vbsfMappingLoaded has found this mapping already. */ +} MAPPING; +/** Pointer to a MAPPING structure. */ +typedef MAPPING *PMAPPING; + +void vbsfMappingInit(void); + +bool vbsfMappingQuery(uint32_t iMapping, PMAPPING *pMapping); + +int vbsfMappingsAdd(const char *pszFolderName, PSHFLSTRING pMapName, bool fWritable, + bool fAutoMount, PSHFLSTRING pAutoMountPoint, bool fCreateSymlinks, bool fMissing, bool fPlaceholder); +int vbsfMappingsRemove(PSHFLSTRING pMapName); + +int vbsfMappingsQuery(PSHFLCLIENTDATA pClient, bool fOnlyAutoMounts, PSHFLMAPPING pMappings, uint32_t *pcMappings); +int vbsfMappingsQueryName(PSHFLCLIENTDATA pClient, SHFLROOT root, SHFLSTRING *pString); +int vbsfMappingsQueryWritable(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fWritable); +int vbsfMappingsQueryAutoMount(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fAutoMount); +int vbsfMappingsQuerySymlinksCreate(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fSymlinksCreate); +int vbsfMappingsQueryInfo(PSHFLCLIENTDATA pClient, SHFLROOT root, PSHFLSTRING pNameBuf, PSHFLSTRING pMntPtBuf, + uint64_t *pfFlags, uint32_t *puVersion); + +int vbsfMapFolder(PSHFLCLIENTDATA pClient, PSHFLSTRING pszMapName, RTUTF16 delimiter, + bool fCaseSensitive, SHFLROOT *pRoot); +int vbsfUnmapFolder(PSHFLCLIENTDATA pClient, SHFLROOT root); + +int vbsfMappingsWaitForChanges(PSHFLCLIENTDATA pClient, VBOXHGCMCALLHANDLE hCall, PVBOXHGCMSVCPARM pParm, bool fRestored); +int vbsfMappingsCancelChangesWaits(PSHFLCLIENTDATA pClient); + +const char* vbsfMappingsQueryHostRoot(SHFLROOT root); +int vbsfMappingsQueryHostRootEx(SHFLROOT hRoot, const char **ppszRoot, uint32_t *pcbRootLen); +bool vbsfIsGuestMappingCaseSensitive(SHFLROOT root); +bool vbsfIsHostMappingCaseSensitive(SHFLROOT root); + +void vbsfMappingLoadingStart(void); +int vbsfMappingLoaded(MAPPING const *pLoadedMapping, SHFLROOT root); +void vbsfMappingLoadingDone(void); +PMAPPING vbsfMappingGetByRoot(SHFLROOT root); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_mappings_h */ + diff --git a/src/VBox/HostServices/SharedFolders/shfl.h b/src/VBox/HostServices/SharedFolders/shfl.h new file mode 100644 index 00000000..04f83eb4 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shfl.h @@ -0,0 +1,89 @@ +/** @file + * Shared Folders: Main header - Common data and function prototypes definitions. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_shfl_h +#define VBOX_INCLUDED_SRC_SharedFolders_shfl_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/err.h> +#include <VBox/hgcmsvc.h> +#include <VBox/shflsvc.h> + +#include <VBox/log.h> + +/** Shared Folders client flags. + * @{ + */ +/** Client has queried mappings at least once and, therefore, the service can + * process its other requests too. */ +#define SHFL_CF_MAPPINGS_QUERIED (0x00000001) +/** Mappings have been changed since last query. */ +#define SHFL_CF_MAPPINGS_CHANGED (0x00000002) +/** Client uses UTF8 encoding, if not set then unicode 16 bit (UCS2) is used. */ +#define SHFL_CF_UTF8 (0x00000004) +/** Client both supports and wants to use symlinks. */ +#define SHFL_CF_SYMLINKS (0x00000008) +/** The call to SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES will return immediately + * because of a SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS call. */ +#define SHFL_CF_CANCEL_NEXT_WAIT (0x00000010) +/** @} */ + +/** + * @note This structure is dumped directly into the saved state, so care must be + * taken when extending it! + */ +typedef struct SHFLCLIENTDATA +{ + /** Client flags */ + uint32_t fu32Flags; + /** Path delimiter. */ + RTUTF16 PathDelimiter; + /** The error style, SHFLERRORSTYLE. */ + uint8_t enmErrorStyle; + /** Set if the client has mapping usage counts. + * This is for helping with saved state. */ + uint8_t fHasMappingCounts; + /** Mapping counts for each root ID so we can unmap the folders when the + * session disconnects or the VM resets. */ + uint16_t acMappings[SHFL_MAX_MAPPINGS]; +} SHFLCLIENTDATA; +/** Pointer to a SHFLCLIENTDATA structure. */ +typedef SHFLCLIENTDATA *PSHFLCLIENTDATA; + + +/** @def SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX + * Whether to make windows error style adjustments on a posix host. + * This always returns false on windows hosts. */ +#ifdef RT_OS_WINDOWS +# define SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(a_pClient) (false) +#else +# define SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(a_pClient) ((a_pClient)->enmErrorStyle == kShflErrorStyle_Windows) +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_shfl_h */ + diff --git a/src/VBox/HostServices/SharedFolders/shflhandle.cpp b/src/VBox/HostServices/SharedFolders/shflhandle.cpp new file mode 100644 index 00000000..abf734dc --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shflhandle.cpp @@ -0,0 +1,236 @@ +/* $Id: shflhandle.cpp $ */ +/** @file + * Shared Folders Service - Handles helper functions. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include "shflhandle.h" +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Very basic and primitive handle management. Should be sufficient for our needs. + * Handle allocation can be rather slow, but at least lookup is fast. + */ +typedef struct +{ + uint32_t uFlags; + uintptr_t pvUserData; + PSHFLCLIENTDATA pClient; +} SHFLINTHANDLE, *PSHFLINTHANDLE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static SHFLINTHANDLE *g_pHandles = NULL; +static int32_t gLastHandleIndex = 0; +static RTCRITSECT gLock; + + +int vbsfInitHandleTable() +{ + g_pHandles = (SHFLINTHANDLE *)RTMemAllocZ (sizeof (SHFLINTHANDLE) * SHFLHANDLE_MAX); + if (!g_pHandles) + { + AssertFailed(); + return VERR_NO_MEMORY; + } + + /* Never return handle 0 */ + g_pHandles[0].uFlags = SHFL_HF_TYPE_DONTUSE; + gLastHandleIndex = 1; + + return RTCritSectInit(&gLock); +} + +int vbsfFreeHandleTable() +{ + if (g_pHandles) + RTMemFree(g_pHandles); + + g_pHandles = NULL; + + if (RTCritSectIsInitialized(&gLock)) + RTCritSectDelete(&gLock); + + return VINF_SUCCESS; +} + +SHFLHANDLE vbsfAllocHandle(PSHFLCLIENTDATA pClient, uint32_t uType, + uintptr_t pvUserData) +{ + SHFLHANDLE handle; + + Assert((uType & SHFL_HF_TYPE_MASK) != 0 && pvUserData); + + RTCritSectEnter(&gLock); + + /* Find next free handle */ + if (gLastHandleIndex >= SHFLHANDLE_MAX-1) + gLastHandleIndex = 1; + + /* Nice linear search */ + for(handle=gLastHandleIndex;handle<SHFLHANDLE_MAX;handle++) + { + if (g_pHandles[handle].pvUserData == 0) + { + gLastHandleIndex = handle; + break; + } + } + + if (handle == SHFLHANDLE_MAX) + { + /* Try once more from the start */ + for(handle=1;handle<SHFLHANDLE_MAX;handle++) + { + if (g_pHandles[handle].pvUserData == 0) + { + gLastHandleIndex = handle; + break; + } + } + if (handle == SHFLHANDLE_MAX) + { + /* Out of handles */ + RTCritSectLeave(&gLock); + AssertFailed(); + return SHFL_HANDLE_NIL; + } + } + g_pHandles[handle].uFlags = (uType & SHFL_HF_TYPE_MASK) | SHFL_HF_VALID; + g_pHandles[handle].pvUserData = pvUserData; + g_pHandles[handle].pClient = pClient; + + gLastHandleIndex++; + + RTCritSectLeave(&gLock); + + return handle; +} + +static int vbsfFreeHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle) +{ + if ( handle < SHFLHANDLE_MAX + && (g_pHandles[handle].uFlags & SHFL_HF_VALID) + && g_pHandles[handle].pClient == pClient) + { + g_pHandles[handle].uFlags = 0; + g_pHandles[handle].pvUserData = 0; + g_pHandles[handle].pClient = 0; + return VINF_SUCCESS; + } + return VERR_INVALID_HANDLE; +} + +uintptr_t vbsfQueryHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle, + uint32_t uType) +{ + if ( handle < SHFLHANDLE_MAX + && (g_pHandles[handle].uFlags & SHFL_HF_VALID) + && g_pHandles[handle].pClient == pClient) + { + Assert((uType & SHFL_HF_TYPE_MASK) != 0); + + if (g_pHandles[handle].uFlags & uType) + return g_pHandles[handle].pvUserData; + } + return 0; +} + +SHFLFILEHANDLE *vbsfQueryFileHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle) +{ + return (SHFLFILEHANDLE *)vbsfQueryHandle(pClient, handle, + SHFL_HF_TYPE_FILE); +} + +SHFLFILEHANDLE *vbsfQueryDirHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle) +{ + return (SHFLFILEHANDLE *)vbsfQueryHandle(pClient, handle, + SHFL_HF_TYPE_DIR); +} + +uint32_t vbsfQueryHandleType(PSHFLCLIENTDATA pClient, SHFLHANDLE handle) +{ + if ( handle < SHFLHANDLE_MAX + && (g_pHandles[handle].uFlags & SHFL_HF_VALID) + && g_pHandles[handle].pClient == pClient) + return g_pHandles[handle].uFlags & SHFL_HF_TYPE_MASK; + + return 0; +} + +SHFLHANDLE vbsfAllocDirHandle(PSHFLCLIENTDATA pClient) +{ + SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)RTMemAllocZ (sizeof (SHFLFILEHANDLE)); + + if (pHandle) + { + pHandle->Header.u32Flags = SHFL_HF_TYPE_DIR; + return vbsfAllocHandle(pClient, pHandle->Header.u32Flags, + (uintptr_t)pHandle); + } + + return SHFL_HANDLE_NIL; +} + +SHFLHANDLE vbsfAllocFileHandle(PSHFLCLIENTDATA pClient) +{ + SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)RTMemAllocZ (sizeof (SHFLFILEHANDLE)); + + if (pHandle) + { + pHandle->Header.u32Flags = SHFL_HF_TYPE_FILE; + return vbsfAllocHandle(pClient, pHandle->Header.u32Flags, + (uintptr_t)pHandle); + } + + return SHFL_HANDLE_NIL; +} + +void vbsfFreeFileHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE hHandle) +{ + SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(pClient, + hHandle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE); + + if (pHandle) + { + vbsfFreeHandle(pClient, hHandle); + RTMemFree (pHandle); + } + else + AssertFailed(); +} + diff --git a/src/VBox/HostServices/SharedFolders/shflhandle.h b/src/VBox/HostServices/SharedFolders/shflhandle.h new file mode 100644 index 00000000..c4a50fbf --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shflhandle.h @@ -0,0 +1,91 @@ +/* $Id: shflhandle.h $ */ +/** @file + * Shared Folders Host Service - Handles helper functions header. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h +#define VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include <VBox/shflsvc.h> +#include <iprt/dir.h> + +#define SHFL_HF_TYPE_MASK (0x000000FF) +#define SHFL_HF_TYPE_DIR (0x00000001) +#define SHFL_HF_TYPE_FILE (0x00000002) +#define SHFL_HF_TYPE_VOLUME (0x00000004) +#define SHFL_HF_TYPE_DONTUSE (0x00000080) + +#define SHFL_HF_VALID (0x80000000) + +#define SHFLHANDLE_MAX (4096) + +typedef struct _SHFLHANDLEHDR +{ + uint32_t u32Flags; +} SHFLHANDLEHDR; + +#define ShflHandleType(__Handle) BIT_FLAG(((SHFLHANDLEHDR *)(__Handle))->u32Flags, SHFL_HF_TYPE_MASK) + +typedef struct _SHFLFILEHANDLE +{ + SHFLHANDLEHDR Header; + SHFLROOT root; /* Where the handle has been opened. */ + union + { + struct + { + RTFILE Handle; + uint64_t fOpenFlags; /**< RTFILE_O_XXX. */ + } file; + struct + { + RTDIR Handle; + RTDIR SearchHandle; + PRTDIRENTRYEX pLastValidEntry; /**< last found file in a directory search */ + } dir; + }; +} SHFLFILEHANDLE; + + +SHFLHANDLE vbsfAllocDirHandle(PSHFLCLIENTDATA pClient); +SHFLHANDLE vbsfAllocFileHandle(PSHFLCLIENTDATA pClient); +void vbsfFreeFileHandle (PSHFLCLIENTDATA pClient, SHFLHANDLE hHandle); + + +int vbsfInitHandleTable(); +int vbsfFreeHandleTable(); +SHFLHANDLE vbsfAllocHandle(PSHFLCLIENTDATA pClient, uint32_t uType, + uintptr_t pvUserData); +SHFLFILEHANDLE *vbsfQueryFileHandle(PSHFLCLIENTDATA pClient, + SHFLHANDLE handle); +SHFLFILEHANDLE *vbsfQueryDirHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle); +uint32_t vbsfQueryHandleType(PSHFLCLIENTDATA pClient, + SHFLHANDLE handle); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h */ diff --git a/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk b/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk new file mode 100644 index 00000000..c451d7a1 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk @@ -0,0 +1,104 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Folders Host Service testcases. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Structure size testcase. +# +PROGRAMS += tstShflSizes +TESTING += $(tstShflSizes_0_OUTDIR)/tstShflSizes.run +ifndef VBOX_ONLY_SDK + ifeq ($(KBUILD_TARGET),$(KBUILD_HOST)) + if1of ($(KBUILD_TARGET_ARCH).$(KBUILD_HOST_ARCH), x86.x86 amd64.amd64 x86.amd64) + OTHERS += $(tstShflSizes_0_OUTDIR)/tstShflSizes.run + endif + endif +endif +tstShflSizes_TEMPLATE = VBoxR3AutoTest +tstShflSizes_DEFS = VBOX_WITH_HGCM +tstShflSizes_SOURCES = tstShflSizes.cpp +tstShflSizes_CLEAN = $(tstShflSizes_0_OUTDIR)/tstShflSizes.run + +$$(tstShflSizes_0_OUTDIR)/tstShflSizes.run: $$(tstShflSizes_1_STAGE_TARGET) + $(tstShflSizes_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + +ifdef VBOX_WITH_TESTCASES + # + # Case conversion testcase. + # + PROGRAMS += tstShflCase + tstShflCase_TEMPLATE = VBoxR3TstExe + tstShflCase_DEFS = VBOX_WITH_HGCM + tstShflCase_SOURCES = tstShflCase.cpp + tstShflCase_LIBS = $(LIB_RUNTIME) + + # + # HGCM service testcase. + # + + PROGRAMS += tstSharedFolderService + tstSharedFolderService_TEMPLATE = VBoxR3TstExe + tstSharedFolderService_DEFS = VBOX_WITH_HGCM UNITTEST + tstSharedFolderService_INCS = .. + tstSharedFolderService_SOURCES = \ + tstSharedFolderService.cpp \ + ../mappings.cpp \ + ../VBoxSharedFoldersSvc.cpp \ + ../shflhandle.cpp \ + ../vbsfpathabs.cpp \ + ../vbsfpath.cpp \ + ../vbsf.cpp + tstSharedFolderService_LDFLAGS.darwin = \ + -framework Carbon + tstSharedFolderService_LIBS = $(LIB_RUNTIME) + + if 0 # Cannot define two RT_OS_XXX macros! + # As there are differences between the Windows build of the service and others, + # we do an additional build with RT_OS_WINDOWS defined on non-Windows targets. + PROGRAMS += \ + tstSharedFolderService \ + $(if $(eq $(KBUILD_TARGET),win),,tstSharedFolderService-win) + tstSharedFolderService-win_TEMPLATE = $(tstSharedFolderService_TEMPLATE) + tstSharedFolderService-win_DEFS = \ + $(tstSharedFolderService_DEFS) \ + RT_OS_WINDOWS + tstSharedFolderService-win_INCS = $(tstSharedFolderService_INCS) + tstSharedFolderService-win_SOURCES = $(tstSharedFolderService_SOURCES) + tstSharedFolderService-win_LDFLAGS.darwin = \ + $(tstSharedFolderService_LDFLAGS.darwin) + tstSharedFolderService-win_LIBS = $(tstSharedFolderService_LIBS) + endif + +endif # VBOX_WITH_TESTCASES + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp new file mode 100644 index 00000000..3f105284 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp @@ -0,0 +1,1368 @@ +/* $Id: tstSharedFolderService.cpp $ */ +/** @file + * Testcase for the shared folder service vbsf API. + * + * Note that this is still very threadbare (there is an awful lot which should + * really be tested, but it already took too long to produce this much). The + * idea is that anyone who makes changes to the shared folders service and who + * cares about unit testing them should add tests to the skeleton framework to + * exercise the bits they change before and after changing them. + */ + +/* + * 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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#include "tstSharedFolderService.h" +#include "vbsf.h" + +#include <iprt/fs.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/symlink.h> +#include <iprt/stream.h> +#include <iprt/test.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "teststubs.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + + +/********************************************************************************************************************************* +* Declarations * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); + + +/********************************************************************************************************************************* +* Helpers * +*********************************************************************************************************************************/ + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) stamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, + STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va) +{ + RT_NOREF(pvInstance, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, va); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) stamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va) +{ + RT_NOREF(pvInstance, pszPatFmt, va); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) infoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser) +{ + RT_NOREF(pvInstance, pszName, pszDesc, pfnHandler, pvUser); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) infoDeregister(void *pvInstance, const char *pszName) +{ + RT_NOREF(pvInstance, pszName); + return VINF_SUCCESS; +} + +/** + * Initialise the HGCM service table as much as we need to start the + * service + * @param pTable the table to initialise + */ +void initTable(VBOXHGCMSVCFNTABLE *pTable, VBOXHGCMSVCHELPERS *pHelpers) +{ + pTable->cbSize = sizeof (VBOXHGCMSVCFNTABLE); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + pHelpers->pfnCallComplete = callComplete; + pHelpers->pfnStamRegisterV = stamRegisterV; + pHelpers->pfnStamDeregisterV = stamDeregisterV; + pHelpers->pfnInfoRegister = infoRegister; + pHelpers->pfnInfoDeregister = infoDeregister; + pTable->pHelpers = pHelpers; +} + +#define LLUIFY(a) ((unsigned long long)(a)) + +static void bufferFromPath(char *pszDst, size_t cbDst, const char *pcszSrc) +{ + RTStrCopy(pszDst, cbDst, pcszSrc); + uintptr_t const uDstEnd = (uintptr_t)&pszDst[cbDst]; + for (char *psz = pszDst; psz && (uintptr_t)psz < uDstEnd; ++psz) + if (*psz == '\\') + *psz = '/'; +} + +#define ARRAY_FROM_PATH(a, b) \ + do { \ + char *p = (a); NOREF(p); \ + Assert((a) == p); /* Constant parameter */ \ + Assert(sizeof((a)) > 0); \ + bufferFromPath(a, sizeof(a), b); \ + } while (0) + + +/********************************************************************************************************************************* +* Stub functions and data * +*********************************************************************************************************************************/ +static bool g_fFailIfNotLowercase = false; + +static RTDIR g_testRTDirClose_hDir = NIL_RTDIR; + +extern int testRTDirClose(RTDIR hDir) +{ + /* RTPrintf("%s: hDir=%p\n", __PRETTY_FUNCTION__, hDir); */ + g_testRTDirClose_hDir = hDir; + return VINF_SUCCESS; +} + +static char g_testRTDirCreate_szPath[256]; +//static RTFMODE testRTDirCreateMode; - unused + +extern int testRTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate) +{ + RT_NOREF2(fMode, fCreate); + /* RTPrintf("%s: pszPath=%s, fMode=0x%llx\n", __PRETTY_FUNCTION__, pszPath, + LLUIFY(fMode)); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirCreate_szPath, pszPath); + return 0; +} + +static char g_testRTDirOpen_szName[256]; +static struct TESTDIRHANDLE +{ + int iEntry; + int iDir; +} g_aTestDirHandles[4]; +static int g_iNextDirHandle = 0; +static RTDIR g_testRTDirOpen_hDir; + +extern int testRTDirOpen(RTDIR *phDir, const char *pszPath) +{ + /* RTPrintf("%s: pszPath=%s\n", __PRETTY_FUNCTION__, pszPath); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirOpen_szName, pszPath); + *phDir = g_testRTDirOpen_hDir; + g_testRTDirOpen_hDir = NIL_RTDIR; + if (!*phDir && g_fFailIfNotLowercase) + *phDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + if (*phDir) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)*phDir; + pRealDir->iEntry = 0; + pRealDir->iDir = 0; + const char *pszSlash = pszPath - 1; + while ((pszSlash = strpbrk(pszSlash + 1, "\\/")) != NULL) + pRealDir->iDir += 1; + /*RTPrintf("opendir %s = %d \n", pszPath, pRealDir->iDir);*/ + } + return VINF_SUCCESS; +} + +/** @todo Do something useful with the last two arguments. */ +extern int testRTDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER, uint32_t) +{ + /* RTPrintf("%s: pszPath=%s\n", __PRETTY_FUNCTION__, pszPath); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirOpen_szName, pszPath); + *phDir = g_testRTDirOpen_hDir; + g_testRTDirOpen_hDir = NIL_RTDIR; + if (!*phDir && g_fFailIfNotLowercase) + *phDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + if (*phDir) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)*phDir; + pRealDir->iEntry = 0; + pRealDir->iDir = 0; + const char *pszSlash = pszPath - 1; + while ((pszSlash = strpbrk(pszSlash + 1, "\\/")) != NULL) + pRealDir->iDir += 1; + pRealDir->iDir -= 1; + /*RTPrintf("openfiltered %s = %d\n", pszPath, pRealDir->iDir);*/ + } + return VINF_SUCCESS; +} + +static RTDIR g_testRTDirQueryInfo_hDir; +static RTTIMESPEC g_testRTDirQueryInfo_ATime; + +extern int testRTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF1(enmAdditionalAttribs); + /* RTPrintf("%s: hDir=%p, enmAdditionalAttribs=0x%llx\n", __PRETTY_FUNCTION__, + hDir, LLUIFY(enmAdditionalAttribs)); */ + g_testRTDirQueryInfo_hDir = hDir; + RT_ZERO(*pObjInfo); + pObjInfo->AccessTime = g_testRTDirQueryInfo_ATime; + RT_ZERO(g_testRTDirQueryInfo_ATime); + return VINF_SUCCESS; +} + +extern int testRTDirRemove(const char *pszPath) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +static RTDIR g_testRTDirReadEx_hDir; + +extern int testRTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF4(pDirEntry, pcbDirEntry, enmAdditionalAttribs, fFlags); + /* RTPrintf("%s: hDir=%p, pcbDirEntry=%d, enmAdditionalAttribs=%llu, fFlags=0x%llx\n", + __PRETTY_FUNCTION__, hDir, pcbDirEntry ? (int) *pcbDirEntry : -1, + LLUIFY(enmAdditionalAttribs), LLUIFY(fFlags)); */ + g_testRTDirReadEx_hDir = hDir; + if (g_fFailIfNotLowercase && hDir != NIL_RTDIR) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)hDir; + if (pRealDir->iDir == 2) /* /test/mapping/ */ + { + if (pRealDir->iEntry == 0) + { + pRealDir->iEntry++; + RT_ZERO(*pDirEntry); + pDirEntry->Info.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH; + pDirEntry->cbName = 4; + pDirEntry->cwcShortName = 4; + strcpy(pDirEntry->szName, "test"); + RTUtf16CopyAscii(pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName), "test"); + /*RTPrintf("readdir: 'test'\n");*/ + return VINF_SUCCESS; + } + } + else if (pRealDir->iDir == 3) /* /test/mapping/test/ */ + { + if (pRealDir->iEntry == 0) + { + pRealDir->iEntry++; + RT_ZERO(*pDirEntry); + pDirEntry->Info.Attr.fMode = RTFS_TYPE_FILE | RTFS_DOS_NT_NORMAL | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH; + pDirEntry->cbName = 4; + pDirEntry->cwcShortName = 4; + strcpy(pDirEntry->szName, "file"); + RTUtf16CopyAscii(pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName), "file"); + /*RTPrintf("readdir: 'file'\n");*/ + return VINF_SUCCESS; + } + } + /*else RTPrintf("%s: iDir=%d\n", pRealDir->iDir);*/ + } + return VERR_NO_MORE_FILES; +} + +static uint64_t g_testRTDirSetMode_fMode; + +extern int testRTDirSetMode(RTDIR hDir, RTFMODE fMode) +{ + RT_NOREF1(hDir); + /* RTPrintf("%s: fMode=%llu\n", __PRETTY_FUNCTION__, LLUIFY(fMode)); */ + g_testRTDirSetMode_fMode = fMode; + return VINF_SUCCESS; +} + +static RTTIMESPEC g_testRTDirSetTimes_ATime; + +extern int testRTDirSetTimes(RTDIR hDir, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RT_NOREF4(hDir, pModificationTime, pChangeTime, pBirthTime); + /* RTPrintf("%s: hDir=%p, *pAccessTime=%lli, *pModificationTime=%lli, *pChangeTime=%lli, *pBirthTime=%lli\n", + __PRETTY_FUNCTION__, hDir, + pAccessTime ? (long long)RTTimeSpecGetNano(pAccessTime) : -1, + pModificationTime + ? (long long)RTTimeSpecGetNano(pModificationTime) : -1, + pChangeTime ? (long long)RTTimeSpecGetNano(pChangeTime) : -1, + pBirthTime ? (long long)RTTimeSpecGetNano(pBirthTime) : -1); */ + if (pAccessTime) + g_testRTDirSetTimes_ATime = *pAccessTime; + else + RT_ZERO(g_testRTDirSetTimes_ATime); + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileClose_hFile; + +extern int testRTFileClose(RTFILE File) +{ + /* RTPrintf("%s: File=%p\n", __PRETTY_FUNCTION__, File); */ + g_testRTFileClose_hFile = File; + return 0; +} + +extern int testRTFileDelete(const char *pszFilename) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszFilename, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +static RTFILE g_testRTFileFlush_hFile; + +extern int testRTFileFlush(RTFILE File) +{ + /* RTPrintf("%s: File=%p\n", __PRETTY_FUNCTION__, File); */ + g_testRTFileFlush_hFile = File; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileLock_hFile; +static unsigned g_testRTFileLock_fLock; +static int64_t g_testRTFileLock_offLock; +static uint64_t g_testRTFileLock_cbLock; + +extern int testRTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + /* RTPrintf("%s: hFile=%p, fLock=%u, offLock=%lli, cbLock=%llu\n", + __PRETTY_FUNCTION__, hFile, fLock, (long long) offLock, + LLUIFY(cbLock)); */ + g_testRTFileLock_hFile = hFile; + g_testRTFileLock_fLock = fLock; + g_testRTFileLock_offLock = offLock; + g_testRTFileLock_cbLock = cbLock; + return VINF_SUCCESS; +} + +static char g_testRTFileOpen_szName[256]; +static uint64_t g_testRTFileOpen_fOpen; +static RTFILE g_testRTFileOpen_hFile; + +extern int testRTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken) +{ + /* RTPrintf("%s, pszFilename=%s, fOpen=0x%llx\n", __PRETTY_FUNCTION__, + pszFilename, LLUIFY(fOpen)); */ + ARRAY_FROM_PATH(g_testRTFileOpen_szName, pszFilename); + g_testRTFileOpen_fOpen = fOpen; + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszFilename, "/\\"))) + return VERR_FILE_NOT_FOUND; + *phFile = g_testRTFileOpen_hFile; + *penmActionTaken = RTFILEACTION_CREATED; + g_testRTFileOpen_hFile = 0; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileQueryInfo_hFile; +static RTTIMESPEC g_testRTFileQueryInfo_ATime; +static uint32_t g_testRTFileQueryInfo_fMode; + +extern int testRTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF1(enmAdditionalAttribs); + /* RTPrintf("%s, hFile=%p, enmAdditionalAttribs=0x%llx\n", + __PRETTY_FUNCTION__, hFile, LLUIFY(enmAdditionalAttribs)); */ + g_testRTFileQueryInfo_hFile = hFile; + RT_ZERO(*pObjInfo); + pObjInfo->AccessTime = g_testRTFileQueryInfo_ATime; + RT_ZERO(g_testRTDirQueryInfo_ATime); + pObjInfo->Attr.fMode = g_testRTFileQueryInfo_fMode; + g_testRTFileQueryInfo_fMode = 0; + return VINF_SUCCESS; +} + +static const char *g_testRTFileRead_pszData; + +extern int testRTFileRead(RTFILE File, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RT_NOREF1(File); + /* RTPrintf("%s : File=%p, cbToRead=%llu\n", __PRETTY_FUNCTION__, File, + LLUIFY(cbToRead)); */ + bufferFromPath((char *)pvBuf, cbToRead, g_testRTFileRead_pszData); + if (pcbRead) + *pcbRead = RT_MIN(cbToRead, strlen(g_testRTFileRead_pszData) + 1); + g_testRTFileRead_pszData = 0; + return VINF_SUCCESS; +} + +extern int testRTFileReadAt(RTFILE hFile, uint64_t offFile, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RT_NOREF1(hFile); + RT_NOREF(offFile); + /* RTPrintf("%s : File=%p, cbToRead=%llu\n", __PRETTY_FUNCTION__, File, + LLUIFY(cbToRead)); */ + bufferFromPath((char *)pvBuf, cbToRead, g_testRTFileRead_pszData); + if (pcbRead) + *pcbRead = RT_MIN(cbToRead, strlen(g_testRTFileRead_pszData) + 1); + g_testRTFileRead_pszData = 0; + return VINF_SUCCESS; +} + +extern int testRTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) +{ + RT_NOREF3(hFile, offSeek, uMethod); + /* RTPrintf("%s : hFile=%p, offSeek=%llu, uMethod=%u\n", __PRETTY_FUNCTION__, + hFile, LLUIFY(offSeek), uMethod); */ + if (poffActual) + *poffActual = 0; + return VINF_SUCCESS; +} + +static uint64_t g_testRTFileSet_fMode; + +extern int testRTFileSetMode(RTFILE File, RTFMODE fMode) +{ + RT_NOREF1(File); + /* RTPrintf("%s: fMode=%llu\n", __PRETTY_FUNCTION__, LLUIFY(fMode)); */ + g_testRTFileSet_fMode = fMode; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileSetSize_hFile; +static RTFOFF g_testRTFileSetSize_cbSize; + +extern int testRTFileSetSize(RTFILE File, uint64_t cbSize) +{ + /* RTPrintf("%s: File=%llu, cbSize=%llu\n", __PRETTY_FUNCTION__, LLUIFY(File), + LLUIFY(cbSize)); */ + g_testRTFileSetSize_hFile = File; + g_testRTFileSetSize_cbSize = (RTFOFF) cbSize; /* Why was this signed before? */ + return VINF_SUCCESS; +} + +static RTTIMESPEC g_testRTFileSetTimes_ATime; + +extern int testRTFileSetTimes(RTFILE File, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RT_NOREF4(File, pModificationTime, pChangeTime, pBirthTime); + /* RTPrintf("%s: pFile=%p, *pAccessTime=%lli, *pModificationTime=%lli, *pChangeTime=%lli, *pBirthTime=%lli\n", + __PRETTY_FUNCTION__, + pAccessTime ? (long long)RTTimeSpecGetNano(pAccessTime) : -1, + pModificationTime + ? (long long)RTTimeSpecGetNano(pModificationTime) : -1, + pChangeTime ? (long long)RTTimeSpecGetNano(pChangeTime) : -1, + pBirthTime ? (long long)RTTimeSpecGetNano(pBirthTime) : -1); */ + if (pAccessTime) + g_testRTFileSetTimes_ATime = *pAccessTime; + else + RT_ZERO(g_testRTFileSetTimes_ATime); + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileUnlock_hFile; +static int64_t g_testRTFileUnlock_offLock; +static uint64_t g_testRTFileUnlock_cbLock; + +extern int testRTFileUnlock(RTFILE File, int64_t offLock, uint64_t cbLock) +{ + /* RTPrintf("%s: hFile=%p, ofLock=%lli, cbLock=%llu\n", __PRETTY_FUNCTION__, + File, (long long) offLock, LLUIFY(cbLock)); */ + g_testRTFileUnlock_hFile = File; + g_testRTFileUnlock_offLock = offLock; + g_testRTFileUnlock_cbLock = cbLock; + return VINF_SUCCESS; +} + +static char g_testRTFileWrite_szData[256]; + +extern int testRTFileWrite(RTFILE File, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RT_NOREF2(File, cbToWrite); + /* RTPrintf("%s: File=%p, pvBuf=%.*s, cbToWrite=%llu\n", __PRETTY_FUNCTION__, + File, cbToWrite, (const char *)pvBuf, LLUIFY(cbToWrite)); */ + ARRAY_FROM_PATH(g_testRTFileWrite_szData, (const char *)pvBuf); + if (pcbWritten) + *pcbWritten = strlen(g_testRTFileWrite_szData) + 1; + return VINF_SUCCESS; +} + +extern int testRTFileWriteAt(RTFILE File, uint64_t offFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RT_NOREF3(File, cbToWrite, offFile); + /* RTPrintf("%s: File=%p, pvBuf=%.*s, cbToWrite=%llu\n", __PRETTY_FUNCTION__, + File, cbToWrite, (const char *)pvBuf, LLUIFY(cbToWrite)); */ + ARRAY_FROM_PATH(g_testRTFileWrite_szData, (const char *)pvBuf); + if (pcbWritten) + *pcbWritten = strlen(g_testRTFileWrite_szData) + 1; + return VINF_SUCCESS; +} + +extern int testRTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + RT_NOREF1(pszFsPath); + /* RTPrintf("%s, pszFsPath=%s\n", __PRETTY_FUNCTION__, pszFsPath); + RT_ZERO(*pProperties); */ + pProperties->cbMaxComponent = 256; + pProperties->fCaseSensitive = true; + return VINF_SUCCESS; +} + +extern int testRTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + RT_NOREF2(pszFsPath, pu32Serial); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} +extern int testRTFsQuerySizes(const char *pszFsPath, PRTFOFF pcbTotal, RTFOFF *pcbFree, uint32_t *pcbBlock, uint32_t *pcbSector) +{ + RT_NOREF5(pszFsPath, pcbTotal, pcbFree, pcbBlock, pcbSector); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +extern int testRTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF2(enmAdditionalAttribs, fFlags); + /* RTPrintf("%s: pszPath=%s, enmAdditionalAttribs=0x%x, fFlags=0x%x\n", + __PRETTY_FUNCTION__, pszPath, (unsigned) enmAdditionalAttribs, + (unsigned) fFlags); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + RT_ZERO(*pObjInfo); + return VINF_SUCCESS; +} + +extern int testRTSymlinkDelete(const char *pszSymlink, uint32_t fDelete) +{ + RT_NOREF2(pszSymlink, fDelete); + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszSymlink, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +extern int testRTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszSymlink, "/\\"))) + return VERR_FILE_NOT_FOUND; + RT_NOREF4(pszSymlink, pszTarget, cbTarget, fRead); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + + +/********************************************************************************************************************************* +* Tests * +*********************************************************************************************************************************/ + +/* Sub-tests for testMappingsQuery(). */ +void testMappingsQuerySimple(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryTooFewBuffers(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryAutoMount(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryArrayWrongSize(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsQueryName(). */ +void testMappingsQueryNameValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryNameInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryNameBadBuffer(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMapFolder(). */ +void testMapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderTwice(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderDelimiter(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderCaseSensitive(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderCaseInsensitive(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testUnmapFolder(). */ +void testUnmapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testUnmapFolderInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testUnmapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testCreate(). */ +void testCreateBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testClose(). */ +void testCloseBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRead(). */ +void testReadBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testWrite(). */ +void testWriteBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testLock(). */ +void testLockBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testFlush(). */ +void testFlushBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testDirList(). */ +void testDirListBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testReadLink(). */ +void testReadLinkBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testFSInfo(). */ +void testFSInfoBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRemove(). */ +void testRemoveBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRename(). */ +void testRenameBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testSymlink(). */ +void testSymlinkBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsAdd(). */ +void testMappingsAddBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsRemove(). */ +void testMappingsRemoveBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +union TESTSHFLSTRING +{ + SHFLSTRING string; + char acData[256]; +}; + +static void fillTestShflString(union TESTSHFLSTRING *pDest, + const char *pcszSource) +{ + const size_t cchSource = strlen(pcszSource); + AssertRelease( cchSource * 2 + 2 + < sizeof(*pDest) - RT_UOFFSETOF(SHFLSTRING, String)); + pDest->string.u16Length = (uint16_t)(cchSource * sizeof(RTUTF16)); + pDest->string.u16Size = pDest->string.u16Length + sizeof(RTUTF16); + /* Copy pcszSource ASCIIZ, including the trailing 0, to the UTF16 pDest->string.String.ucs2. */ + for (unsigned i = 0; i <= cchSource; ++i) + pDest->string.String.ucs2[i] = (uint16_t)pcszSource[i]; +} + +static SHFLROOT initWithWritableMapping(RTTEST hTest, + VBOXHGCMSVCFNTABLE *psvcTable, + VBOXHGCMSVCHELPERS *psvcHelpers, + const char *pcszFolderName, + const char *pcszMapping, + bool fCaseSensitive = true) +{ + VBOXHGCMSVCPARM aParms[RT_MAX(SHFL_CPARMS_ADD_MAPPING, + SHFL_CPARMS_MAP_FOLDER)]; + union TESTSHFLSTRING FolderName; + union TESTSHFLSTRING Mapping; + union TESTSHFLSTRING AutoMountPoint; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + int rc; + + initTable(psvcTable, psvcHelpers); + AssertReleaseRC(VBoxHGCMSvcLoad(psvcTable)); + AssertRelease( psvcTable->pvService + = RTTestGuardedAllocTail(hTest, psvcTable->cbClient)); + RT_BZERO(psvcTable->pvService, psvcTable->cbClient); + fillTestShflString(&FolderName, pcszFolderName); + fillTestShflString(&Mapping, pcszMapping); + fillTestShflString(&AutoMountPoint, ""); + HGCMSvcSetPv(&aParms[0], &FolderName, RT_UOFFSETOF(SHFLSTRING, String) + + FolderName.string.u16Size); + HGCMSvcSetPv(&aParms[1], &Mapping, RT_UOFFSETOF(SHFLSTRING, String) + + Mapping.string.u16Size); + HGCMSvcSetU32(&aParms[2], 1); + HGCMSvcSetPv(&aParms[3], &AutoMountPoint, SHFLSTRING_HEADER_SIZE + AutoMountPoint.string.u16Size); + rc = psvcTable->pfnHostCall(psvcTable->pvService, SHFL_FN_ADD_MAPPING, + SHFL_CPARMS_ADD_MAPPING, aParms); + AssertReleaseRC(rc); + HGCMSvcSetPv(&aParms[0], &Mapping, RT_UOFFSETOF(SHFLSTRING, String) + + Mapping.string.u16Size); + HGCMSvcSetU32(&aParms[1], 0); /* root */ + HGCMSvcSetU32(&aParms[2], '/'); /* delimiter */ + HGCMSvcSetU32(&aParms[3], fCaseSensitive); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + AssertReleaseRC(callHandle.rc); + return aParms[1].u.uint32; +} + +/** @todo Mappings should be automatically removed by unloading the service, + * but unloading is currently a no-op! */ +static void unmapAndRemoveMapping(RTTEST hTest, VBOXHGCMSVCFNTABLE *psvcTable, + SHFLROOT root, const char *pcszFolderName) +{ + RT_NOREF1(hTest); + VBOXHGCMSVCPARM aParms[RT_MAX(SHFL_CPARMS_UNMAP_FOLDER, + SHFL_CPARMS_REMOVE_MAPPING)]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + union TESTSHFLSTRING FolderName; + int rc; + + HGCMSvcSetU32(&aParms[0], root); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_UNMAP_FOLDER, + SHFL_CPARMS_UNMAP_FOLDER, aParms, 0); + AssertReleaseRC(callHandle.rc); + fillTestShflString(&FolderName, pcszFolderName); + HGCMSvcSetPv(&aParms[0], &FolderName, RT_UOFFSETOF(SHFLSTRING, String) + + FolderName.string.u16Size); + rc = psvcTable->pfnHostCall(psvcTable->pvService, SHFL_FN_REMOVE_MAPPING, + SHFL_CPARMS_REMOVE_MAPPING, aParms); + AssertReleaseRC(rc); +} + +static int createFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + const char *pcszFilename, uint32_t fCreateFlags, + SHFLHANDLE *pHandle, SHFLCREATERESULT *pResult) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_CREATE]; + union TESTSHFLSTRING Path; + SHFLCREATEPARMS CreateParms; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + fillTestShflString(&Path, pcszFilename); + RT_ZERO(CreateParms); + CreateParms.CreateFlags = fCreateFlags; + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetPv(&aParms[1], &Path, RT_UOFFSETOF(SHFLSTRING, String) + + Path.string.u16Size); + HGCMSvcSetPv(&aParms[2], &CreateParms, sizeof(CreateParms)); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_CREATE, + RT_ELEMENTS(aParms), aParms, 0); + if (RT_FAILURE(callHandle.rc)) + return callHandle.rc; + if (pHandle) + *pHandle = CreateParms.Handle; + if (pResult) + *pResult = CreateParms.Result; + return VINF_SUCCESS; +} + +static int readFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + SHFLHANDLE hFile, uint64_t offSeek, uint32_t cbRead, + uint32_t *pcbRead, void *pvBuf, uint32_t cbBuf) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_READ]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetU64(&aParms[1], (uint64_t) hFile); + HGCMSvcSetU64(&aParms[2], offSeek); + HGCMSvcSetU32(&aParms[3], cbRead); + HGCMSvcSetPv(&aParms[4], pvBuf, cbBuf); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_READ, + RT_ELEMENTS(aParms), aParms, 0); + if (pcbRead) + *pcbRead = aParms[3].u.uint32; + return callHandle.rc; +} + +static int writeFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + SHFLHANDLE hFile, uint64_t offSeek, uint32_t cbWrite, + uint32_t *pcbWritten, const void *pvBuf, uint32_t cbBuf) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_WRITE]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetU64(&aParms[1], (uint64_t) hFile); + HGCMSvcSetU64(&aParms[2], offSeek); + HGCMSvcSetU32(&aParms[3], cbWrite); + HGCMSvcSetPv(&aParms[4], (void *)pvBuf, cbBuf); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_WRITE, + RT_ELEMENTS(aParms), aParms, 0); + if (pcbWritten) + *pcbWritten = aParms[3].u.uint32; + return callHandle.rc; +} + +static int flushFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_FLUSH]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_FLUSH, + SHFL_CPARMS_FLUSH, aParms, 0); + return callHandle.rc; +} + +static int listDir(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, uint32_t fFlags, + const char *pcszPath, void *pvBuf, uint32_t cbBuf, + uint32_t resumePoint, uint32_t *pcFiles) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_LIST]; + union TESTSHFLSTRING Path; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU32(&aParms[2], fFlags); + HGCMSvcSetU32(&aParms[3], cbBuf); + if (pcszPath) + { + fillTestShflString(&Path, pcszPath); + HGCMSvcSetPv(&aParms[4], &Path, RT_UOFFSETOF(SHFLSTRING, String) + + Path.string.u16Size); + } + else + HGCMSvcSetPv(&aParms[4], NULL, 0); + HGCMSvcSetPv(&aParms[5], pvBuf, cbBuf); + HGCMSvcSetU32(&aParms[6], resumePoint); + HGCMSvcSetU32(&aParms[7], 0); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_LIST, + RT_ELEMENTS(aParms), aParms, 0); + if (pcFiles) + *pcFiles = aParms[7].u.uint32; + return callHandle.rc; +} + +static int sfInformation(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, uint32_t fFlags, uint32_t cb, + SHFLFSOBJINFO *pInfo) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_INFORMATION]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU32(&aParms[2], fFlags); + HGCMSvcSetU32(&aParms[3], cb); + HGCMSvcSetPv(&aParms[4], pInfo, cb); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_INFORMATION, + RT_ELEMENTS(aParms), aParms, 0); + return callHandle.rc; +} + +static int lockFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, int64_t offLock, uint64_t cbLock, + uint32_t fFlags) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_LOCK]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU64(&aParms[2], offLock); + HGCMSvcSetU64(&aParms[3], cbLock); + HGCMSvcSetU32(&aParms[4], fFlags); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_LOCK, + RT_ELEMENTS(aParms), aParms, 0); + return callHandle.rc; +} + +void testCreateFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLCREATERESULT Result; + int rc; + + RTTestSub(hTest, "Create file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, NULL, + &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/file"), + (hTest, "pszFilename=%s\n", &g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, g_testRTFileOpen_fOpen == 0x181, + (hTest, "fOpen=%llu\n", LLUIFY(g_testRTFileOpen_fOpen))); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, + (hTest, "File=%u\n", (uintptr_t)g_testRTFileClose_hFile)); +} + +void testCreateFileSimpleCaseInsensitive(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLCREATERESULT Result; + int rc; + + g_fFailIfNotLowercase = true; + + RTTestSub(hTest, "Create file case insensitive"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname", false /*fCaseSensitive*/); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/TesT/FilE", SHFL_CF_ACCESS_READ, NULL, + &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/file"), + (hTest, "pszFilename=%s\n", &g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, g_testRTFileOpen_fOpen == 0x181, + (hTest, "fOpen=%llu\n", LLUIFY(g_testRTFileOpen_fOpen))); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, + (hTest, "File=%u\n", (uintptr_t)g_testRTFileClose_hFile)); + + g_fFailIfNotLowercase = false; +} + +void testCreateDirSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + SHFLCREATERESULT Result; + int rc; + + RTTestSub(hTest, "Create directory simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, NULL, &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTDirCreate_szPath[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/dir"), + (hTest, "pszPath=%s\n", &g_testRTDirCreate_szPath[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTDirOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/dir"), + (hTest, "pszFilename=%s\n", &g_testRTDirOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testReadFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + const char *pcszReadData = "Data to read"; + char achBuf[sizeof(pcszReadData) + 10]; + uint32_t cbRead; + int rc; + + RTTestSub(hTest, "Read file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + g_testRTFileRead_pszData = pcszReadData; + memset(achBuf, 'f', sizeof(achBuf)); + rc = readFile(&svcTable, Root, Handle, 0, (uint32_t)strlen(pcszReadData) + 1, + &cbRead, achBuf, (uint32_t)sizeof(achBuf)); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strncmp(achBuf, pcszReadData, sizeof(achBuf)), + (hTest, "pvBuf=%.*s Handle=%#RX64\n", sizeof(achBuf), achBuf, Handle)); + RTTEST_CHECK_MSG(hTest, cbRead == strlen(pcszReadData) + 1, + (hTest, "cbRead=%llu\n", LLUIFY(cbRead))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); +} + +void testWriteFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + const char *pcszWrittenData = "Data to write"; + uint32_t cbToWrite = (uint32_t)strlen(pcszWrittenData) + 1; + uint32_t cbWritten; + int rc; + + RTTestSub(hTest, "Write file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = writeFile(&svcTable, Root, Handle, 0, cbToWrite, &cbWritten, + pcszWrittenData, cbToWrite); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(g_testRTFileWrite_szData, pcszWrittenData), + (hTest, "pvBuf=%s\n", g_testRTFileWrite_szData)); + RTTEST_CHECK_MSG(hTest, cbWritten == cbToWrite, + (hTest, "cbWritten=%llu\n", LLUIFY(cbWritten))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); +} + +void testFlushFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Flush file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = flushFile(&svcTable, Root, Handle); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileFlush_hFile == hFile, (hTest, "File=%u\n", g_testRTFileFlush_hFile)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testDirListEmpty(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + SHFLHANDLE Handle; + union + { + SHFLDIRINFO DirInfo; + uint8_t abBuffer[sizeof(SHFLDIRINFO) + 2 * sizeof(RTUTF16)]; + } Buf; + uint32_t cFiles; + int rc; + + RTTestSub(hTest, "List empty directory"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = listDir(&svcTable, Root, Handle, 0, NULL, &Buf.DirInfo, sizeof(Buf), 0, &cFiles); + RTTEST_CHECK_RC(hTest, rc, VERR_NO_MORE_FILES); + RTTEST_CHECK_MSG(hTest, g_testRTDirReadEx_hDir == hDir, (hTest, "Dir=%p\n", g_testRTDirReadEx_hDir)); + RTTEST_CHECK_MSG(hTest, cFiles == 0, + (hTest, "cFiles=%llu\n", LLUIFY(cFiles))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testFSInfoQuerySetFMode(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const uint32_t fMode = 0660; + SHFLFSOBJINFO Info; + int rc; + + RTTestSub(hTest, "Query and set file size"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + SHFLHANDLE Handle = SHFL_HANDLE_NIL; + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK_RETV(hTest, rc); + + RT_ZERO(Info); + g_testRTFileQueryInfo_fMode = fMode; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileQueryInfo_hFile == hFile, (hTest, "File=%u\n", g_testRTFileQueryInfo_hFile)); + RTTEST_CHECK_MSG(hTest, Info.Attr.fMode == fMode, + (hTest, "cbObject=%llu\n", LLUIFY(Info.cbObject))); + RT_ZERO(Info); + Info.Attr.fMode = fMode; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileSet_fMode == fMode, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileSet_fMode))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testFSInfoQuerySetDirATime(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + const int64_t ccAtimeNano = 100000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Query and set directory atime"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + RTTimeSpecSetNano(&g_testRTDirQueryInfo_ATime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTDirQueryInfo_hDir == hDir, (hTest, "Dir=%p\n", g_testRTDirQueryInfo_hDir)); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&Info.AccessTime) == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&Info.AccessTime)))); + RT_ZERO(Info); + RTTimeSpecSetNano(&Info.AccessTime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&g_testRTDirSetTimes_ATime) + == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&g_testRTDirSetTimes_ATime)))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testFSInfoQuerySetFileATime(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const int64_t ccAtimeNano = 100000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Query and set file atime"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + RTTimeSpecSetNano(&g_testRTFileQueryInfo_ATime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileQueryInfo_hFile == hFile, (hTest, "File=%u\n", g_testRTFileQueryInfo_hFile)); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&Info.AccessTime) == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&Info.AccessTime)))); + RT_ZERO(Info); + RTTimeSpecSetNano(&Info.AccessTime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&g_testRTFileSetTimes_ATime) + == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&g_testRTFileSetTimes_ATime)))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testFSInfoQuerySetEndOfFile(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const RTFOFF cbNew = 50000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Set end of file position"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + Info.cbObject = cbNew; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_SIZE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileSetSize_hFile == hFile, (hTest, "File=%u\n", g_testRTFileSetSize_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileSetSize_cbSize == cbNew, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileSetSize_cbSize))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testLockFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const int64_t offLock = 50000; + const uint64_t cbLock = 4000; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Simple file lock and unlock"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = lockFile(&svcTable, Root, Handle, offLock, cbLock, SHFL_LOCK_SHARED); + RTTEST_CHECK_RC_OK(hTest, rc); +#ifdef RT_OS_WINDOWS /* Locking is a no-op elsewhere. */ + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_hFile == hFile, (hTest, "File=%u\n", g_testRTFileLock_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_fLock == 0, + (hTest, "fLock=%u\n", g_testRTFileLock_fLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_offLock == offLock, + (hTest, "Offs=%llu\n", (long long) g_testRTFileLock_offLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_cbLock == cbLock, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileLock_cbLock))); +#endif + rc = lockFile(&svcTable, Root, Handle, offLock, cbLock, SHFL_LOCK_CANCEL); + RTTEST_CHECK_RC_OK(hTest, rc); +#ifdef RT_OS_WINDOWS + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_hFile == hFile, (hTest, "File=%u\n", g_testRTFileUnlock_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_offLock == offLock, + (hTest, "Offs=%llu\n", + (long long) g_testRTFileUnlock_offLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_cbLock == cbLock, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileUnlock_cbLock))); +#endif + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + + +/********************************************************************************************************************************* +* Main code * +*********************************************************************************************************************************/ + +static void testAPI(RTTEST hTest) +{ + testMappingsQuery(hTest); + testMappingsQueryName(hTest); + testMapFolder(hTest); + testUnmapFolder(hTest); + testCreate(hTest); + testClose(hTest); + testRead(hTest); + testWrite(hTest); + testLock(hTest); + testFlush(hTest); + testDirList(hTest); + testReadLink(hTest); + testFSInfo(hTest); + testRemove(hTest); + testRename(hTest); + testSymlink(hTest); + testMappingsAdd(hTest); + testMappingsRemove(hTest); + /* testSetStatusLed(hTest); */ +} + +int main(int argc, char **argv) +{ + RT_NOREF1(argc); + RTEXITCODE rcExit = RTTestInitAndCreate(RTPathFilename(argv[0]), &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + testAPI(g_hTest); + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h new file mode 100644 index 00000000..3be50c23 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h @@ -0,0 +1,140 @@ +/** @file + * VBox Shared Folders testcase stub redefinitions. + */ + +/* + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h +#define VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Grumble... if the coding style let us use the anonymous "struct RTTESTINT *" + * instead of "PRTTEST" here we wouldn't need to unnecessarily include this. */ +#include <iprt/test.h> + +void testMappingsQuery(RTTEST hTest); +/* Sub-tests for testMappingsQuery(). */ +void testMappingsQuerySimple(RTTEST hTest); +void testMappingsQueryTooFewBuffers(RTTEST hTest); +void testMappingsQueryAutoMount(RTTEST hTest); +void testMappingsQueryArrayWrongSize(RTTEST hTest); + +void testMappingsQueryName(RTTEST hTest); +/* Sub-tests for testMappingsQueryName(). */ +void testMappingsQueryNameValid(RTTEST hTest); +void testMappingsQueryNameInvalid(RTTEST hTest); +void testMappingsQueryNameBadBuffer(RTTEST hTest); + +void testMapFolder(RTTEST hTest); +/* Sub-tests for testMapFolder(). */ +void testMapFolderValid(RTTEST hTest); +void testMapFolderInvalid(RTTEST hTest); +void testMapFolderTwice(RTTEST hTest); +void testMapFolderDelimiter(RTTEST hTest); +void testMapFolderCaseSensitive(RTTEST hTest); +void testMapFolderCaseInsensitive(RTTEST hTest); +void testMapFolderBadParameters(RTTEST hTest); + +void testUnmapFolder(RTTEST hTest); +/* Sub-tests for testUnmapFolder(). */ +void testUnmapFolderValid(RTTEST hTest); +void testUnmapFolderInvalid(RTTEST hTest); +void testUnmapFolderBadParameters(RTTEST hTest); + +void testCreate(RTTEST hTest); +/* Sub-tests for testCreate(). */ +void testCreateFileSimple(RTTEST hTest); +void testCreateFileSimpleCaseInsensitive(RTTEST hTest); +void testCreateDirSimple(RTTEST hTest); +void testCreateBadParameters(RTTEST hTest); + +void testClose(RTTEST hTest); +/* Sub-tests for testClose(). */ +void testCloseBadParameters(RTTEST hTest); + +void testRead(RTTEST hTest); +/* Sub-tests for testRead(). */ +void testReadBadParameters(RTTEST hTest); +void testReadFileSimple(RTTEST hTest); + +void testWrite(RTTEST hTest); +/* Sub-tests for testWrite(). */ +void testWriteBadParameters(RTTEST hTest); +void testWriteFileSimple(RTTEST hTest); + +void testLock(RTTEST hTest); +/* Sub-tests for testLock(). */ +void testLockBadParameters(RTTEST hTest); +void testLockFileSimple(RTTEST hTest); + +void testFlush(RTTEST hTest); +/* Sub-tests for testFlush(). */ +void testFlushBadParameters(RTTEST hTest); +void testFlushFileSimple(RTTEST hTest); + +void testDirList(RTTEST hTest); +/* Sub-tests for testDirList(). */ +void testDirListBadParameters(RTTEST hTest); +void testDirListEmpty(RTTEST hTest); + +void testReadLink(RTTEST hTest); +/* Sub-tests for testReadLink(). */ +void testReadLinkBadParameters(RTTEST hTest); + +void testFSInfo(RTTEST hTest); +/* Sub-tests for testFSInfo(). */ +void testFSInfoBadParameters(RTTEST hTest); +void testFSInfoQuerySetFMode(RTTEST hTest); +void testFSInfoQuerySetDirATime(RTTEST hTest); +void testFSInfoQuerySetFileATime(RTTEST hTest); +void testFSInfoQuerySetEndOfFile(RTTEST hTest); + +void testRemove(RTTEST hTest); +/* Sub-tests for testRemove(). */ +void testRemoveBadParameters(RTTEST hTest); + +void testRename(RTTEST hTest); +/* Sub-tests for testRename(). */ +void testRenameBadParameters(RTTEST hTest); + +void testSymlink(RTTEST hTest); +/* Sub-tests for testSymlink(). */ +void testSymlinkBadParameters(RTTEST hTest); + +void testMappingsAdd(RTTEST hTest); +/* Sub-tests for testMappingsAdd(). */ +void testMappingsAddBadParameters(RTTEST hTest); + +void testMappingsRemove(RTTEST hTest); +/* Sub-tests for testMappingsRemove(). */ +void testMappingsRemoveBadParameters(RTTEST hTest); + +#if 0 /* Where should this go? */ +void testSetStatusLed(RTTEST hTest); +/* Sub-tests for testStatusLed(). */ +void testSetStatusLedBadParameters(RTTEST hTest); +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h */ diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp new file mode 100644 index 00000000..052b80d7 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp @@ -0,0 +1,452 @@ +/** @file + * Testcase for shared folder case conversion code. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MISC +#define LOG_ENABLED +#include <VBox/shflsvc.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/fs.h> +#include <iprt/dir.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/uni.h> +#include <stdio.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* Override slash for non-windows hosts. */ +#undef RTPATH_DELIMITER +#define RTPATH_DELIMITER '\\' + +/* Use our own RTPath and RTDir methods. */ +#define RTPathQueryInfo rtPathQueryInfo +#define RTDirOpenFiltered rtDirOpenFiltered +#define RTDirClose rtDirClose +#define RTDirReadEx rtDirReadEx + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static int iDirList = 0; +static int iDirFile = 0; + +static const char *g_apszDirs[] = +{ + "c:", + "c:\\test dir", + "c:\\test dir\\SUBDIR", +}; + +static const char *g_apszDirsC[] = +{ + ".", + "..", + "test dir" +}; + +static const char *g_apszTestdirEntries[] = +{ + ".", + "..", + "SUBDIR", + "a.bat", + "aTestJe.bat", + "aTestje.bat", + "b.bat", + "c.bat", + "d.bat", + "e.bat", + "f.bat", + "g.bat", + "h.bat", + "x.bat", + "z.bat", +}; + +static const char *g_apszSUBDIREntries[] = +{ + ".", + "..", + "a.bat", + "aTestJe.bat", + "aTestje.bat", + "b.bat", + "c.bat", + "d.bat", + "e.bat", + "f.bat", + "g.bat", + "h.bat", + "x.bat", + "z.bat", +}; + +int rtDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags) +{ + RT_NOREF2(enmFilter, fFlags); + if (!strcmp(pszPath, "c:\\*")) + iDirList = 1; + else if (!strcmp(pszPath, "c:\\test dir\\*")) + iDirList = 2; + else if (!strcmp(pszPath, "c:\\test dir\\SUBDIR\\*")) + iDirList = 3; + else + AssertFailed(); + + *phDir = (RTDIR)1; + return VINF_SUCCESS; +} + +int rtDirClose(RTDIR hDir) +{ + RT_NOREF1(hDir); + iDirFile = 0; + return VINF_SUCCESS; +} + +int rtDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF4(hDir, pcbDirEntry, enmAdditionalAttribs, fFlags); + switch (iDirList) + { + case 1: + if (iDirFile == RT_ELEMENTS(g_apszDirsC)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszDirsC[iDirFile]); + strcpy(pDirEntry->szName, g_apszDirsC[iDirFile++]); + break; + case 2: + if (iDirFile == RT_ELEMENTS(g_apszTestdirEntries)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszTestdirEntries[iDirFile]); + strcpy(pDirEntry->szName, g_apszTestdirEntries[iDirFile++]); + break; + case 3: + if (iDirFile == RT_ELEMENTS(g_apszSUBDIREntries)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszSUBDIREntries[iDirFile]); + strcpy(pDirEntry->szName, g_apszSUBDIREntries[iDirFile++]); + break; + } + return VINF_SUCCESS; +} + +int rtPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF2(pObjInfo, enmAdditionalAttribs); + int cMax; + + /* first try g_apszDirs */ + for (unsigned int i=0;i<RT_ELEMENTS(g_apszDirs);i++) + { + if(!strcmp(pszPath, g_apszDirs[i])) + return VINF_SUCCESS; + } + + const char **papszDirList; + switch (iDirList) + { + case 1: + cMax = RT_ELEMENTS(g_apszDirsC); + papszDirList = g_apszDirsC; + break; + case 2: + cMax = RT_ELEMENTS(g_apszTestdirEntries); + papszDirList = g_apszTestdirEntries; + break; + case 3: + cMax = RT_ELEMENTS(g_apszSUBDIREntries); + papszDirList = g_apszSUBDIREntries; + break; + default: + return VERR_FILE_NOT_FOUND; + } + for (int i = 0; i < cMax; i++) + { + if (!strcmp(pszPath, papszDirList[i])) + return VINF_SUCCESS; + } + return VERR_FILE_NOT_FOUND; +} + +static int vbsfCorrectCasing(char *pszFullPath, char *pszStartComponent) +{ + PRTDIRENTRYEX pDirEntry = NULL; + uint32_t cbDirEntry; + size_t cbComponent; + int rc = VERR_FILE_NOT_FOUND; + RTDIR hSearch = NIL_RTDIR; + char szWildCard[4]; + + Log2(("vbsfCorrectCasing: %s %s\n", pszFullPath, pszStartComponent)); + + cbComponent = strlen(pszStartComponent); + + cbDirEntry = 4096; + pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry); + if (pDirEntry == 0) + { + AssertFailed(); + return VERR_NO_MEMORY; + } + + /** @todo this is quite inefficient, especially for directories with many files */ + Assert(pszFullPath < pszStartComponent-1); + Assert(*(pszStartComponent-1) == RTPATH_DELIMITER); + *(pszStartComponent-1) = 0; + strcpy(pDirEntry->szName, pszFullPath); + szWildCard[0] = RTPATH_DELIMITER; + szWildCard[1] = '*'; + szWildCard[2] = 0; + strcat(pDirEntry->szName, szWildCard); + + rc = RTDirOpenFiltered(&hSearch, pDirEntry->szName, RTDIRFILTER_WINNT, 0 /*fFlags*/); + *(pszStartComponent-1) = RTPATH_DELIMITER; + if (RT_FAILURE(rc)) + goto end; + + for(;;) + { + size_t cbDirEntrySize = cbDirEntry; + + rc = RTDirReadEx(hSearch, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (rc == VERR_NO_MORE_FILES) + break; + + if (VINF_SUCCESS != rc && rc != VWRN_NO_DIRENT_INFO) + { + AssertFailed(); + if (rc != VERR_NO_TRANSLATION) + break; + else + continue; + } + + Log2(("vbsfCorrectCasing: found %s\n", &pDirEntry->szName[0])); + if ( pDirEntry->cbName == cbComponent + && !RTStrICmp(pszStartComponent, &pDirEntry->szName[0])) + { + Log(("Found original name %s (%s)\n", &pDirEntry->szName[0], pszStartComponent)); + strcpy(pszStartComponent, &pDirEntry->szName[0]); + rc = VINF_SUCCESS; + break; + } + } + if (RT_FAILURE(rc)) + Log(("vbsfCorrectCasing %s failed with %d\n", pszStartComponent, rc)); + +end: + if (pDirEntry) + RTMemFree(pDirEntry); + + if (hSearch) + RTDirClose(hSearch); + return rc; +} + + + +int testCase(char *pszFullPath, bool fWildCard = false) +{ + int rc; + RTFSOBJINFO info; + char *pszWildCardComponent = NULL; + + if (fWildCard) + { + /* strip off the last path component, that contains the wildcard(s) */ + size_t len = strlen(pszFullPath); + char *src = pszFullPath + len - 1; + + while(src > pszFullPath) + { + if (*src == RTPATH_DELIMITER) + break; + src--; + } + if (*src == RTPATH_DELIMITER) + { + bool fHaveWildcards = false; + char *temp = src; + + while(*temp) + { + char uc = *temp; + /** @todo should depend on the guest OS */ + if (uc == '*' || uc == '?' || uc == '>' || uc == '<' || uc == '"') + { + fHaveWildcards = true; + break; + } + temp++; + } + + if (fHaveWildcards) + { + pszWildCardComponent = src; + *pszWildCardComponent = 0; + } + } + } + + rc = RTPathQueryInfo(pszFullPath, &info, RTFSOBJATTRADD_NOTHING); + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + size_t len = strlen(pszFullPath); + char *src = pszFullPath + len - 1; + + Log(("Handle case insensitive guest fs on top of host case sensitive fs for %s\n", pszFullPath)); + + /* Find partial path that's valid */ + while(src > pszFullPath) + { + if (*src == RTPATH_DELIMITER) + { + *src = 0; + rc = RTPathQueryInfo (pszFullPath, &info, RTFSOBJATTRADD_NOTHING); + *src = RTPATH_DELIMITER; + if (rc == VINF_SUCCESS) + { +#ifdef DEBUG + *src = 0; + Log(("Found valid partial path %s\n", pszFullPath)); + *src = RTPATH_DELIMITER; +#endif + break; + } + } + + src--; + } + Assert(*src == RTPATH_DELIMITER && RT_SUCCESS(rc)); + if ( *src == RTPATH_DELIMITER + && RT_SUCCESS(rc)) + { + src++; + for(;;) + { + char *end = src; + bool fEndOfString = true; + + while(*end) + { + if (*end == RTPATH_DELIMITER) + break; + end++; + } + + if (*end == RTPATH_DELIMITER) + { + fEndOfString = false; + *end = 0; + rc = RTPathQueryInfo(src, &info, RTFSOBJATTRADD_NOTHING); + Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND); + } + else + if (end == src) + rc = VINF_SUCCESS; /* trailing delimiter */ + else + rc = VERR_FILE_NOT_FOUND; + + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + /* path component is invalid; try to correct the casing */ + rc = vbsfCorrectCasing(pszFullPath, src); + if (RT_FAILURE(rc)) + { + if (!fEndOfString) + *end = RTPATH_DELIMITER; + break; + } + } + + if (fEndOfString) + break; + + *end = RTPATH_DELIMITER; + src = end + 1; + } + if (RT_FAILURE(rc)) + Log(("Unable to find suitable component rc=%d\n", rc)); + } + else + rc = VERR_FILE_NOT_FOUND; + + } + if (pszWildCardComponent) + *pszWildCardComponent = RTPATH_DELIMITER; + + if (RT_SUCCESS(rc)) + Log(("New valid path %s\n", pszFullPath)); + else + Log(("Old invalid path %s\n", pszFullPath)); + return rc; +} + + +int main() +{ + char szTest[128]; + + RTR3InitExeNoArguments(0); + RTLogFlush(NULL); + RTLogDestinations(NULL, "stdout"); + RTLogGroupSettings(NULL, "misc=~0"); + RTLogFlags(NULL, "unbuffered"); + + strcpy(szTest, "c:\\test Dir\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\SUBDIR\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\SUBDiR\\atestje.bat"); + testCase(szTest); + strcpy(szTest, "c:\\TEST dir\\subDiR\\aTestje.baT"); + testCase(szTest); + strcpy(szTest, "c:\\TEST dir\\subDiR\\*"); + testCase(szTest, true); + strcpy(szTest, "c:\\TEST dir\\subDiR\\"); + testCase(szTest ,true); + strcpy(szTest, "c:\\test dir\\SUBDIR\\"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\invalid\\SUBDIR\\test.bat"); + testCase(szTest); + return 0; +} + diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp new file mode 100644 index 00000000..c9832493 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp @@ -0,0 +1,144 @@ +/** @file + * tstShflSize - Testcase for shared folder structure sizes. + * Run this on Linux and Windows, then compare. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/shflsvc.h> +#include <iprt/string.h> +#include <iprt/stream.h> + +#define STRUCT(t, size) \ + do { \ + if (fPrintChecks) \ + RTPrintf(" STRUCT(" #t ", %d);\n", (int)sizeof(t)); \ + else if ((size) != sizeof(t)) \ + { \ + RTPrintf("%30s: %d expected %d!\n", #t, (int)sizeof(t), (size)); \ + cErrors++; \ + } \ + else if (!fQuiet)\ + RTPrintf("%30s: %d\n", #t, (int)sizeof(t)); \ + } while (0) + + +int main(int argc, char **argv) +{ + unsigned cErrors = 0; + + /* + * Prints the code below if any argument was giving. + */ + bool fQuiet = argc == 2 && !strcmp(argv[1], "quiet"); + bool fPrintChecks = !fQuiet && argc != 1; + + RTPrintf("tstShflSizes: TESTING\n"); + + /* + * The checks. + */ + STRUCT(SHFLROOT, 4); + STRUCT(SHFLHANDLE, 8); + STRUCT(SHFLSTRING, 6); + STRUCT(SHFLCREATERESULT, 4); + STRUCT(SHFLCREATEPARMS, 108); + STRUCT(SHFLMAPPING, 8); + STRUCT(SHFLDIRINFO, 128); + STRUCT(SHFLVOLINFO, 40); + STRUCT(SHFLFSOBJATTR, 44); + STRUCT(SHFLFSOBJINFO, 92); +#ifdef VBOX_WITH_64_BITS_GUESTS +/* The size of the guest structures depends on the current architecture bit count (ARCH_BITS) + * because the HGCMFunctionParameter structure differs in 32 and 64 bit guests. + * The host VMMDev device takes care about this. + * + * Therefore this testcase verifies whether structure sizes are correct for the current ARCH_BITS. + */ +# if ARCH_BITS == 64 + STRUCT(VBoxSFQueryMappings, 88); + STRUCT(VBoxSFQueryMapName, 72); + STRUCT(VBoxSFMapFolder_Old, 88); + STRUCT(VBoxSFMapFolder, 104); + STRUCT(VBoxSFUnmapFolder, 56); + STRUCT(VBoxSFCreate, 88); + STRUCT(VBoxSFClose, 72); + STRUCT(VBoxSFRead, 120); + STRUCT(VBoxSFWrite, 120); + STRUCT(VBoxSFLock, 120); + STRUCT(VBoxSFFlush, 72); + STRUCT(VBoxSFList, 168); + STRUCT(VBoxSFInformation, 120); + STRUCT(VBoxSFRemove, 88); + STRUCT(VBoxSFRename, 104); +# elif ARCH_BITS == 32 + STRUCT(VBoxSFQueryMappings, 24+52); + STRUCT(VBoxSFQueryMapName, 24+40); /* this was changed from 52 in 21976 after VBox-1.4. */ + STRUCT(VBoxSFMapFolder_Old, 24+52); + STRUCT(VBoxSFMapFolder, 24+64); + STRUCT(VBoxSFUnmapFolder, 24+28); + STRUCT(VBoxSFCreate, 24+52); + STRUCT(VBoxSFClose, 24+40); + STRUCT(VBoxSFRead, 24+76); + STRUCT(VBoxSFWrite, 24+76); + STRUCT(VBoxSFLock, 24+76); + STRUCT(VBoxSFFlush, 24+40); + STRUCT(VBoxSFList, 24+112); + STRUCT(VBoxSFInformation, 24+76); + STRUCT(VBoxSFRemove, 24+52); + STRUCT(VBoxSFRename, 24+64); +# else +# error "Unsupported ARCH_BITS" +# endif /* ARCH_BITS */ +#else + STRUCT(VBoxSFQueryMappings, 24+52); + STRUCT(VBoxSFQueryMapName, 24+40); /* this was changed from 52 in 21976 after VBox-1.4. */ + STRUCT(VBoxSFMapFolder_Old, 24+52); + STRUCT(VBoxSFMapFolder, 24+64); + STRUCT(VBoxSFUnmapFolder, 24+28); + STRUCT(VBoxSFCreate, 24+52); + STRUCT(VBoxSFClose, 24+40); + STRUCT(VBoxSFRead, 24+76); + STRUCT(VBoxSFWrite, 24+76); + STRUCT(VBoxSFLock, 24+76); + STRUCT(VBoxSFFlush, 24+40); + STRUCT(VBoxSFList, 24+112); + STRUCT(VBoxSFInformation, 24+76); + STRUCT(VBoxSFRemove, 24+52); + STRUCT(VBoxSFRename, 24+64); +#endif /* VBOX_WITH_64_BITS_GUESTS */ + + /* + * The summary. + */ + if (!cErrors) + RTPrintf("tstShflSizes: SUCCESS\n"); + else + RTPrintf("tstShflSizes: FAILURE - %d errors\n", cErrors); + return !!cErrors; +} + diff --git a/src/VBox/HostServices/SharedFolders/teststubs.h b/src/VBox/HostServices/SharedFolders/teststubs.h new file mode 100644 index 00000000..c7478621 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/teststubs.h @@ -0,0 +1,104 @@ +/** @file + * VBox Shared Folders testcase stub redefinitions. + */ + +/* + * 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 + */ + +/** + * Macros for renaming iprt file operations to redirect them to testcase + * stub functions (mocks). The religiously correct way to do this would be + * to make the service use a file operations structure with function pointers + * but I'm not sure that would be universally appreciated. */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_teststubs_h +#define VBOX_INCLUDED_SRC_SharedFolders_teststubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/time.h> + +#define RTDirClose testRTDirClose +extern int testRTDirClose(RTDIR hDir); +#define RTDirCreate testRTDirCreate +extern int testRTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate); +#define RTDirOpen testRTDirOpen +extern int testRTDirOpen(RTDIR *phDir, const char *pszPath); +#define RTDirOpenFiltered testRTDirOpenFiltered +extern int testRTDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags); +#define RTDirQueryInfo testRTDirQueryInfo +extern int testRTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs); +#define RTDirRemove testRTDirRemove +extern int testRTDirRemove(const char *pszPath); +#define RTDirReadEx testRTDirReadEx +extern int testRTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags); +#define RTDirSetMode testRTDirSetMode +extern int testRTDirSetMode(RTDIR hDir, RTFMODE fMode); +#define RTDirSetTimes testRTDirSetTimes +extern int testRTDirSetTimes(RTDIR hDir, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime); +#define RTFileClose testRTFileClose +extern int testRTFileClose(RTFILE hFile); +#define RTFileDelete testRTFileDelete +extern int testRTFileDelete(const char *pszFilename); +#define RTFileFlush testRTFileFlush +extern int testRTFileFlush(RTFILE hFile); +#define RTFileLock testRTFileLock +extern int testRTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock); +#define RTFileOpenEx testRTFileOpenEx +extern int testRTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken); +#define RTFileQueryInfo testRTFileQueryInfo +extern int testRTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs); +#define RTFileRead testRTFileRead +extern int testRTFileRead(RTFILE hFile, void *pvBuf, size_t cbToRead, size_t *pcbRead); +#define RTFileReadAt testRTFileReadAt +extern int testRTFileReadAt(RTFILE hFile, uint64_t offFile, void *pvBuf, size_t cbToRead, size_t *pcbRead); +#define RTFileSetMode testRTFileSetMode +extern int testRTFileSetMode(RTFILE hFile, RTFMODE fMode); +#define RTFileSetSize testRTFileSetSize +extern int testRTFileSetSize(RTFILE hFile, uint64_t cbSize); +#define RTFileSetTimes testRTFileSetTimes +extern int testRTFileSetTimes(RTFILE hFile, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime); +#define RTFileSeek testRTFileSeek +extern int testRTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual); +#define RTFileUnlock testRTFileUnlock +extern int testRTFileUnlock(RTFILE hFile, int64_t offLock, uint64_t cbLock); +#define RTFileWrite testRTFileWrite +extern int testRTFileWrite(RTFILE hFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten); +#define RTFileWriteAt testRTFileWriteAt +extern int testRTFileWriteAt(RTFILE hFile, uint64_t offFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten); +#define RTFsQueryProperties testRTFsQueryProperties +extern int testRTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties); +#define RTFsQuerySerial testRTFsQuerySerial +extern int testRTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial); +#define RTFsQuerySizes testRTFsQuerySizes +extern int testRTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, uint32_t *pcbBlock, uint32_t *pcbSector); +#define RTPathQueryInfoEx testRTPathQueryInfoEx +extern int testRTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags); +#define RTSymlinkDelete testRTSymlinkDelete +extern int testRTSymlinkDelete(const char *pszSymlink, uint32_t fDelete); +#define RTSymlinkRead testRTSymlinkRead +extern int testRTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_teststubs_h */ diff --git a/src/VBox/HostServices/SharedFolders/vbsf.cpp b/src/VBox/HostServices/SharedFolders/vbsf.cpp new file mode 100644 index 00000000..59907584 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsf.cpp @@ -0,0 +1,2690 @@ +/* $Id: vbsf.cpp $ */ +/** @file + * Shared Folders - VBox Shared Folders. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "vbsfpath.h" +#include "mappings.h" +#include "vbsf.h" +#include "shflhandle.h" + +#include <VBox/AssertGuest.h> +#include <VBox/param.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/fs.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/uni.h> +#include <iprt/stream.h> +#ifdef RT_OS_DARWIN +# include <Carbon/Carbon.h> +#endif + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_RT_LINK(pClient) ((pClient)->fu32Flags & SHFL_CF_SYMLINKS ? RTPATH_F_ON_LINK : RTPATH_F_FOLLOW_LINK) + +/** + * @todo find a better solution for supporting the execute bit for non-windows + * guests on windows host. Search for "0111" to find all the relevant places. + */ + + +#ifndef RT_OS_WINDOWS + +/** + * Helps to check if pszPath deserves a VERR_PATH_NOT_FOUND status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsPathNotFound(char *pszPath) +{ + /* + * Check if the parent directory actually exists. We temporarily modify the path here. + */ + size_t cchParent = RTPathParentLength(pszPath); + char chSaved = pszPath[cchParent]; + pszPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszPath[cchParent] = chSaved; + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return false; + return true; + } + if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND) + return true; + return false; +} + +/** + * Helps to check if pszPath deserves a VERR_PATH_NOT_FOUND status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsPathNotFound2(char *pszSrcPath, char *pszDstPath) +{ + /* + * Do the source parent first. + */ + size_t cchParent = RTPathParentLength(pszSrcPath); + char chSaved = pszSrcPath[cchParent]; + pszSrcPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszSrcPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszSrcPath[cchParent] = chSaved; + if ( (RT_SUCCESS(vrc) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + || vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + return true; + if (RT_FAILURE(vrc)) + return false; + + /* + * The source itself. + */ + vrc = RTPathQueryInfoEx(pszSrcPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + /* + * The source is fine, continue with the destination. + */ + cchParent = RTPathParentLength(pszDstPath); + chSaved = pszDstPath[cchParent]; + pszDstPath[cchParent] = '\0'; + vrc = RTPathQueryInfoEx(pszDstPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszDstPath[cchParent] = chSaved; + if ( (RT_SUCCESS(vrc) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + || vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + return true; + } + return false; +} + +/** + * Helps checking if the specified path happens to exist but not be a directory. + */ +static bool vbsfErrorStyleIsWindowsNotADirectory(const char *pszPath) +{ + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return false; + return true; + } + return false; +} + +/** + * Helps to check if pszPath deserves a VERR_INVALID_NAME status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsInvalidNameForNonDir(char *pszPath) +{ + /* + * This only applies to paths with trailing slashes. + */ + size_t const cchPath = strlen(pszPath); + if (cchPath > 0 && RTPATH_IS_SLASH(pszPath[cchPath - 1])) + { + /* + * However it doesn't if an earlier path component is missing or not a file. + */ + size_t cchParent = RTPathParentLength(pszPath); + char chSaved = pszPath[cchParent]; + pszPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszPath[cchParent] = chSaved; + if (RT_SUCCESS(vrc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return true; + } + return false; +} + +#endif /* RT_OS_WINDOWS */ + +void vbsfStripLastComponent(char *pszFullPath, uint32_t cbFullPathRoot) +{ + RTUNICP cp; + + /* Do not strip root. */ + char *s = pszFullPath + cbFullPathRoot; + char *delimSecondLast = NULL; + char *delimLast = NULL; + + LogFlowFunc(("%s -> %s\n", pszFullPath, s)); + + for (;;) + { + cp = RTStrGetCp(s); + + if (cp == RTUNICP_INVALID || cp == 0) + { + break; + } + + if (cp == RTPATH_DELIMITER) + { + if (delimLast != NULL) + { + delimSecondLast = delimLast; + } + + delimLast = s; + } + + s = RTStrNextCp(s); + } + + if (cp == 0) + { + if (delimLast + 1 == s) + { + if (delimSecondLast) + { + *delimSecondLast = 0; + } + else if (delimLast) + { + *delimLast = 0; + } + } + else + { + if (delimLast) + { + *delimLast = 0; + } + } + } + + LogFlowFunc(("%s, %s, %s\n", pszFullPath, delimLast, delimSecondLast)); +} + +static int vbsfBuildFullPath(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, + uint32_t cbPath, char **ppszFullPath, uint32_t *pcbFullPathRoot, + bool fWildCard = false, bool fPreserveLastComponent = false) +{ + char *pszHostPath = NULL; + uint32_t fu32PathFlags = 0; + uint32_t fu32Options = VBSF_O_PATH_CHECK_ROOT_ESCAPE + | (fWildCard? VBSF_O_PATH_WILDCARD: 0) + | (fPreserveLastComponent? VBSF_O_PATH_PRESERVE_LAST_COMPONENT: 0); + + int rc = vbsfPathGuestToHost(pClient, root, pPath, cbPath, + &pszHostPath, pcbFullPathRoot, fu32Options, &fu32PathFlags); + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + LogRel2(("SharedFolders: GuestToHost 0x%RX32 [%.*s]->[%s] %Rrc\n", fu32PathFlags, pPath->u16Length, &pPath->String.utf8[0], pszHostPath, rc)); + } + else + { + LogRel2(("SharedFolders: GuestToHost 0x%RX32 [%.*ls]->[%s] %Rrc\n", fu32PathFlags, pPath->u16Length / 2, &pPath->String.ucs2[0], pszHostPath, rc)); + } + + if (RT_SUCCESS(rc)) + { + if (ppszFullPath) + *ppszFullPath = pszHostPath; + } + return rc; +} + +static void vbsfFreeFullPath(char *pszFullPath) +{ + vbsfFreeHostPath(pszFullPath); +} + +typedef enum VBSFCHECKACCESS +{ + VBSF_CHECK_ACCESS_READ = 0, + VBSF_CHECK_ACCESS_WRITE = 1 +} VBSFCHECKACCESS; + +/** + * Check if the handle data is valid and the operation is allowed on the shared folder. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared folder + * @param root The index of the shared folder in the table of mappings. + * @param pHandle Information about the file or directory object. + * @param enmCheckAccess Whether the operation needs read only or write access. + */ +static int vbsfCheckHandleAccess(SHFLCLIENTDATA *pClient, SHFLROOT root, + SHFLFILEHANDLE *pHandle, VBSFCHECKACCESS enmCheckAccess) +{ + /* Handle from the same 'root' index? */ + if (RT_LIKELY(RT_VALID_PTR(pHandle) && root == pHandle->root)) + { /* likely */ } + else + return VERR_INVALID_HANDLE; + + /* Check if the guest is still allowed to access this share. + * vbsfMappingsQueryWritable returns error if the shared folder has been removed from the VM settings. + */ + bool fWritable; + int rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + if (enmCheckAccess == VBSF_CHECK_ACCESS_WRITE) + { + /* Operation requires write access. Check if the shared folder is writable too. */ + if (RT_LIKELY(fWritable)) + { /* likely */ } + else + return VERR_WRITE_PROTECT; + } + + return VINF_SUCCESS; +} + +/** + * Convert shared folder create flags (see include/iprt/shflsvc.h) into iprt create flags. + * + * @returns iprt status code + * @param fWritable whether the shared folder is writable + * @param fShflFlags shared folder create flags + * @param fMode file attributes + * @param handleInitial initial handle + * @param pfOpen Where to return iprt create flags + */ +static int vbsfConvertFileOpenFlags(bool fWritable, unsigned fShflFlags, RTFMODE fMode, SHFLHANDLE handleInitial, uint64_t *pfOpen) +{ + uint64_t fOpen = 0; + int rc = VINF_SUCCESS; + + if ( (fMode & RTFS_DOS_MASK) != 0 + && (fMode & RTFS_UNIX_MASK) == 0) + { + /* A DOS/Windows guest, make RTFS_UNIX_* from RTFS_DOS_*. + * @todo this is based on rtFsModeNormalize/rtFsModeFromDos. + * May be better to use RTFsModeNormalize here. + */ + fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH; + /* x for directories. */ + if (fMode & RTFS_DOS_DIRECTORY) + fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH; + /* writable? */ + if (!(fMode & RTFS_DOS_READONLY)) + fMode |= RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH; + + /* Set the requested mode using only allowed bits. */ + fOpen |= ((fMode & RTFS_UNIX_MASK) << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + } + else + { + /* Old linux and solaris additions did not initialize the Info.Attr.fMode field + * and it contained random bits from stack. Detect this using the handle field value + * passed from the guest: old additions set it (incorrectly) to 0, new additions + * set it to SHFL_HANDLE_NIL(~0). + */ + if (handleInitial == 0) + { + /* Old additions. Do nothing, use default mode. */ + } + else + { + /* New additions or Windows additions. Set the requested mode using only allowed bits. + * Note: Windows guest set RTFS_UNIX_MASK bits to 0, which means a default mode + * will be set in fOpen. + */ + fOpen |= ((fMode & RTFS_UNIX_MASK) << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + } + } + + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_RW)) + { + default: + case SHFL_CF_ACCESS_NONE: + { +#ifdef RT_OS_WINDOWS + if (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_ATTR) != SHFL_CF_ACCESS_ATTR_NONE) + fOpen |= RTFILE_O_ATTR_ONLY; + else +#endif + fOpen |= RTFILE_O_READ; + Log(("FLAG: SHFL_CF_ACCESS_NONE\n")); + break; + } + + case SHFL_CF_ACCESS_READ: + { + fOpen |= RTFILE_O_READ; + Log(("FLAG: SHFL_CF_ACCESS_READ\n")); + break; + } + + case SHFL_CF_ACCESS_WRITE: + { + fOpen |= RTFILE_O_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_WRITE\n")); + break; + } + + case SHFL_CF_ACCESS_READWRITE: + { + fOpen |= RTFILE_O_READWRITE; + Log(("FLAG: SHFL_CF_ACCESS_READWRITE\n")); + break; + } + } + + if (fShflFlags & SHFL_CF_ACCESS_APPEND) + { + fOpen |= RTFILE_O_APPEND; + } + + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_ATTR)) + { + default: + case SHFL_CF_ACCESS_ATTR_NONE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_DEFAULT; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_NONE\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_READ: + { + fOpen |= RTFILE_O_ACCESS_ATTR_READ; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_READ\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_WRITE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_WRITE\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_READWRITE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_READWRITE; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_READWRITE\n")); + break; + } + } + + /* Sharing mask */ + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_DENY)) + { + default: + case SHFL_CF_ACCESS_DENYNONE: + fOpen |= RTFILE_O_DENY_NONE; + Log(("FLAG: SHFL_CF_ACCESS_DENYNONE\n")); + break; + + case SHFL_CF_ACCESS_DENYREAD: + fOpen |= RTFILE_O_DENY_READ; + Log(("FLAG: SHFL_CF_ACCESS_DENYREAD\n")); + break; + + case SHFL_CF_ACCESS_DENYWRITE: + fOpen |= RTFILE_O_DENY_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_DENYWRITE\n")); + break; + + case SHFL_CF_ACCESS_DENYALL: + fOpen |= RTFILE_O_DENY_ALL; + Log(("FLAG: SHFL_CF_ACCESS_DENYALL\n")); + break; + } + + /* Open/Create action mask */ + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_EXISTS)) + { + case SHFL_CF_ACT_OPEN_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN_CREATE; + Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN; + Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_FAIL_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE; + Log(("FLAGS: SHFL_CF_ACT_FAIL_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_REPLACE_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE_REPLACE; + Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE; + Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_OVERWRITE_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE_REPLACE; + Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE; + Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + default: + rc = VERR_INVALID_PARAMETER; + Log(("FLAG: SHFL_CF_ACT_MASK_IF_EXISTS - invalid parameter\n")); + } + + if (RT_SUCCESS(rc)) + { + if (!fWritable) + fOpen &= ~RTFILE_O_WRITE; + + *pfOpen = fOpen; + } + return rc; +} + +/** + * Open a file or create and open a new one. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared folder + * @param root The index of the shared folder in the table of mappings. + * @param pszPath Path to the file or folder on the host. + * @param pParms Input: + * - @a CreateFlags: Creation or open parameters, see include/VBox/shflsvc.h + * - @a Info: When a new file is created this specifies the initial parameters. + * When a file is created or overwritten, it also specifies the + * initial size. + * Output: + * - @a Result: Shared folder status code, see include/VBox/shflsvc.h + * - @a Handle: On success the (shared folder) handle of the file opened or + * created + * - @a Info: On success the parameters of the file opened or created + */ +static int vbsfOpenFile(SHFLCLIENTDATA *pClient, SHFLROOT root, char *pszPath, SHFLCREATEPARMS *pParms) +{ + LogFlow(("vbsfOpenFile: pszPath = %s, pParms = %p\n", pszPath, pParms)); + Log(("SHFL create flags %08x\n", pParms->CreateFlags)); + + RTFILEACTION enmActionTaken = RTFILEACTION_INVALID; + SHFLHANDLE handle = SHFL_HANDLE_NIL; + SHFLFILEHANDLE *pHandle = NULL; + + /* is the guest allowed to write to this share? */ + bool fWritable; + int rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc)) + fWritable = false; + + uint64_t fOpen = 0; + rc = vbsfConvertFileOpenFlags(fWritable, pParms->CreateFlags, pParms->Info.Attr.fMode, pParms->Handle, &fOpen); + if (RT_SUCCESS(rc)) + { + rc = VERR_NO_MEMORY; /* Default error. */ + handle = vbsfAllocFileHandle(pClient); + if (handle != SHFL_HANDLE_NIL) + { + pHandle = vbsfQueryFileHandle(pClient, handle); + if (pHandle) + { + pHandle->root = root; + pHandle->file.fOpenFlags = fOpen; + rc = RTFileOpenEx(pszPath, fOpen, &pHandle->file.Handle, &enmActionTaken); + } + } + } + bool fNoError = false; + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_FILE_NOT_FOUND: + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + pParms->Result = SHFL_PATH_NOT_FOUND; +#endif + /* This actually isn't an error, so correct the rc before return later, + because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */ + fNoError = true; + break; + + case VERR_PATH_NOT_FOUND: +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsInvalidNameForNonDir(pszPath)) + { + rc = VERR_INVALID_NAME; + pParms->Result = SHFL_NO_RESULT; + break; + } +#endif + pParms->Result = SHFL_PATH_NOT_FOUND; + fNoError = true; /* Not an error either (see above). */ + break; + + case VERR_ALREADY_EXISTS: + { + RTFSOBJINFO info; + + /** @todo Possible race left here. */ + if (RT_SUCCESS(RTPathQueryInfoEx(pszPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)))) + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + pParms->Result = SHFL_FILE_EXISTS; + + /* This actually isn't an error, so correct the rc before return later, + because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */ + fNoError = true; + break; + } + + case VERR_TOO_MANY_OPEN_FILES: + { + static int s_cErrors; + if (s_cErrors < 32) + { + LogRel(("SharedFolders host service: Cannot open '%s' -- too many open files.\n", pszPath)); +#if defined RT_OS_LINUX || defined(RT_OS_SOLARIS) + if (s_cErrors < 1) + LogRel(("SharedFolders host service: Try to increase the limit for open files (ulimit -n)\n")); +#endif + s_cErrors++; + } + pParms->Result = SHFL_NO_RESULT; + break; + } + + default: + pParms->Result = SHFL_NO_RESULT; + } + } + else + { + switch (enmActionTaken) + { + default: + AssertFailed(); + RT_FALL_THRU(); + case RTFILEACTION_OPENED: + pParms->Result = SHFL_FILE_EXISTS; + break; + case RTFILEACTION_CREATED: + pParms->Result = SHFL_FILE_CREATED; + break; + case RTFILEACTION_REPLACED: + case RTFILEACTION_TRUNCATED: /* not quite right */ + pParms->Result = SHFL_FILE_REPLACED; + break; + } + + if ( (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_REPLACE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_OVERWRITE_IF_EXISTS) + { + /* For now, we do not treat a failure here as fatal. */ + /** @todo Also set the size for SHFL_CF_ACT_CREATE_IF_NEW if SHFL_CF_ACT_FAIL_IF_EXISTS is set. */ + /** @todo r=bird: Exactly document cbObject usage and see what we can get + * away with here. I suspect it is only needed for windows and only + * with SHFL_FILE_CREATED and SHFL_FILE_REPLACED, and only if + * cbObject is non-zero. */ + RTFileSetSize(pHandle->file.Handle, pParms->Info.cbObject); + } +#if 0 + /** @todo */ + /* Set new attributes. */ + if ( ( SHFL_CF_ACT_REPLACE_IF_EXISTS + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS)) + || ( SHFL_CF_ACT_CREATE_IF_NEW + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW))) + { + RTFileSetTimes(pHandle->file.Handle, + &pParms->Info.AccessTime, + &pParms->Info.ModificationTime, + &pParms->Info.ChangeTime, + &pParms->Info.BirthTime + ); + + RTFileSetMode (pHandle->file.Handle, pParms->Info.Attr.fMode); + } +#endif + RTFSOBJINFO info; + + /* Get file information */ + rc = RTFileQueryInfo(pHandle->file.Handle, &info, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + } + /* Free resources if any part of the function has failed. */ + if (RT_FAILURE(rc)) + { + if ( (0 != pHandle) + && (NIL_RTFILE != pHandle->file.Handle) + && (0 != pHandle->file.Handle)) + { + RTFileClose(pHandle->file.Handle); + pHandle->file.Handle = NIL_RTFILE; + } + if (SHFL_HANDLE_NIL != handle) + { + vbsfFreeFileHandle(pClient, handle); + } + pParms->Handle = SHFL_HANDLE_NIL; + } + else + { + pParms->Handle = handle; + } + + /* Report the driver that all is okay, we're done here */ + if (fNoError) + rc = VINF_SUCCESS; + + LogFlow(("vbsfOpenFile: rc = %Rrc\n", rc)); + return rc; +} + +/** + * Open a folder or create and open a new one. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared + * folder + * @param root The index of the shared folder in the table of mappings. + * @param pszPath Path to the file or folder on the host. + * @param pParms Input: @a CreateFlags Creation or open parameters, see + * include/VBox/shflsvc.h + * Output: + * - @a Result: Shared folder status code, see include/VBox/shflsvc.h + * - @a Handle: On success the (shared folder) handle of the folder opened or + * created + * - @a Info: On success the parameters of the folder opened or created + * + * @note folders are created with fMode = 0777 + */ +static int vbsfOpenDir(SHFLCLIENTDATA *pClient, SHFLROOT root, char *pszPath, + SHFLCREATEPARMS *pParms) +{ + LogFlow(("vbsfOpenDir: pszPath = %s, pParms = %p\n", pszPath, pParms)); + Log(("SHFL create flags %08x\n", pParms->CreateFlags)); + + int rc = VERR_NO_MEMORY; + SHFLHANDLE handle = vbsfAllocDirHandle(pClient); + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, handle); + if (0 != pHandle) + { + pHandle->root = root; + pParms->Result = SHFL_FILE_EXISTS; /* May be overwritten with SHFL_FILE_CREATED. */ + /** @todo Can anyone think of a sensible, race-less way to do this? Although + I suspect that the race is inherent, due to the API available... */ + /* Try to create the folder first if "create if new" is specified. If this + fails, and "open if exists" is specified, then we ignore the failure and try + to open the folder anyway. */ + if ( SHFL_CF_ACT_CREATE_IF_NEW + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + /** @todo render supplied attributes. + * bird: The guest should specify this. For windows guests RTFS_DOS_DIRECTORY should suffice. */ + RTFMODE fMode = 0777; + + pParms->Result = SHFL_FILE_CREATED; + rc = RTDirCreate(pszPath, fMode, 0); + if (RT_FAILURE(rc)) + { + /** @todo we still return 'rc' as failure here, so this is mostly pointless. */ + switch (rc) + { + case VERR_ALREADY_EXISTS: + pParms->Result = SHFL_FILE_EXISTS; + break; + case VERR_PATH_NOT_FOUND: + pParms->Result = SHFL_PATH_NOT_FOUND; + break; + case VERR_FILE_NOT_FOUND: /* may happen on posix */ + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VERR_PATH_NOT_FOUND; + } +#endif + break; + default: + pParms->Result = SHFL_NO_RESULT; + } + } + } + else + rc = VINF_SUCCESS; + if ( RT_SUCCESS(rc) + || (SHFL_CF_ACT_OPEN_IF_EXISTS == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS))) + { + /* Open the directory now */ + rc = RTDirOpenFiltered(&pHandle->dir.Handle, pszPath, RTDIRFILTER_NONE, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO info; + + rc = RTDirQueryInfo(pHandle->dir.Handle, &info, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + } + else + { + /** @todo we still return 'rc' as failure here, so this is mostly pointless. */ + switch (rc) + { + case VERR_FILE_NOT_FOUND: + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VERR_PATH_NOT_FOUND; + } +#endif + break; + case VERR_PATH_NOT_FOUND: + pParms->Result = SHFL_PATH_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsNotADirectory(pszPath)) + { + pParms->Result = SHFL_FILE_EXISTS; + rc = VERR_NOT_A_DIRECTORY; + break; + } +#endif + break; + case VERR_ACCESS_DENIED: + pParms->Result = SHFL_FILE_EXISTS; + break; + default: + pParms->Result = SHFL_NO_RESULT; + } + } + } + } + if (RT_FAILURE(rc)) + { + if ( (0 != pHandle) + && (0 != pHandle->dir.Handle)) + { + RTDirClose(pHandle->dir.Handle); + pHandle->dir.Handle = 0; + } + if (SHFL_HANDLE_NIL != handle) + { + vbsfFreeFileHandle(pClient, handle); + } + pParms->Handle = SHFL_HANDLE_NIL; + } + else + { + pParms->Handle = handle; + } + LogFlow(("vbsfOpenDir: rc = %Rrc\n", rc)); + return rc; +} + +static int vbsfCloseDir(SHFLFILEHANDLE *pHandle) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCloseDir: Handle = %08X Search Handle = %08X\n", + pHandle->dir.Handle, pHandle->dir.SearchHandle)); + + RTDirClose(pHandle->dir.Handle); + + if (pHandle->dir.SearchHandle) + RTDirClose(pHandle->dir.SearchHandle); + + if (pHandle->dir.pLastValidEntry) + { + RTMemFree(pHandle->dir.pLastValidEntry); + pHandle->dir.pLastValidEntry = NULL; + } + + LogFlow(("vbsfCloseDir: rc = %d\n", rc)); + + return rc; +} + + +static int vbsfCloseFile(SHFLFILEHANDLE *pHandle) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCloseFile: Handle = %08X\n", + pHandle->file.Handle)); + + rc = RTFileClose(pHandle->file.Handle); + + LogFlow(("vbsfCloseFile: rc = %d\n", rc)); + + return rc; +} + +/** + * Look up file or folder information by host path. + * + * @returns iprt status code (currently VINF_SUCCESS) + * @param pClient client data + * @param pszPath The path of the file to be looked up + * @param pParms Output: + * - @a Result: Status of the operation (success or error) + * - @a Info: On success, information returned about the + * file + */ +static int vbsfLookupFile(SHFLCLIENTDATA *pClient, char *pszPath, SHFLCREATEPARMS *pParms) +{ + RTFSOBJINFO info; + int rc; + + rc = RTPathQueryInfoEx(pszPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + LogFlow(("SHFL_CF_LOOKUP\n")); + /* Client just wants to know if the object exists. */ + switch (rc) + { + case VINF_SUCCESS: + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + pParms->Result = SHFL_FILE_EXISTS; + break; + } + + case VERR_FILE_NOT_FOUND: + { + pParms->Result = SHFL_FILE_NOT_FOUND; + rc = VINF_SUCCESS; + break; + } + + case VERR_PATH_NOT_FOUND: + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VINF_SUCCESS; + break; + } + } + pParms->Handle = SHFL_HANDLE_NIL; + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_CREATE API. Located here as a form of API + * documentation. */ +void testCreate(RTTEST hTest) +{ + /* Simple opening of an existing file. */ + testCreateFileSimple(hTest); + testCreateFileSimpleCaseInsensitive(hTest); + /* Simple opening of an existing directory. */ + /** @todo How do wildcards in the path name work? */ + testCreateDirSimple(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testCreateBadParameters(hTest); + /* Add tests as required... */ +} +#endif + +/** + * Create or open a file or folder. Perform character set and case + * conversion on the file name if necessary. + * + * @returns IPRT status code, but see note below + * @param pClient Data structure describing the client accessing the + * shared folder + * @param root The index of the shared folder in the table of mappings. + * The host path of the shared folder is found using this. + * @param pPath The path of the file or folder relative to the host path + * indexed by root. + * @param cbPath Presumably the length of the path in pPath. Actually + * ignored, as pPath contains a length parameter. + * @param pParms Input: If a new file is created or an old one + * overwritten, set the @a Info attribute. + * + * Output: + * - @a Result Shared folder result code, see include/VBox/shflsvc.h + * - @a Handle Shared folder handle to the newly opened file + * - @a Info Attributes of the file or folder opened + * + * @note This function returns success if a "non-exceptional" error occurred, + * such as "no such file". In this case, the caller should check the + * pParms->Result return value and whether pParms->Handle is valid. + */ +int vbsfCreate(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, SHFLCREATEPARMS *pParms) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCreate: pClient = %p, pPath = %p, cbPath = %d, pParms = %p CreateFlags=%x\n", + pClient, pPath, cbPath, pParms, pParms->CreateFlags)); + + /* Check the client access rights to the root. */ + /** @todo */ + + /* Build a host full path for the given path, handle file name case issues (if the guest + * expects case-insensitive paths but the host is case-sensitive) and convert ucs2 to utf8 if + * necessary. + */ + char *pszFullPath = NULL; + uint32_t cbFullPathRoot = 0; + + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, &cbFullPathRoot); + if (RT_SUCCESS(rc)) + { + /* Reset return value in case client forgot to do so. + * pParms->Handle must not be reset here, as it is used + * in vbsfOpenFile to detect old additions. + */ + pParms->Result = SHFL_NO_RESULT; + + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_LOOKUP)) + { + rc = vbsfLookupFile(pClient, pszFullPath, pParms); + } + else + { + /* Query path information. */ + RTFSOBJINFO info; + + rc = RTPathQueryInfoEx(pszFullPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + LogFlow(("RTPathQueryInfoEx returned %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + /* Mark it as a directory in case the caller didn't. */ + /** + * @todo I left this in in order not to change the behaviour of the + * function too much. Is it really needed, and should it really be + * here? + */ + if (BIT_FLAG(info.Attr.fMode, RTFS_DOS_DIRECTORY)) + { + pParms->CreateFlags |= SHFL_CF_DIRECTORY; + } + + /** + * @todo This should be in the Windows Guest Additions, as no-one else + * needs it. + */ + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_OPEN_TARGET_DIRECTORY)) + { + vbsfStripLastComponent(pszFullPath, cbFullPathRoot); + pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_EXISTS; + pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_NEW; + pParms->CreateFlags |= SHFL_CF_DIRECTORY; + pParms->CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS; + pParms->CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW; + } + } + + rc = VINF_SUCCESS; + + /* Note: do not check the SHFL_CF_ACCESS_WRITE here, only check if the open operation + * will cause changes. + * + * Actual operations (write, set attr, etc), which can write to a shared folder, have + * the check and will return VERR_WRITE_PROTECT if the folder is not writable. + */ + if ( (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_REPLACE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_OVERWRITE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_NEW) == SHFL_CF_ACT_CREATE_IF_NEW + ) + { + /* is the guest allowed to write to this share? */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc) || !fWritable) + rc = VERR_WRITE_PROTECT; + } + + if (RT_SUCCESS(rc)) + { + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_DIRECTORY)) + { + rc = vbsfOpenDir(pClient, root, pszFullPath, pParms); + } + else + { + rc = vbsfOpenFile(pClient, root, pszFullPath, pParms); + } + } + else + { + pParms->Handle = SHFL_HANDLE_NIL; + } + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + + Log(("vbsfCreate: handle = %RX64 rc = %Rrc result=%x\n", (uint64_t)pParms->Handle, rc, pParms->Result)); + + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_CLOSE API. Located here as a form of API + * documentation. */ +void testClose(RTTEST hTest) +{ + /* If the API parameters are invalid the API should fail. */ + testCloseBadParameters(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfClose(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle) +{ + LogFunc(("pClient = %p, root 0x%RX32, Handle = 0x%RX64\n", + pClient, root, Handle)); + + int rc = VERR_INVALID_HANDLE; + uint32_t type = vbsfQueryHandleType(pClient, Handle); + Assert((type & ~(SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) == 0); + switch (type & (SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) + { + case SHFL_HF_TYPE_DIR: + { + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + if (RT_LIKELY(pHandle && root == pHandle->root)) + { + rc = vbsfCloseDir(pHandle); + vbsfFreeFileHandle(pClient, Handle); + } + break; + } + case SHFL_HF_TYPE_FILE: + { + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + if (RT_LIKELY(pHandle && root == pHandle->root)) + { + rc = vbsfCloseFile(pHandle); + vbsfFreeFileHandle(pClient, Handle); + } + break; + } + default: + break; + } + + LogFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** + * Helper for vbsfReadPages and vbsfWritePages that creates a S/G buffer from a + * pages parameter. + */ +static int vbsfPagesToSgBuf(VBOXHGCMSVCPARMPAGES const *pPages, uint32_t cbLeft, PRTSGBUF pSgBuf) +{ + PRTSGSEG paSegs = (PRTSGSEG)RTMemTmpAlloc(sizeof(paSegs[0]) * pPages->cPages); + if (paSegs) + { + /* + * Convert the pages to segments. + */ + uint32_t iSeg = 0; + uint32_t iPage = 0; + for (;;) + { + Assert(iSeg < pPages->cPages); + Assert(iPage < pPages->cPages); + + /* Current page. */ + void *pvSeg; + paSegs[iSeg].pvSeg = pvSeg = pPages->papvPages[iPage]; + uint32_t cbSeg = PAGE_SIZE - (uint32_t)((uintptr_t)pvSeg & PAGE_OFFSET_MASK); + iPage++; + + /* Adjacent to the next page? */ + while ( iPage < pPages->cPages + && (uintptr_t)pvSeg + cbSeg == (uintptr_t)pPages->papvPages[iPage]) + { + iPage++; + cbSeg += PAGE_SIZE; + } + + /* Adjust for max size. */ + if (cbLeft <= cbSeg) + { + paSegs[iSeg++].cbSeg = cbLeft; + break; + } + paSegs[iSeg++].cbSeg = cbSeg; + cbLeft -= cbSeg; + } + + /* + * Initialize the s/g buffer and execute the read. + */ + RTSgBufInit(pSgBuf, paSegs, iSeg); + return VINF_SUCCESS; + } + pSgBuf->paSegs = NULL; + return VERR_NO_TMP_MEMORY; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_READ API. Located here as a form of API + * documentation. */ +void testRead(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testReadBadParameters(hTest); + /* Basic reading from a file. */ + testReadFileSimple(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRead(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64, offset 0x%RX64, bytes 0x%RX32\n", + pClient, root, Handle, offset, pcbBuffer? *pcbBuffer: 0)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + size_t const cbToRead = *pcbBuffer; + if (RT_LIKELY(cbToRead > 0)) + { + size_t cbActual = 0; + rc = RTFileReadAt(pHandle->file.Handle, offset, pBuffer, cbToRead, &cbActual); + *pcbBuffer = (uint32_t)cbActual; + } + else + { + /* Reading zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbBuffer = 0; + + LogFunc(("%Rrc bytes read 0x%RX32\n", rc, *pcbBuffer)); + return rc; +} + +/** + * SHFL_FN_READ w/o bounce buffering. + */ +int vbsfReadPages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t offFile, + uint32_t *pcbRead, PVBOXHGCMSVCPARMPAGES pPages) +{ + LogFunc(("pClient %p, idRoot %#RX32, hFile %#RX64, offFile %#RX64, cbRead %#RX32, cPages %#x\n", + pClient, idRoot, hFile, offFile, *pcbRead, pPages->cPages)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + size_t cbTotal = 0; + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + uint32_t const cbToRead = *pcbRead; + if (cbToRead > 0) + { + ASSERT_GUEST_RETURN(pPages->cPages > 0, VERR_INTERNAL_ERROR_3); + + /* + * Convert to a scatter-gather buffer. + * + * We need not do any platform specific code here as the RTSGBUF + * segment array maps directly onto the posix iovec structure. + * Windows does currently benefit much from this conversion, but + * so be it. + */ + RTSGBUF SgBuf; + rc = vbsfPagesToSgBuf(pPages, cbToRead, &SgBuf); + if (RT_SUCCESS(rc)) + { + rc = RTFileSgReadAt(pHandle->file.Handle, offFile, &SgBuf, cbToRead, &cbTotal); + while (rc == VERR_INTERRUPTED) + { + RTSgBufReset(&SgBuf); + rc = RTFileSgReadAt(pHandle->file.Handle, offFile, &SgBuf, cbToRead, &cbTotal); + } + + RTMemTmpFree((void *)SgBuf.paSegs); + } + else + rc = VERR_NO_TMP_MEMORY; + + *pcbRead = (uint32_t)cbTotal; + } + else + { + /* Reading zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbRead = 0; + + LogFunc(("%Rrc bytes read %#zx\n", rc, cbTotal)); + return rc; +} + +/** + * Helps with writes to RTFILE_O_APPEND files. + */ +static uint64_t vbsfWriteCalcPostAppendFilePosition(RTFILE hFile, uint64_t offGuessed) +{ + RTFSOBJINFO ObjInfo; + int rc2 = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc2) && (uint64_t)ObjInfo.cbObject >= offGuessed) + return ObjInfo.cbObject; + return offGuessed; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_WRITE API. Located here as a form of API + * documentation. */ +void testWrite(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testWriteBadParameters(hTest); + /* Simple test of writing to a file. */ + testWriteFileSimple(hTest); + /* Add tests as required... */ +} +#endif +int vbsfWrite(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + uint64_t offFile = *poffFile; + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64, offFile 0x%RX64, bytes 0x%RX32\n", + pClient, idRoot, hFile, offFile, *pcbBuffer)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + size_t const cbToWrite = *pcbBuffer; + if (RT_LIKELY(cbToWrite != 0)) + { + size_t cbWritten = 0; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + rc = RTFileWriteAt(pHandle->file.Handle, offFile, pBuffer, cbToWrite, &cbWritten); + else + { + rc = RTFileSeek(pHandle->file.Handle, offFile, RTFILE_SEEK_BEGIN, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(pHandle->file.Handle, pBuffer, cbToWrite, &cbWritten); + *pcbBuffer = (uint32_t)cbWritten; + } + } + + /* Update the file offset (mainly for RTFILE_O_APPEND), */ + if (RT_SUCCESS(rc)) + { + offFile += cbWritten; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + *poffFile = offFile; + else + *poffFile = vbsfWriteCalcPostAppendFilePosition(pHandle->file.Handle, offFile); + } + } + else + { + /** @todo What writing zero bytes should do? */ + rc = VINF_SUCCESS; + } + } + else + *pcbBuffer = 0; + LogFunc(("%Rrc bytes written 0x%RX32\n", rc, *pcbBuffer)); + return rc; +} + +/** + * SHFL_FN_WRITE w/o bounce buffering. + */ +int vbsfWritePages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbWrite, PVBOXHGCMSVCPARMPAGES pPages) +{ + uint64_t offFile = *poffFile; + LogFunc(("pClient %p, idRoot %#RX32, hFile %#RX64, offFile %#RX64, cbWrite %#RX32, cPages %#x\n", + pClient, idRoot, hFile, offFile, *pcbWrite, pPages->cPages)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + size_t cbTotal = 0; + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + uint32_t const cbToWrite = *pcbWrite; + if (cbToWrite > 0) + { + ASSERT_GUEST_RETURN(pPages->cPages > 0, VERR_INTERNAL_ERROR_3); + + /* + * Convert to a scatter-gather buffer. + * + * We need not do any platform specific code here as the RTSGBUF + * segment array maps directly onto the posix iovec structure. + * Windows does currently benefit much from this conversion, but + * so be it. + */ + RTSGBUF SgBuf; + rc = vbsfPagesToSgBuf(pPages, cbToWrite, &SgBuf); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_LINUX + /* Cannot use RTFileSgWriteAt or RTFileWriteAt when opened with + RTFILE_O_APPEND, except for on linux where the offset is + then ignored by the low level kernel API. */ + if (pHandle->file.fOpenFlags & RTFILE_O_APPEND) + { + /* paranoia */ + RTFileSeek(pHandle->file.Handle, 0, RTFILE_SEEK_END, NULL); + + for (size_t iSeg = 0; iSeg < SgBuf.cSegs; iSeg++) + { + size_t cbWrittenNow = 0; + do + rc = RTFileWrite(pHandle->file.Handle, SgBuf.paSegs[iSeg].pvSeg, + SgBuf.paSegs[iSeg].cbSeg, &cbWrittenNow); + while (rc == VERR_INTERRUPTED); + if (RT_SUCCESS(rc)) + { + cbTotal += cbWrittenNow; + if (cbWrittenNow < SgBuf.paSegs[iSeg].cbSeg) + break; + } + else + { + if (cbTotal > 0) + rc = VINF_SUCCESS; + break; + } + } + } + else +#endif + { + rc = RTFileSgWriteAt(pHandle->file.Handle, offFile, &SgBuf, cbToWrite, &cbTotal); + while (rc == VERR_INTERRUPTED) + { + RTSgBufReset(&SgBuf); + rc = RTFileSgWriteAt(pHandle->file.Handle, offFile, &SgBuf, cbToWrite, &cbTotal); + } + } + + RTMemTmpFree((void *)SgBuf.paSegs); + + /* Update the file offset (mainly for RTFILE_O_APPEND), */ + if (RT_SUCCESS(rc)) + { + offFile += cbTotal; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + *poffFile = offFile; + else + *poffFile = vbsfWriteCalcPostAppendFilePosition(pHandle->file.Handle, offFile); + } + } + else + rc = VERR_NO_TMP_MEMORY; + + *pcbWrite = (uint32_t)cbTotal; + } + else + { + /* Writing zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbWrite = 0; + + LogFunc(("%Rrc bytes written %#zx\n", rc, cbTotal)); + return rc; +} + +/** + * Implements SHFL_FN_COPY_FILE_PART (wrapping RTFileCopyPart). + */ +int vbsfCopyFilePart(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, SHFLHANDLE hFileSrc, uint64_t offSrc, + SHFLROOT idRootDst, SHFLHANDLE hFileDst, uint64_t offDst, uint64_t *pcbToCopy, uint32_t fFlags) +{ + /* + * Validate and translates handles. + */ + uint64_t const cbToCopy = *pcbToCopy; + *pcbToCopy = 0; + LogFunc(("pClient %p, idRootSrc %#RX32, hFileSrc %#RX64, offSrc %#RX64, idRootSrc %#RX32, hFileSrc %#RX64, offSrc %#RX64, cbToCopy %#RX64, fFlags %#x\n", + pClient, idRootSrc, hFileSrc, offSrc, idRootDst, hFileDst, offDst, cbToCopy, fFlags)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + uint64_t cbTotal = 0; + + SHFLFILEHANDLE *pHandleSrc = vbsfQueryFileHandle(pClient, hFileSrc); + int rc = vbsfCheckHandleAccess(pClient, idRootSrc, pHandleSrc, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + SHFLFILEHANDLE *pHandleDst = vbsfQueryFileHandle(pClient, hFileDst); + rc = vbsfCheckHandleAccess(pClient, idRootDst, pHandleDst, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + /* + * Do the job. + */ + rc = RTFileCopyPart(pHandleSrc->file.Handle, offSrc, pHandleDst->file.Handle, offDst, cbToCopy, 0, &cbTotal); + *pcbToCopy = cbTotal; + } + } + + RT_NOREF(fFlags); + LogFunc(("%Rrc bytes written %#zx\n", rc, cbTotal)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_FLUSH API. Located here as a form of API + * documentation. */ +void testFlush(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testFlushBadParameters(hTest); + /* Simple opening and flushing of a file. */ + testFlushFileSimple(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfFlush(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle) +{ + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64\n", + pClient, root, Handle)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + rc = RTFileFlush(pHandle->file.Handle); + + LogFunc(("%Rrc\n", rc)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_LIST API. Located here as a form of API + * documentation. */ +void testDirList(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testDirListBadParameters(hTest); + /* Test listing an empty directory (simple edge case). */ + testDirListEmpty(hTest); + /* Add tests as required... */ +} +#endif +int vbsfDirList(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, SHFLSTRING *pPath, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer, uint32_t *pIndex, uint32_t *pcFiles) +{ + PRTDIRENTRYEX pDirEntry = 0, pDirEntryOrg; + uint32_t cbDirEntry, cbBufferOrg; + PSHFLDIRINFO pSFDEntry; + PRTUTF16 pwszString; + RTDIR hDir; + const bool fUtf8 = BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8) != 0; + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + Assert(*pIndex == 0); + + cbDirEntry = 4096; + pDirEntryOrg = pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry); + if (pDirEntry == 0) + { + AssertFailed(); + return VERR_NO_MEMORY; + } + + cbBufferOrg = *pcbBuffer; + *pcbBuffer = 0; + pSFDEntry = (PSHFLDIRINFO)pBuffer; + + *pIndex = 1; /* not yet complete */ + *pcFiles = 0; + + if (!pPath) + hDir = pHandle->dir.Handle; + else + { + if (pHandle->dir.SearchHandle == 0) + { + /* Build a host full path for the given path + * and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + + Assert(pHandle->dir.pLastValidEntry == 0); + + rc = vbsfBuildFullPath(pClient, root, pPath, pPath->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPath, NULL, true); + + if (RT_SUCCESS(rc)) + { + rc = RTDirOpenFiltered(&pHandle->dir.SearchHandle, pszFullPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + + if (RT_FAILURE(rc)) + goto end; + } + else + goto end; + flags &= ~SHFL_LIST_RESTART; + } + Assert(pHandle->dir.SearchHandle); + hDir = pHandle->dir.SearchHandle; + } + + if (flags & SHFL_LIST_RESTART) + { + rc = RTDirRewind(hDir); + if (RT_FAILURE(rc)) + goto end; + } + + while (cbBufferOrg) + { + size_t cbDirEntrySize = cbDirEntry; + uint32_t cbNeeded; + + /* Do we still have a valid last entry for the active search? If so, then return it here */ + if (pHandle->dir.pLastValidEntry) + { + pDirEntry = pHandle->dir.pLastValidEntry; + } + else + { + pDirEntry = pDirEntryOrg; + + rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + if (rc == VERR_NO_MORE_FILES) + { + *pIndex = 0; /* listing completed */ + break; + } + + if ( rc != VINF_SUCCESS + && rc != VWRN_NO_DIRENT_INFO) + { + //AssertFailed(); + if ( rc == VERR_NO_TRANSLATION + || rc == VERR_INVALID_UTF8_ENCODING) + continue; + break; + } + } + + cbNeeded = RT_OFFSETOF(SHFLDIRINFO, name.String); + if (fUtf8) + cbNeeded += pDirEntry->cbName + 1; + else + /* Overestimating, but that's ok */ + cbNeeded += (pDirEntry->cbName + 1) * 2; + + if (cbBufferOrg < cbNeeded) + { + /* No room, so save this directory entry, or else it's lost forever */ + pHandle->dir.pLastValidEntry = pDirEntry; + + if (*pcFiles == 0) + { + AssertFailed(); + return VINF_BUFFER_OVERFLOW; /* Return directly and don't free pDirEntry */ + } + return VINF_SUCCESS; /* Return directly and don't free pDirEntry */ + } + +#ifdef RT_OS_WINDOWS + pDirEntry->Info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pSFDEntry->Info, &pDirEntry->Info); + + /* The shortname (only used by OS/2 atm): */ + Assert(pDirEntry->cwcShortName < RT_ELEMENTS(pSFDEntry->uszShortName)); + Assert(pDirEntry->wszShortName[pDirEntry->cwcShortName] == '\0'); + pSFDEntry->cucShortName = pDirEntry->cwcShortName; + if (pDirEntry->cwcShortName) + memcpy(pSFDEntry->uszShortName, pDirEntry->wszShortName, sizeof(pSFDEntry->uszShortName)); + + /* The name: */ + if (fUtf8) + { + void *src, *dst; + + src = &pDirEntry->szName[0]; + dst = &pSFDEntry->name.String.utf8[0]; + + memcpy(dst, src, pDirEntry->cbName + 1); + + pSFDEntry->name.u16Size = pDirEntry->cbName + 1; + pSFDEntry->name.u16Length = pDirEntry->cbName; + } + else + { + pSFDEntry->name.String.ucs2[0] = 0; + pwszString = pSFDEntry->name.String.ucs2; + int rc2 = RTStrToUtf16Ex(pDirEntry->szName, RTSTR_MAX, &pwszString, pDirEntry->cbName+1, NULL); + AssertRC(rc2); + +#ifdef RT_OS_DARWIN +/** @todo This belongs in rtPathToNative or in the windows shared folder file system driver... + * The question is simply whether the NFD normalization is actually applied on a (virtual) file + * system level in darwin, or just by the user mode application libs. */ + { + // Convert to + // Normalization Form C (composed Unicode). We need this because + // Mac OS X file system uses NFD (Normalization Form D :decomposed Unicode) + // while most other OS', server-side programs usually expect NFC. + uint16_t ucs2Length; + CFRange rangeCharacters; + CFMutableStringRef inStr = ::CFStringCreateMutable(NULL, 0); + + ::CFStringAppendCharacters(inStr, (UniChar *)pwszString, RTUtf16Len(pwszString)); + ::CFStringNormalize(inStr, kCFStringNormalizationFormC); + ucs2Length = ::CFStringGetLength(inStr); + + rangeCharacters.location = 0; + rangeCharacters.length = ucs2Length; + ::CFStringGetCharacters(inStr, rangeCharacters, pwszString); + pwszString[ucs2Length] = 0x0000; // NULL terminated + + CFRelease(inStr); + } +#endif + pSFDEntry->name.u16Length = (uint32_t)RTUtf16Len(pSFDEntry->name.String.ucs2) * 2; + pSFDEntry->name.u16Size = pSFDEntry->name.u16Length + 2; + + Log(("SHFL: File name size %d\n", pSFDEntry->name.u16Size)); + Log(("SHFL: File name %ls\n", &pSFDEntry->name.String.ucs2)); + + // adjust cbNeeded (it was overestimated before) + cbNeeded = RT_OFFSETOF(SHFLDIRINFO, name.String) + pSFDEntry->name.u16Size; + } + + /* Advance */ + pSFDEntry = (PSHFLDIRINFO)((uintptr_t)pSFDEntry + cbNeeded); + *pcbBuffer += cbNeeded; + cbBufferOrg-= cbNeeded; + + *pcFiles += 1; + + /* Free the saved last entry, that we've just returned */ + if (pHandle->dir.pLastValidEntry) + { + RTMemFree(pHandle->dir.pLastValidEntry); + pHandle->dir.pLastValidEntry = NULL; + + /* And use the newly allocated buffer from now. */ + pDirEntry = pDirEntryOrg; + } + + if (flags & SHFL_LIST_RETURN_ONE) + break; /* we're done */ + } + Assert(rc != VINF_SUCCESS || *pcbBuffer > 0); + +end: + if (pDirEntry) + RTMemFree(pDirEntry); + + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_READLINK API. Located here as a form of API + * documentation. */ +void testReadLink(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testReadLinkBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfReadLink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, uint8_t *pBuffer, uint32_t cbBuffer) +{ + int rc = VINF_SUCCESS; + + if (pPath == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Build a host full path for the given path, handle file name case issues + * (if the guest expects case-insensitive paths but the host is + * case-sensitive) and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + uint32_t cbFullPathRoot = 0; + + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, &cbFullPathRoot); + + if (RT_SUCCESS(rc)) + { + rc = RTSymlinkRead(pszFullPath, (char *) pBuffer, cbBuffer, 0); + if (RT_SUCCESS(rc)) + { + /* Convert the slashes in the link target to the guest path separator characters. */ + /** @todo r=bird: for some messed up reason, we return UTF-8 here rather than + * the character set selected by the client. We also don't return the + * length, so the clients are paranoid about the zero termination behavior. */ + char ch; + char *psz = (char *)pBuffer; + while ((ch = *psz) != '\0') + { + if (RTPATH_IS_SLASH(ch)) + *psz = pClient->PathDelimiter; + psz++; + } + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + + return rc; +} + +int vbsfQueryFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + uint32_t type = vbsfQueryHandleType(pClient, Handle); + int rc = VINF_SUCCESS; + SHFLFSOBJINFO *pObjInfo = (SHFLFSOBJINFO *)pBuffer; + RTFSOBJINFO fileinfo; + + + AssertReturn(type == SHFL_HF_TYPE_DIR || type == SHFL_HF_TYPE_FILE, VERR_INVALID_PARAMETER); + AssertReturn(pcbBuffer != NULL, VERR_INVALID_PARAMETER); + AssertReturn(pObjInfo != NULL, VERR_INVALID_PARAMETER); + AssertReturn(*pcbBuffer >= sizeof(SHFLFSOBJINFO), VERR_INVALID_PARAMETER); + + /** @todo other options */ + Assert(flags == (SHFL_INFO_GET|SHFL_INFO_FILE)); + + *pcbBuffer = 0; + + if (type == SHFL_HF_TYPE_DIR) + { + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + rc = RTDirQueryInfo(pHandle->dir.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); + } + else + { + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + rc = RTFileQueryInfo(pHandle->file.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); +#ifdef RT_OS_WINDOWS + if (RT_SUCCESS(rc) && RTFS_IS_FILE(pObjInfo->Attr.fMode)) + pObjInfo->Attr.fMode |= 0111; +#endif + } + if (rc == VINF_SUCCESS) + { + vbfsCopyFsObjInfoFromIprt(pObjInfo, &fileinfo); + *pcbBuffer = sizeof(SHFLFSOBJINFO); + } + else + AssertFailed(); + + return rc; +} + +static int vbsfSetFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + uint32_t type = vbsfQueryHandleType(pClient, Handle); + int rc = VINF_SUCCESS; + SHFLFSOBJINFO *pSFDEntry; + + if ( !(type == SHFL_HF_TYPE_DIR || type == SHFL_HF_TYPE_FILE) + || pcbBuffer == 0 + || pBuffer == 0 + || *pcbBuffer < sizeof(SHFLFSOBJINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + *pcbBuffer = 0; + pSFDEntry = (SHFLFSOBJINFO *)pBuffer; + + Assert(flags == (SHFL_INFO_SET | SHFL_INFO_FILE)); + + /* + * Get the handle. + */ + SHFLFILEHANDLE *pHandle; + if (type == SHFL_HF_TYPE_FILE) + { + pHandle = vbsfQueryFileHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + } + else + { + Assert(type == SHFL_HF_TYPE_DIR); + pHandle = vbsfQueryDirHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + } + if (RT_SUCCESS(rc)) + { + /* + * Any times to set? + */ + if ( RTTimeSpecGetNano(&pSFDEntry->AccessTime) + || RTTimeSpecGetNano(&pSFDEntry->ModificationTime) + || RTTimeSpecGetNano(&pSFDEntry->ChangeTime) + || RTTimeSpecGetNano(&pSFDEntry->BirthTime)) + { + + /* Change only the time values that are not zero */ + if (type == SHFL_HF_TYPE_FILE) + rc = RTFileSetTimes(pHandle->file.Handle, + RTTimeSpecGetNano(&pSFDEntry->AccessTime) ? &pSFDEntry->AccessTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ModificationTime) ? &pSFDEntry->ModificationTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ChangeTime) ? &pSFDEntry->ChangeTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->BirthTime) ? &pSFDEntry->BirthTime : NULL); + else + rc = RTDirSetTimes( pHandle->dir.Handle, + RTTimeSpecGetNano(&pSFDEntry->AccessTime) ? &pSFDEntry->AccessTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ModificationTime) ? &pSFDEntry->ModificationTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ChangeTime) ? &pSFDEntry->ChangeTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->BirthTime) ? &pSFDEntry->BirthTime : NULL); + if (RT_FAILURE(rc)) + { + Log(("RT%sSetTimes failed with %Rrc\n", type == SHFL_HF_TYPE_FILE ? "File" : "Dir", rc)); + Log(("AccessTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->AccessTime))); + Log(("ModificationTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->ModificationTime))); + Log(("ChangeTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->ChangeTime))); + Log(("BirthTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->BirthTime))); + /* "temporary" hack */ + rc = VINF_SUCCESS; + } + } + + /* + * Any mode changes? + */ + if (pSFDEntry->Attr.fMode) + { + RTFMODE fMode = pSFDEntry->Attr.fMode; + + if (type == SHFL_HF_TYPE_FILE) + { +#ifndef RT_OS_WINDOWS + /* Don't allow the guest to clear the read own bit, otherwise the guest wouldn't + * be able to access this file anymore. Only for guests, which set the UNIX mode. + * Also, clear bits which we don't pass through for security reasons. */ + if (fMode & RTFS_UNIX_MASK) + { + fMode |= RTFS_UNIX_IRUSR; + fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT); + } +#endif + rc = RTFileSetMode(pHandle->file.Handle, fMode); + } + else + { +#ifndef RT_OS_WINDOWS + /* Don't allow the guest to clear the read+execute own bits, otherwise the guest + * wouldn't be able to access this directory anymore. Only for guests, which set + * the UNIX mode. Also, clear bits which we don't pass through for security reasons. */ + if (fMode & RTFS_UNIX_MASK) + { + fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IXUSR; + fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT /*?*/); + } +#endif + rc = RTDirSetMode(pHandle->dir.Handle, fMode); + } + if (RT_FAILURE(rc)) + { + Log(("RT%sSetMode %#x (%#x) failed with %Rrc\n", type == SHFL_HF_TYPE_FILE ? "File" : "Dir", + fMode, pSFDEntry->Attr.fMode, rc)); + /* silent failure, because this tends to fail with e.g. windows guest & linux host */ + rc = VINF_SUCCESS; + } + } + + /* + * Return the current file info on success. + */ + if (RT_SUCCESS(rc)) + { + uint32_t bufsize = sizeof(*pSFDEntry); + rc = vbsfQueryFileInfo(pClient, root, Handle, SHFL_INFO_GET | SHFL_INFO_FILE, &bufsize, (uint8_t *)pSFDEntry); + if (RT_SUCCESS(rc)) + *pcbBuffer = sizeof(SHFLFSOBJINFO); + else + AssertFailed(); + } + } + return rc; +} + + +/** + * Handles SHFL_FN_SET_FILE_SIZE. + */ +int vbsfSetFileSize(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hHandle, uint64_t cbNewSize) +{ + /* + * Resolve handle and validate write access. + */ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hHandle); + ASSERT_GUEST_RETURN(pHandle, VERR_INVALID_HANDLE); + + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + /* + * Execute the request. + */ + rc = RTFileSetSize(pHandle->file.Handle, cbNewSize); + } + return rc; +} + + +static int vbsfSetEndOfFile(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + SHFLFSOBJINFO *pSFDEntry; + + if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(SHFLFSOBJINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + *pcbBuffer = 0; + pSFDEntry = (SHFLFSOBJINFO *)pBuffer; + + if (flags & SHFL_INFO_SIZE) + { + rc = RTFileSetSize(pHandle->file.Handle, pSFDEntry->cbObject); + if (rc != VINF_SUCCESS) + AssertFailed(); + } + else + AssertFailed(); + + if (rc == VINF_SUCCESS) + { + RTFSOBJINFO fileinfo; + + /* Query the new object info and return it */ + rc = RTFileQueryInfo(pHandle->file.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); + if (rc == VINF_SUCCESS) + { +#ifdef RT_OS_WINDOWS + fileinfo.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(pSFDEntry, &fileinfo); + *pcbBuffer = sizeof(SHFLFSOBJINFO); + } + else + AssertFailed(); + } + + return rc; +} + +int vbsfQueryVolumeInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF2(root, flags); + int rc = VINF_SUCCESS; + SHFLVOLINFO *pSFDEntry; + char *pszFullPath = NULL; + union + { + SHFLSTRING Dummy; + uint8_t abDummy[SHFLSTRING_HEADER_SIZE + sizeof(RTUTF16)]; + } Buf; + + if (pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(SHFLVOLINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /** @todo other options */ + Assert(flags == (SHFL_INFO_GET|SHFL_INFO_VOLUME)); + + *pcbBuffer = 0; + pSFDEntry = (PSHFLVOLINFO)pBuffer; + + ShflStringInitBuffer(&Buf.Dummy, sizeof(Buf)); + Buf.Dummy.String.ucs2[0] = '\0'; + rc = vbsfBuildFullPath(pClient, root, &Buf.Dummy, sizeof(Buf), &pszFullPath, NULL); + + if (RT_SUCCESS(rc)) + { + rc = RTFsQuerySizes(pszFullPath, &pSFDEntry->ullTotalAllocationBytes, &pSFDEntry->ullAvailableAllocationBytes, &pSFDEntry->ulBytesPerAllocationUnit, &pSFDEntry->ulBytesPerSector); + if (rc != VINF_SUCCESS) + goto exit; + + rc = RTFsQuerySerial(pszFullPath, &pSFDEntry->ulSerial); + if (rc != VINF_SUCCESS) + goto exit; + + RTFSPROPERTIES FsProperties; + rc = RTFsQueryProperties(pszFullPath, &FsProperties); + if (rc != VINF_SUCCESS) + goto exit; + vbfsCopyFsPropertiesFromIprt(&pSFDEntry->fsProperties, &FsProperties); + + *pcbBuffer = sizeof(SHFLVOLINFO); + } + else AssertFailed(); + +exit: + AssertMsg(rc == VINF_SUCCESS, ("failure: rc = %Rrc\n", rc)); + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + return rc; +} + +int vbsfQueryFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + if (pcbBuffer == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + if (flags & SHFL_INFO_FILE) + return vbsfQueryFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + + if (flags & SHFL_INFO_VOLUME) + return vbsfQueryVolumeInfo(pClient, root, flags, pcbBuffer, pBuffer); + + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_INFORMATION API. Located here as a form of API + * documentation. */ +void testFSInfo(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testFSInfoBadParameters(hTest); + /* Basic get and set file size test. */ + testFSInfoQuerySetFMode(hTest); + /* Basic get and set dir atime test. */ + testFSInfoQuerySetDirATime(hTest); + /* Basic get and set file atime test. */ + testFSInfoQuerySetFileATime(hTest); + /* Basic set end of file. */ + testFSInfoQuerySetEndOfFile(hTest); + /* Add tests as required... */ +} +#endif +int vbsfSetFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + uint32_t type = vbsfQueryHandleType(pClient, Handle) + & (SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE|SHFL_HF_TYPE_VOLUME); + + if (type == 0 || pcbBuffer == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + if (flags & SHFL_INFO_FILE) + return vbsfSetFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + + if (flags & SHFL_INFO_SIZE) + return vbsfSetEndOfFile(pClient, root, Handle, flags, pcbBuffer, pBuffer); + +// if (flags & SHFL_INFO_VOLUME) +// return vbsfVolumeInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_LOCK API. Located here as a form of API + * documentation. */ +void testLock(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testLockBadParameters(hTest); + /* Simple file locking and unlocking test. */ + testLockFileSimple(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfLock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags) +{ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + uint32_t fRTLock = 0; + + Assert((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL); + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + if ( ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + || (flags & SHFL_LOCK_ENTIRE) + ) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Lock type */ + switch(flags & SHFL_LOCK_MODE_MASK) + { + case SHFL_LOCK_SHARED: + fRTLock = RTFILE_LOCK_READ; + break; + + case SHFL_LOCK_EXCLUSIVE: + fRTLock = RTFILE_LOCK_READ | RTFILE_LOCK_WRITE; + break; + + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Lock wait type */ + if (flags & SHFL_LOCK_WAIT) + fRTLock |= RTFILE_LOCK_WAIT; + else + fRTLock |= RTFILE_LOCK_IMMEDIATELY; + +#ifdef RT_OS_WINDOWS + rc = RTFileLock(pHandle->file.Handle, fRTLock, offset, length); + if (rc != VINF_SUCCESS) + Log(("RTFileLock %RTfile %RX64 %RX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc)); +#else + Log(("vbsfLock: Pretend success handle=%x\n", Handle)); + rc = VINF_SUCCESS; + RT_NOREF2(offset, length); +#endif + return rc; +} + +int vbsfUnlock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags) +{ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + + Assert((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL); + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + if ( ((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL) + || (flags & SHFL_LOCK_ENTIRE) + ) + { + return VERR_INVALID_PARAMETER; + } + +#ifdef RT_OS_WINDOWS + rc = RTFileUnlock(pHandle->file.Handle, offset, length); + if (rc != VINF_SUCCESS) + Log(("RTFileUnlock %RTfile %RX64 %RTX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc)); +#else + Log(("vbsfUnlock: Pretend success handle=%x\n", Handle)); + rc = VINF_SUCCESS; + RT_NOREF2(offset, length); +#endif + + return rc; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_REMOVE API. Located here as a form of API + * documentation. */ +void testRemove(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testRemoveBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRemove(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, uint32_t cbPath, uint32_t flags, SHFLHANDLE hToClose) +{ + + /* Validate input */ + Assert(pPath); + AssertReturn(pPath->u16Size > 0, VERR_INVALID_PARAMETER); + + /* + * Close the handle if specified. + */ + int rc = VINF_SUCCESS; + if (hToClose != SHFL_HANDLE_NIL) + rc = vbsfClose(pClient, root, hToClose); + if (RT_SUCCESS(rc)) + { + /* + * Build a host full path for the given path and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Is the guest allowed to write to this share? + */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_SUCCESS(rc) && fWritable) + { + /* + * Do the removal/deletion according to the type flags. + */ + if (flags & SHFL_REMOVE_SYMLINK) + rc = RTSymlinkDelete(pszFullPath, 0); + else if (flags & SHFL_REMOVE_FILE) + rc = RTFileDelete(pszFullPath); + else + rc = RTDirRemove(pszFullPath); + +#if 0 //ndef RT_OS_WINDOWS + /* There are a few adjustments to be made here: */ + if ( rc == VERR_FILE_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszFullPath)) + rc = VERR_PATH_NOT_FOUND; + else if ( rc == VERR_PATH_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient)) + { + if (flags & (SHFL_REMOVE_FILE | SHFL_REMOVE_SYMLINK)) + { + size_t cchFullPath = strlen(pszFullPath); + if (cchFullPath > 0 && RTPATH_IS_SLASH(pszFullPath[cchFullPath - 1])) + rc = VERR_INVALID_NAME; + } + else if (vbsfErrorStyleIsWindowsNotADirectory(pszFullPath)) + rc = VERR_NOT_A_DIRECTORY; + } +#endif + } + else + rc = VERR_WRITE_PROTECT; + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + } + return rc; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_RENAME API. Located here as a form of API + * documentation. */ +void testRename(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testRenameBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRename(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pSrc, SHFLSTRING *pDest, uint32_t flags) +{ + int rc = VINF_SUCCESS; + + /* Validate input */ + if ( flags & ~(SHFL_RENAME_FILE|SHFL_RENAME_DIR|SHFL_RENAME_REPLACE_IF_EXISTS) + || pSrc == 0 + || pDest == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Build a host full path for the given path + * and convert ucs2 to utf8 if necessary. + */ + char *pszFullPathSrc = NULL; + char *pszFullPathDest = NULL; + + rc = vbsfBuildFullPath(pClient, root, pSrc, pSrc->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPathSrc, NULL); + if (rc != VINF_SUCCESS) + return rc; + + rc = vbsfBuildFullPath(pClient, root, pDest, pDest->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPathDest, NULL, false, true); + if (RT_SUCCESS (rc)) + { + Log(("Rename %s to %s\n", pszFullPathSrc, pszFullPathDest)); + + /* is the guest allowed to write to this share? */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc) || !fWritable) + rc = VERR_WRITE_PROTECT; + + if (RT_SUCCESS(rc)) + { + if ((flags & (SHFL_RENAME_FILE | SHFL_RENAME_DIR)) == (SHFL_RENAME_FILE | SHFL_RENAME_DIR)) + { + rc = RTPathRename(pszFullPathSrc, pszFullPathDest, + flags & SHFL_RENAME_REPLACE_IF_EXISTS ? RTPATHRENAME_FLAGS_REPLACE : 0); + } + else if (flags & SHFL_RENAME_FILE) + { + rc = RTFileMove(pszFullPathSrc, pszFullPathDest, + ((flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTFILEMOVE_FLAGS_REPLACE : 0)); + } + else + { + /* NT ignores the REPLACE flag and simply return and already exists error. */ + rc = RTDirRename(pszFullPathSrc, pszFullPathDest, + ((flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTPATHRENAME_FLAGS_REPLACE : 0)); + } +#ifndef RT_OS_WINDOWS + if ( rc == VERR_FILE_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound2(pszFullPathSrc, pszFullPathDest)) + rc = VERR_PATH_NOT_FOUND; +#endif + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPathDest); + } + /* free the path string */ + vbsfFreeFullPath(pszFullPathSrc); + return rc; +} + +/** + * Implements SHFL_FN_COPY_FILE (wrapping RTFileCopy). + */ +int vbsfCopyFile(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, PCSHFLSTRING pStrPathSrc, + SHFLROOT idRootDst, PCSHFLSTRING pStrPathDst, uint32_t fFlags) +{ + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + if (pClient->fu32Flags & SHFL_CF_UTF8) + LogFunc(("pClient %p, idRootSrc %#RX32, '%.*s', idRootSrc %#RX32, '%.*s', fFlags %#x\n", pClient, idRootSrc, + pStrPathSrc->u16Length, pStrPathSrc->String.ach, idRootDst, pStrPathDst->u16Length, pStrPathDst->String.ach, fFlags)); + else + LogFunc(("pClient %p, idRootSrc %#RX32, '%.*ls', idRootSrc %#RX32, '%.*ls', fFlags %#x\n", pClient, + idRootSrc, pStrPathSrc->u16Length / sizeof(RTUTF16), pStrPathSrc->String.ach, + idRootDst, pStrPathDst->u16Length / sizeof(RTUTF16), pStrPathDst->String.ach, fFlags)); + + /* + * Build host paths. + */ + char *pszPathSrc = NULL; + int rc = vbsfBuildFullPath(pClient, idRootSrc, pStrPathSrc, pStrPathSrc->u16Size + SHFLSTRING_HEADER_SIZE, &pszPathSrc, NULL); + if (RT_SUCCESS(rc)) + { + char *pszPathDst = NULL; + rc = vbsfBuildFullPath(pClient, idRootDst, pStrPathDst, pStrPathDst->u16Size + SHFLSTRING_HEADER_SIZE, &pszPathDst, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Do the job. + */ + rc = RTFileCopy(pszPathSrc, pszPathDst); + + vbsfFreeFullPath(pszPathDst); + } + vbsfFreeFullPath(pszPathSrc); + } + + RT_NOREF(fFlags); + LogFunc(("returns %Rrc\n", rc)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_SYMLINK API. Located here as a form of API + * documentation. */ +void testSymlink(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testSymlinkBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfSymlink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pNewPath, SHFLSTRING *pOldPath, SHFLFSOBJINFO *pInfo) +{ + int rc = VINF_SUCCESS; + + char *pszFullNewPath = NULL; + char *pszFullOldPath = NULL; + + /* XXX: no support for UCS2 at the moment. */ + if (!BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + return VERR_NOT_IMPLEMENTED; + + bool fSymlinksCreate; + rc = vbsfMappingsQuerySymlinksCreate(pClient, root, &fSymlinksCreate); + AssertRCReturn(rc, rc); + if (!fSymlinksCreate) + return VERR_WRITE_PROTECT; /* XXX or VERR_TOO_MANY_SYMLINKS? */ + + rc = vbsfBuildFullPath(pClient, root, pNewPath, pNewPath->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullNewPath, NULL); + AssertRCReturn(rc, rc); + + /* Verify that the link target can be a valid host path, i.e. does not contain invalid characters. */ + uint32_t fu32PathFlags = 0; + uint32_t fu32Options = 0; + rc = vbsfPathGuestToHost(pClient, root, pOldPath, pOldPath->u16Size + SHFLSTRING_HEADER_SIZE, + &pszFullOldPath, NULL, fu32Options, &fu32PathFlags); + if (RT_FAILURE(rc)) + { + vbsfFreeFullPath(pszFullNewPath); + return rc; + } + + /** @todo r=bird: We _must_ perform slash conversion on the target (what this + * code calls 'pOldPath' for some peculiar reason)! */ + + rc = RTSymlinkCreate(pszFullNewPath, (const char *)pOldPath->String.utf8, + RTSYMLINKTYPE_UNKNOWN, 0); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO info; + rc = RTPathQueryInfoEx(pszFullNewPath, &info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + vbfsCopyFsObjInfoFromIprt(pInfo, &info); + } + + vbsfFreeFullPath(pszFullOldPath); + vbsfFreeFullPath(pszFullNewPath); + + return rc; +} + +/* + * Clean up our mess by freeing all handles that are still valid. + * + */ +int vbsfDisconnect(SHFLCLIENTDATA *pClient) +{ + for (int i = 0; i < SHFLHANDLE_MAX; ++i) + { + SHFLFILEHANDLE *pHandle = NULL; + SHFLHANDLE Handle = (SHFLHANDLE)i; + + uint32_t type = vbsfQueryHandleType(pClient, Handle); + switch (type & (SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) + { + case SHFL_HF_TYPE_DIR: + { + pHandle = vbsfQueryDirHandle(pClient, Handle); + break; + } + case SHFL_HF_TYPE_FILE: + { + pHandle = vbsfQueryFileHandle(pClient, Handle); + break; + } + default: + break; + } + + if (pHandle) + { + LogFunc(("Opened handle 0x%08x\n", i)); + vbsfClose(pClient, pHandle->root, Handle); + } + } + + for (uint32_t i = 0; i < RT_ELEMENTS(pClient->acMappings); i++) + if (pClient->acMappings[i]) + { + uint16_t cMappings = pClient->acMappings[i]; + while (cMappings-- > 0) + vbsfUnmapFolder(pClient, i); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostServices/SharedFolders/vbsf.h b/src/VBox/HostServices/SharedFolders/vbsf.h new file mode 100644 index 00000000..04ef280a --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsf.h @@ -0,0 +1,68 @@ +/* $Id: vbsf.h $ */ +/** @file + * VBox Shared Folders header. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_vbsf_h +#define VBOX_INCLUDED_SRC_SharedFolders_vbsf_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include <VBox/shflsvc.h> + +int vbsfCreate(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, SHFLCREATEPARMS *pParms); + +int vbsfClose(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle); + +int vbsfRead(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfReadPages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t offFile, + uint32_t *pcbBuffer, PVBOXHGCMSVCPARMPAGES pPages); +int vbsfWrite(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t *poffFile, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfWritePages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbBuffer, PVBOXHGCMSVCPARMPAGES pPages); +int vbsfCopyFilePart(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, SHFLHANDLE hFileSrc, uint64_t offSrc, + SHFLROOT idRootDst, SHFLHANDLE hFileDst, uint64_t offDst, uint64_t *pcbToCopy, uint32_t fFlags); + +int vbsfLock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags); +int vbsfUnlock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags); +int vbsfRemove(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, uint32_t cbPath, uint32_t flags, SHFLHANDLE hToClose); +int vbsfRename(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pSrc, SHFLSTRING *pDest, uint32_t flags); +int vbsfCopyFile(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, PCSHFLSTRING pStrPathSrc, + SHFLROOT idRootDst, PCSHFLSTRING pStrPathDst, uint32_t fFlags); +int vbsfDirList(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, SHFLSTRING *pPath, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer, uint32_t *pIndex, uint32_t *pcFiles); +int vbsfFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfSetFileSize(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hHandle, uint64_t cbNewSize); +int vbsfQueryFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfSetFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfFlush(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle); +int vbsfDisconnect(SHFLCLIENTDATA *pClient); +int vbsfQueryFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfReadLink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, uint8_t *pBuffer, uint32_t cbBuffer); +int vbsfSymlink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pNewPath, SHFLSTRING *pOldPath, SHFLFSOBJINFO *pInfo); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_vbsf_h */ + diff --git a/src/VBox/HostServices/SharedFolders/vbsfpath.cpp b/src/VBox/HostServices/SharedFolders/vbsfpath.cpp new file mode 100644 index 00000000..afd13c4d --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpath.cpp @@ -0,0 +1,709 @@ +/* $Id: vbsfpath.cpp $ */ +/** @file + * Shared Folders Service - guest/host path convertion and verification. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "vbsfpath.h" +#include "mappings.h" +#include "vbsf.h" +#include "shflhandle.h" + +#include <iprt/alloc.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/fs.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/uni.h> +#include <iprt/stream.h> +#ifdef RT_OS_DARWIN +# include <Carbon/Carbon.h> +#endif + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_RT_LINK(pClient) ((pClient)->fu32Flags & SHFL_CF_SYMLINKS ? RTPATH_F_ON_LINK : RTPATH_F_FOLLOW_LINK) + + +/** + * @todo find a better solution for supporting the execute bit for non-windows + * guests on windows host. Search for "0111" to find all the relevant places. + */ + +/** + * Corrects the casing of the final component + * + * @returns + * @param pClient . + * @param pszFullPath . + * @param pszStartComponent . + */ +static int vbsfCorrectCasing(SHFLCLIENTDATA *pClient, char *pszFullPath, char *pszStartComponent) +{ + Log2(("vbsfCorrectCasing: %s %s\n", pszFullPath, pszStartComponent)); + + AssertReturn((uintptr_t)pszFullPath < (uintptr_t)pszStartComponent - 1U, VERR_INTERNAL_ERROR_2); + AssertReturn(pszStartComponent[-1] == RTPATH_DELIMITER, VERR_INTERNAL_ERROR_5); + + /* + * Allocate a buffer that can hold really long file name entries as well as + * the initial search pattern. + */ + size_t cchComponent = strlen(pszStartComponent); + size_t cchParentDir = pszStartComponent - pszFullPath; + size_t cchFullPath = cchParentDir + cchComponent; + Assert(strlen(pszFullPath) == cchFullPath); + + size_t cbDirEntry = 4096; + if (cchFullPath + 4 > cbDirEntry - RT_OFFSETOF(RTDIRENTRYEX, szName)) + cbDirEntry = RT_OFFSETOF(RTDIRENTRYEX, szName) + cchFullPath + 4; + + PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry); + if (pDirEntry == NULL) + return VERR_NO_MEMORY; + + /* + * Construct the search criteria in the szName member of pDirEntry. + */ + /** @todo This is quite inefficient, especially for directories with many + * files. If any of the typically case sensitive host systems start + * supporting opendir wildcard filters, it would make sense to build + * one here with '?' for case foldable charaters. */ + /** @todo Use RTDirOpen here and drop the whole uncessary path copying? */ + int rc = RTPathJoinEx(pDirEntry->szName, cbDirEntry - RT_OFFSETOF(RTDIRENTRYEX, szName), + pszFullPath, cchParentDir, + RT_STR_TUPLE("*"), RTPATH_STR_F_STYLE_HOST); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RTDIR hSearch = NULL; + rc = RTDirOpenFiltered(&hSearch, pDirEntry->szName, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + for (;;) + { + size_t cbDirEntrySize = cbDirEntry; + + rc = RTDirReadEx(hSearch, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + if (rc == VERR_NO_MORE_FILES) + break; + + if ( rc != VINF_SUCCESS + && rc != VWRN_NO_DIRENT_INFO) + { + if ( rc == VERR_NO_TRANSLATION + || rc == VERR_INVALID_UTF8_ENCODING) + continue; + AssertMsgFailed(("%Rrc\n", rc)); + break; + } + + Log2(("vbsfCorrectCasing: found %s\n", &pDirEntry->szName[0])); + if ( pDirEntry->cbName == cchComponent + && !RTStrICmp(pszStartComponent, &pDirEntry->szName[0])) + { + Log(("Found original name %s (%s)\n", &pDirEntry->szName[0], pszStartComponent)); + strcpy(pszStartComponent, &pDirEntry->szName[0]); + rc = VINF_SUCCESS; + break; + } + } + + RTDirClose(hSearch); + } + } + + if (RT_FAILURE(rc)) + Log(("vbsfCorrectCasing %s failed with %Rrc\n", pszStartComponent, rc)); + + RTMemFree(pDirEntry); + + return rc; +} + +/* Temporary stand-in for RTPathExistEx. */ +static int vbsfQueryExistsEx(const char *pszPath, uint32_t fFlags) +{ +#if 0 /** @todo Fix the symlink issue on windows! */ + return RTPathExistsEx(pszPath, fFlags); +#else + RTFSOBJINFO IgnInfo; + return RTPathQueryInfoEx(pszPath, &IgnInfo, RTFSOBJATTRADD_NOTHING, fFlags); +#endif +} + +/** + * Helper for vbsfBuildFullPath that performs case corrections on the path + * that's being build. + * + * @returns VINF_SUCCESS at the moment. + * @param pClient The client data. + * @param pszFullPath Pointer to the full path. This is the path + * which may need case corrections. The + * corrections will be applied in place. + * @param cchFullPath The length of the full path. + * @param fWildCard Whether the last component may contain + * wildcards and thus might require exclusion + * from the case correction. + * @param fPreserveLastComponent Always exclude the last component from case + * correction if set. + */ +static int vbsfCorrectPathCasing(SHFLCLIENTDATA *pClient, char *pszFullPath, size_t cchFullPath, + bool fWildCard, bool fPreserveLastComponent) +{ + /* + * Hide the last path component if it needs preserving. This is required + * in the following cases: + * - Contains the wildcard(s). + * - Is a 'rename' target. + */ + char *pszLastComponent = NULL; + if (fWildCard || fPreserveLastComponent) + { + char *pszSrc = pszFullPath + cchFullPath - 1; + Assert(strchr(pszFullPath, '\0') == pszSrc + 1); + while ((uintptr_t)pszSrc > (uintptr_t)pszFullPath) + { + if (*pszSrc == RTPATH_DELIMITER) + break; + pszSrc--; + } + if (*pszSrc == RTPATH_DELIMITER) + { + if ( fPreserveLastComponent + /* Or does it really have wildcards? */ + || strchr(pszSrc + 1, '*') != NULL + || strchr(pszSrc + 1, '?') != NULL + || strchr(pszSrc + 1, '>') != NULL + || strchr(pszSrc + 1, '<') != NULL + || strchr(pszSrc + 1, '"') != NULL ) + { + pszLastComponent = pszSrc; + *pszLastComponent = '\0'; + } + } + } + + /* + * If the path/file doesn't exist, we need to attempt case correcting it. + */ + /** @todo Don't check when creating files or directories; waste of time. */ + int rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + Log(("Handle case insensitive guest fs on top of host case sensitive fs for %s\n", pszFullPath)); + + /* + * Work from the end of the path to find a partial path that's valid. + */ + char *pszSrc = pszLastComponent ? pszLastComponent - 1 : pszFullPath + cchFullPath - 1; + Assert(strchr(pszFullPath, '\0') == pszSrc + 1); + + while ((uintptr_t)pszSrc > (uintptr_t)pszFullPath) + { + if (*pszSrc == RTPATH_DELIMITER) + { + *pszSrc = '\0'; + rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); + *pszSrc = RTPATH_DELIMITER; + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + *pszSrc = '\0'; + Log(("Found valid partial path %s\n", pszFullPath)); + *pszSrc = RTPATH_DELIMITER; +#endif + break; + } + } + + pszSrc--; + } + Assert(*pszSrc == RTPATH_DELIMITER && RT_SUCCESS(rc)); + if ( *pszSrc == RTPATH_DELIMITER + && RT_SUCCESS(rc)) + { + /* + * Turn around and work the other way case correcting the components. + */ + pszSrc++; + for (;;) + { + bool fEndOfString = true; + + /* Find the end of the component. */ + char *pszEnd = pszSrc; + while (*pszEnd) + { + if (*pszEnd == RTPATH_DELIMITER) + break; + pszEnd++; + } + + if (*pszEnd == RTPATH_DELIMITER) + { + fEndOfString = false; + *pszEnd = '\0'; +#if 0 /** @todo Please, double check this. The original code is in the #if 0, what I hold as correct is in the #else. */ + rc = RTPathQueryInfoEx(pszSrc, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); +#else + rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); +#endif + Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND); + } + else if (pszEnd == pszSrc) + rc = VINF_SUCCESS; /* trailing delimiter */ + else + rc = VERR_FILE_NOT_FOUND; + + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + /* Path component is invalid; try to correct the casing. */ + rc = vbsfCorrectCasing(pClient, pszFullPath, pszSrc); + if (RT_FAILURE(rc)) + { + /* Failed, so don't bother trying any further components. */ + if (!fEndOfString) + *pszEnd = RTPATH_DELIMITER; /* Restore the original full path. */ + break; + } + } + + /* Next (if any). */ + if (fEndOfString) + break; + + *pszEnd = RTPATH_DELIMITER; + pszSrc = pszEnd + 1; + } + if (RT_FAILURE(rc)) + Log(("Unable to find suitable component rc=%d\n", rc)); + } + else + rc = VERR_FILE_NOT_FOUND; + + } + + /* Restore the final component if it was dropped. */ + if (pszLastComponent) + *pszLastComponent = RTPATH_DELIMITER; + + /* might be a new file so don't fail here! */ + return VINF_SUCCESS; +} + + +#ifdef RT_OS_DARWIN +/* Misplaced hack! See todo! */ + +/** Normalize the string using kCFStringNormalizationFormD. + * + * @param pwszSrc The input UTF-16 string. + * @param cwcSrc Length of the input string in characters. + * @param ppwszDst Where to store the pointer to the resulting normalized string. + * @param pcwcDst Where to store length of the normalized string in characters (without the trailing nul). + */ +static int vbsfNormalizeStringDarwin(PCRTUTF16 pwszSrc, uint32_t cwcSrc, PRTUTF16 *ppwszDst, uint32_t *pcwcDst) +{ + /** @todo This belongs in rtPathToNative or in the windows shared folder file system driver... + * The question is simply whether the NFD normalization is actually applied on a (virtual) file + * system level in darwin, or just by the user mode application libs. */ + + PRTUTF16 pwszNFD; + uint32_t cwcNFD; + + CFMutableStringRef inStr = ::CFStringCreateMutable(NULL, 0); + + /* Is 8 times length enough for decomposed in worst case...? */ + size_t cbNFDAlloc = cwcSrc * 8 + 2; + pwszNFD = (PRTUTF16)RTMemAllocZ(cbNFDAlloc); + if (!pwszNFD) + { + return VERR_NO_MEMORY; + } + + ::CFStringAppendCharacters(inStr, (UniChar*)pwszSrc, cwcSrc); + ::CFStringNormalize(inStr, kCFStringNormalizationFormD); + cwcNFD = ::CFStringGetLength(inStr); + + CFRange rangeCharacters; + rangeCharacters.location = 0; + rangeCharacters.length = cwcNFD; + ::CFStringGetCharacters(inStr, rangeCharacters, pwszNFD); + + pwszNFD[cwcNFD] = 0x0000; /* NULL terminated */ + + CFRelease(inStr); + + *ppwszDst = pwszNFD; + *pcwcDst = cwcNFD; + return VINF_SUCCESS; +} +#endif + + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +/* See MSDN "Naming Files, Paths, and Namespaces". + * '<', '>' and '"' are allowed as possible wildcards (see ANSI_DOS_STAR, etc in ntifs.h) + */ +static const char sachCharBlackList[] = ":/\\|"; +#else +/* Something else. */ +static const char sachCharBlackList[] = "/"; +#endif + +/** Verify if the character can be used in a host file name. + * Wildcard characters ('?', '*') are allowed. + * + * @param c Character to verify. + */ +static bool vbsfPathIsValidNameChar(unsigned char c) +{ + /* Character 0 is not allowed too. */ + if (c == 0 || strchr(sachCharBlackList, c)) + { + return false; + } + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + /* Characters less than 32 are not allowed. */ + if (c < 32) + { + return false; + } +#endif + + return true; +} + +/** Verify if the character is a wildcard. + * + * @param c Character to verify. + */ +static bool vbsfPathIsWildcardChar(char c) +{ + if ( c == '*' + || c == '?' +#ifdef RT_OS_WINDOWS /* See ntifs.h */ + || c == '<' /* ANSI_DOS_STAR */ + || c == '>' /* ANSI_DOS_QM */ + || c == '"' /* ANSI_DOS_DOT */ +#endif + ) + { + return true; + } + + return false; +} + +int vbsfPathGuestToHost(SHFLCLIENTDATA *pClient, SHFLROOT hRoot, + PCSHFLSTRING pGuestString, uint32_t cbGuestString, + char **ppszHostPath, uint32_t *pcbHostPathRoot, + uint32_t fu32Options, + uint32_t *pfu32PathFlags) +{ +#ifdef VBOX_STRICT + /* + * Check that the pGuestPath has correct size and encoding. + */ + if (ShflStringIsValidIn(pGuestString, cbGuestString, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) == false) + { + LogFunc(("Invalid input string\n")); + return VERR_INTERNAL_ERROR; + } +#else + NOREF(cbGuestString); +#endif + + /* + * Resolve the root handle into a string. + */ + uint32_t cbRootLen = 0; + const char *pszRoot = NULL; + int rc = vbsfMappingsQueryHostRootEx(hRoot, &pszRoot, &cbRootLen); + if (RT_FAILURE(rc)) + { + LogFunc(("invalid root\n")); + return rc; + } + + AssertReturn(cbRootLen > 0, VERR_INTERNAL_ERROR_2); /* vbsfMappingsQueryHostRootEx ensures this. */ + + /* + * Get the UTF8 string with the relative path provided by the guest. + * If guest uses UTF-16 then convert it to UTF-8. + */ + uint32_t cbGuestPath = 0; /* Shut up MSC */ + const char *pchGuestPath = NULL; /* Ditto. */ + char *pchGuestPathAllocated = NULL; /* Converted from UTF-16. */ + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + /* UTF-8 */ + cbGuestPath = pGuestString->u16Length; + pchGuestPath = pGuestString->String.ach; + } + else + { + /* UTF-16 */ + +#ifdef RT_OS_DARWIN /* Misplaced hack! See todo! */ + uint32_t cwcSrc = 0; + PRTUTF16 pwszSrc = NULL; + rc = vbsfNormalizeStringDarwin(&pGuestString->String.ucs2[0], + pGuestString->u16Length / sizeof(RTUTF16), + &pwszSrc, &cwcSrc); +#else + uint32_t const cwcSrc = pGuestString->u16Length / sizeof(RTUTF16); + PCRTUTF16 const pwszSrc = &pGuestString->String.ucs2[0]; +#endif + + if (RT_SUCCESS(rc)) + { + size_t cbPathAsUtf8 = RTUtf16CalcUtf8Len(pwszSrc); + if (cbPathAsUtf8 >= cwcSrc) + { + /* Allocate buffer that will be able to contain the converted UTF-8 string. */ + pchGuestPathAllocated = (char *)RTMemAlloc(cbPathAsUtf8 + 1); + if (RT_LIKELY(pchGuestPathAllocated != NULL)) + { + if (RT_LIKELY(cbPathAsUtf8)) + { + size_t cchActual; + char *pszDst = pchGuestPathAllocated; + rc = RTUtf16ToUtf8Ex(pwszSrc, cwcSrc, &pszDst, cbPathAsUtf8 + 1, &cchActual); + AssertRC(rc); + AssertStmt(RT_FAILURE(rc) || cchActual == cbPathAsUtf8, rc = VERR_INTERNAL_ERROR_4); + Assert(strlen(pszDst) == cbPathAsUtf8); + } + + if (RT_SUCCESS(rc)) + { + /* Terminate the string. */ + pchGuestPathAllocated[cbPathAsUtf8] = '\0'; + + cbGuestPath = (uint32_t)cbPathAsUtf8; Assert(cbGuestPath == cbPathAsUtf8); + pchGuestPath = pchGuestPathAllocated; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + else + { + AssertFailed(); + rc = VERR_INTERNAL_ERROR_3; + } + +#ifdef RT_OS_DARWIN + RTMemFree(pwszSrc); +#endif + } + } + + char *pszFullPath = NULL; + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Root %s path %.*s\n", pszRoot, cbGuestPath, pchGuestPath)); + + /* + * Allocate enough memory to build the host full path from the root and the relative path. + */ + const uint32_t cbFullPathAlloc = cbRootLen + 1 + cbGuestPath + 1; /* root + possible_slash + relative + 0 */ + pszFullPath = (char *)RTMemAlloc(cbFullPathAlloc); + if (RT_LIKELY(pszFullPath != NULL)) + { + /* Buffer for the verified guest path. */ + char *pchVerifiedPath = (char *)RTMemAlloc(cbGuestPath + 1); + if (RT_LIKELY(pchVerifiedPath != NULL)) + { + /* Init the pointer for the guest relative path. */ + uint32_t cbSrc = cbGuestPath; + const char *pchSrc = pchGuestPath; + + /* Strip leading delimiters from the path the guest specified. */ + while ( cbSrc > 0 + && *pchSrc == pClient->PathDelimiter) + { + ++pchSrc; + --cbSrc; + } + + /* + * Iterate the guest path components, verify each of them replacing delimiters with the host slash. + */ + char *pchDst = pchVerifiedPath; + bool fLastComponentHasWildcard = false; + for (; cbSrc > 0; --cbSrc, ++pchSrc) + { + if (RT_LIKELY(*pchSrc != pClient->PathDelimiter)) + { + if (RT_LIKELY(vbsfPathIsValidNameChar(*pchSrc))) + { + if (pfu32PathFlags && vbsfPathIsWildcardChar(*pchSrc)) + { + fLastComponentHasWildcard = true; + } + + *pchDst++ = *pchSrc; + } + else + { + rc = VERR_INVALID_NAME; + break; + } + } + else + { + /* Replace with the host slash. */ + *pchDst++ = RTPATH_SLASH; + + if (pfu32PathFlags && fLastComponentHasWildcard && cbSrc > 1) + { + /* Processed component has a wildcard and there are more characters in the path. */ + *pfu32PathFlags |= VBSF_F_PATH_HAS_WILDCARD_IN_PREFIX; + } + fLastComponentHasWildcard = false; + } + } + + if (RT_SUCCESS(rc)) + { + *pchDst++ = 0; + + /* Construct the full host path removing '.' and '..'. */ + rc = vbsfPathAbs(pszRoot, pchVerifiedPath, pszFullPath, cbFullPathAlloc); + if (RT_SUCCESS(rc)) + { + if (pfu32PathFlags && fLastComponentHasWildcard) + { + *pfu32PathFlags |= VBSF_F_PATH_HAS_WILDCARD_IN_LAST; + } + + /* Check if the full path is still within the shared folder. */ + if (fu32Options & VBSF_O_PATH_CHECK_ROOT_ESCAPE) + { + if (!RTPathStartsWith(pszFullPath, pszRoot)) + { + rc = VERR_INVALID_NAME; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * If the host file system is case sensitive and the guest expects + * a case insensitive fs, then correct the path components casing. + */ + if ( vbsfIsHostMappingCaseSensitive(hRoot) + && !vbsfIsGuestMappingCaseSensitive(hRoot)) + { + const bool fWildCard = RT_BOOL(fu32Options & VBSF_O_PATH_WILDCARD); + const bool fPreserveLastComponent = RT_BOOL(fu32Options & VBSF_O_PATH_PRESERVE_LAST_COMPONENT); + rc = vbsfCorrectPathCasing(pClient, pszFullPath, strlen(pszFullPath), + fWildCard, fPreserveLastComponent); + } + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("%s\n", pszFullPath)); + + /* Return the full host path. */ + *ppszHostPath = pszFullPath; + + if (pcbHostPathRoot) + { + /* Return the length of the root path without the trailing slash. */ + *pcbHostPathRoot = RTPATH_IS_SLASH(pszFullPath[cbRootLen - 1]) ? + cbRootLen - 1 : /* pszRoot already had the trailing slash. */ + cbRootLen; /* pszRoot did not have the trailing slash. */ + } + } + } + } + else + { + LogFunc(("vbsfPathAbs %Rrc\n", rc)); + } + } + + RTMemFree(pchVerifiedPath); + } + else + { + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + + /* + * Cleanup. + */ + RTMemFree(pchGuestPathAllocated); + + if (RT_SUCCESS(rc)) + { + return rc; + } + + /* + * Cleanup on failure. + */ + RTMemFree(pszFullPath); + + LogFunc(("%Rrc\n", rc)); + return rc; +} + +void vbsfFreeHostPath(char *pszHostPath) +{ + RTMemFree(pszHostPath); +} + diff --git a/src/VBox/HostServices/SharedFolders/vbsfpath.h b/src/VBox/HostServices/SharedFolders/vbsfpath.h new file mode 100644 index 00000000..f2b4e1a6 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpath.h @@ -0,0 +1,80 @@ +/* $Id: vbsfpath.h $ */ +/** @file + * Shared Folders Service - Guest/host path convertion and verification. + */ + +/* + * Copyright (C) 2006-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 + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h +#define VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include <VBox/shflsvc.h> + +#define VBSF_O_PATH_WILDCARD UINT32_C(0x00000001) +#define VBSF_O_PATH_PRESERVE_LAST_COMPONENT UINT32_C(0x00000002) +#define VBSF_O_PATH_CHECK_ROOT_ESCAPE UINT32_C(0x00000004) + +#define VBSF_F_PATH_HAS_WILDCARD_IN_PREFIX UINT32_C(0x00000001) /* A component before the last one contains a wildcard. */ +#define VBSF_F_PATH_HAS_WILDCARD_IN_LAST UINT32_C(0x00000002) /* The last component contains a wildcard. */ + +/** + * + * @param pClient Shared folder client. + * @param hRoot Root handle. + * @param pGuestString Guest want to access the path. + * @param cbGuestString Size of pGuestString memory buffer. + * @param ppszHostPath Returned full host path: root prefix + guest path. + * @param pcbHostPathRoot Length of the root prefix in bytes. Optional, can be NULL. + * @param fu32Options Options. + * @param pfu32PathFlags VBSF_F_PATH_* flags. Optional, can be NULL. + */ +int vbsfPathGuestToHost(SHFLCLIENTDATA *pClient, SHFLROOT hRoot, + PCSHFLSTRING pGuestString, uint32_t cbGuestString, + char **ppszHostPath, uint32_t *pcbHostPathRoot, + uint32_t fu32Options, uint32_t *pfu32PathFlags); + +/** Free the host path returned by vbsfPathGuestToHost. + * + * @param pszHostPath Host path string. + */ +void vbsfFreeHostPath(char *pszHostPath); + +/** + * Build the absolute path by combining an absolute pszRoot and a relative pszPath. + * The resulting path does not contain '.' and '..' components. + * Similar to RTPathAbsEx but with support for Windows extended-length paths ("\\?\" prefix). + * Uses RTPathAbsEx for regular paths and on non-Windows hosts. + * + * @param pszRoot The absolute prefix. It is copied to the pszAbsPath without any processing. + * If NULL then the pszPath must be converted to the absolute path. + * @param pszPath The relative path to be appended to pszRoot. Already has correct delimiters (RTPATH_SLASH). + * @param pszAbsPath Where to store the resulting absolute path. + * @param cbAbsPath Size of pszAbsBuffer in bytes. + */ +int vbsfPathAbs(const char *pszRoot, const char *pszPath, char *pszAbsPath, size_t cbAbsPath); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h */ diff --git a/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp b/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp new file mode 100644 index 00000000..ce6f9391 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp @@ -0,0 +1,200 @@ +/* $Id: vbsfpathabs.cpp $ */ +/** @file + * Shared Folders Service - guest/host path convertion and verification. + */ + +/* + * Copyright (C) 2017-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/string.h> + + +#if defined(RT_OS_WINDOWS) +static void vbsfPathResolveRelative(char *pszPathBegin) +{ + char *pszCur = pszPathBegin; + char * const pszTop = pszCur; + + /* + * Get rid of double dot path components by evaluating them. + */ + for (;;) + { + char const chFirst = pszCur[0]; + if ( chFirst == '.' + && pszCur[1] == '.' + && (!pszCur[2] || pszCur[2] == RTPATH_SLASH)) + { + /* rewind to the previous component if any */ + char *pszPrev = pszCur; + if ((uintptr_t)pszPrev > (uintptr_t)pszTop) + { + pszPrev--; + while ( (uintptr_t)pszPrev > (uintptr_t)pszTop + && pszPrev[-1] != RTPATH_SLASH) + pszPrev--; + } + if (!pszCur[2]) + { + if (pszPrev != pszTop) + pszPrev[-1] = '\0'; + else + *pszPrev = '\0'; + break; + } + Assert(pszPrev[-1] == RTPATH_SLASH); + memmove(pszPrev, pszCur + 3, strlen(pszCur + 3) + 1); + pszCur = pszPrev - 1; + } + else if ( chFirst == '.' + && (!pszCur[1] || pszCur[1] == RTPATH_SLASH)) + { + /* remove unnecessary '.' */ + if (!pszCur[1]) + { + if (pszCur != pszTop) + pszCur[-1] = '\0'; + else + *pszCur = '\0'; + break; + } + memmove(pszCur, pszCur + 2, strlen(pszCur + 2) + 1); + continue; + } + else + { + /* advance to end of component. */ + while (*pszCur && *pszCur != RTPATH_SLASH) + pszCur++; + } + + if (!*pszCur) + break; + + /* skip the slash */ + ++pszCur; + } +} +#endif /* RT_OS_WINDOWS */ + +int vbsfPathAbs(const char *pszRoot, const char *pszPath, char *pszAbsPath, size_t cbAbsPath) +{ +#if defined(RT_OS_WINDOWS) + /** @todo This code is not needed in 6.0 and later as IPRT translates paths + * to //./ (inverted slashes for doxygen) format if they're too long. */ + const char *pszPathStart = pszRoot? pszRoot: pszPath; + + /* Windows extended-length paths. */ + if ( RTPATH_IS_SLASH(pszPathStart[0]) + && RTPATH_IS_SLASH(pszPathStart[1]) + && pszPathStart[2] == '?' + && RTPATH_IS_SLASH(pszPathStart[3]) + ) + { + /* Maximum total path length of 32,767 characters. */ + if (cbAbsPath > _32K) + cbAbsPath = _32K; + + /* Copy the root to pszAbsPath buffer. */ + size_t cchRoot = pszRoot? strlen(pszRoot): 0; + if (cchRoot >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + if (pszRoot) + { + /* Caller must ensure that the path is relative, without the leading path separator. */ + if (RTPATH_IS_SLASH(pszPath[0])) + return VERR_INVALID_PARAMETER; + + if (cchRoot) + memcpy(pszAbsPath, pszRoot, cchRoot); + + if (cchRoot == 0 || !RTPATH_IS_SLASH(pszAbsPath[cchRoot - 1])) + { + /* Append path separator after the root. */ + ++cchRoot; + if (cchRoot >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + pszAbsPath[cchRoot - 1] = RTPATH_SLASH; + } + } + + /* Append the path to the pszAbsPath buffer. */ + const size_t cchPath = strlen(pszPath); + if (cchRoot + cchPath >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + memcpy(&pszAbsPath[cchRoot], pszPath, cchPath + 1); /* Including trailing 0. */ + + /* Find out where the actual path begins, i.e. skip the root spec. */ + char *pszPathBegin = &pszAbsPath[4]; /* Skip the extended-length path prefix "\\?\" */ + if ( pszPathBegin[0] + && RTPATH_IS_VOLSEP(pszPathBegin[1]) + && pszPathBegin[2] == RTPATH_SLASH) + { + /* "\\?\C:\" */ + pszPathBegin += 3; + } + else if ( pszPathBegin[0] == 'U' + && pszPathBegin[1] == 'N' + && pszPathBegin[2] == 'C' + && pszPathBegin[3] == RTPATH_SLASH) + { + /* "\\?\UNC\server\share" */ + pszPathBegin += 4; + + /* Skip "server\share" too. */ + while (*pszPathBegin != RTPATH_SLASH && *pszPathBegin) + ++pszPathBegin; + if (*pszPathBegin == RTPATH_SLASH) + { + ++pszPathBegin; + while (*pszPathBegin != RTPATH_SLASH && *pszPathBegin) + ++pszPathBegin; + if (*pszPathBegin == RTPATH_SLASH) + ++pszPathBegin; + } + } + else + return VERR_INVALID_NAME; + + /* Process pszAbsPath in place. */ + vbsfPathResolveRelative(pszPathBegin); + + return VINF_SUCCESS; + } +#endif /* RT_OS_WINDOWS */ + + /* Fallback for the common paths. */ + + if (*pszPath != '\0') + return RTPathAbsEx(pszRoot, pszPath, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath); + return RTPathAbsEx(NULL, pszRoot, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath); +} diff --git a/src/VBox/HostServices/auth/Makefile.kmk b/src/VBox/HostServices/auth/Makefile.kmk new file mode 100644 index 00000000..93fc8eea --- /dev/null +++ b/src/VBox/HostServices/auth/Makefile.kmk @@ -0,0 +1,76 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBox RDP authentication plugins. +# + +# +# Copyright (C) 2006-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# The plugin. +ifndef VBOX_ONLY_SDK + if ("$(KBUILD_TARGET)" != "linux" && "$(KBUILD_TARGET)" != "solaris") || defined(VBOX_WITH_PAM) + DLLS += VBoxAuth + endif +endif +VBoxAuth_TEMPLATE = VBoxR3Dll +VBoxAuth_SOURCES.linux = pam/VBoxAuthPAM.c +VBoxAuth_SOURCES.solaris = pam/VBoxAuthPAM.c +VBoxAuth_SOURCES.freebsd = pam/VBoxAuthPAM.c +VBoxAuth_SOURCES.win = winlogon/winlogon.cpp winlogon/VBoxAuth.rc +VBoxAuth_SOURCES.darwin = directoryservice/directoryservice.cpp +VBoxAuth_CXXFLAGS.darwin = -Wno-deprecated-declarations +VBoxAuth_LIBS.linux = $(LIB_RUNTIME) dl +VBoxAuth_LIBS.solaris = $(LIB_RUNTIME) dl +VBoxAuth_LIBS.freebsd = $(LIB_RUNTIME) +VBoxAuth_LIBS.darwin = $(LIB_RUNTIME) +VBoxAuth_LDFLAGS.darwin = -framework DirectoryService + +# The simple plugin. +ifndef VBOX_ONLY_SDK + if defined(VBOX_WITH_MAIN) + DLLS += VBoxAuthSimple + endif +endif +VBoxAuthSimple_TEMPLATE = VBoxMainClientDll +VBoxAuthSimple_SOURCES = simple/VBoxAuthSimple.cpp +VBoxAuthSimple_SOURCES.win = simple/VBoxAuthSimple.rc + +# Install the SDK samples. +INSTALLS += VBoxAuth-samples +VBoxAuth-samples_INST = $(INST_SDK)bindings/auth/ +VBoxAuth-samples_MODE = a+r,u+w +VBoxAuth-samples_SOURCES = simple/VBoxAuthSimple.cpp +VBoxAuth-samples_SOURCES.linux = pam/VBoxAuthPAM.c +VBoxAuth-samples_SOURCES.win = winlogon/winlogon.cpp + +# Install the SDK header. +INSTALLS += VBoxAuth-sdkhdr +VBoxAuth-sdkhdr_INST = $(INST_SDK)bindings/auth/include/ +VBoxAuth-sdkhdr_MODE = a+r,u+w +VBoxAuth-sdkhdr_SOURCES = $(PATH_ROOT)/include/VBox/VBoxAuth.h=>VBoxAuth.h + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/auth/directoryservice/Makefile.kup b/src/VBox/HostServices/auth/directoryservice/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostServices/auth/directoryservice/Makefile.kup diff --git a/src/VBox/HostServices/auth/directoryservice/directoryservice.cpp b/src/VBox/HostServices/auth/directoryservice/directoryservice.cpp new file mode 100644 index 00000000..ea245961 --- /dev/null +++ b/src/VBox/HostServices/auth/directoryservice/directoryservice.cpp @@ -0,0 +1,338 @@ +/** @file + * + * VirtualBox External Authentication Library: + * Mac OS X Authentication. This is based on + * http://developer.apple.com/mac/library/samplecode/CryptNoMore/ + */ + +/* + * Copyright (C) 2009-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 + */ + +#include <iprt/cdefs.h> +#include <iprt/assert.h> + +#include <VBox/VBoxAuth.h> + +#include <DirectoryService/DirectoryService.h> + +/* Globals */ +static const size_t s_cBufferSize = 32 * 1024; + +tDirStatus defaultSearchNodePath(tDirReference pDirRef, tDataListPtr *pdsNodePath) +{ + tDirStatus dsErr = eDSNoErr; + /* Create a buffer for the resulting nodes */ + tDataBufferPtr pTmpBuf = NULL; + pTmpBuf = dsDataBufferAllocate(pDirRef, s_cBufferSize); + if (pTmpBuf) + { + /* Try to find the default search node for local names */ + UInt32 cNodes; + tContextData hCtx = 0; + dsErr = dsFindDirNodes(pDirRef, pTmpBuf, NULL, eDSLocalNodeNames, &cNodes, &hCtx); + /* Any nodes found? */ + if ( dsErr == eDSNoErr + && cNodes >= 1) + /* The first path of the node list is what we looking for. */ + dsErr = dsGetDirNodeName(pDirRef, pTmpBuf, 1, pdsNodePath); + else + dsErr = eDSNodeNotFound; + + if (hCtx) /* (DSoNodeConfig.m from DSTools-162 does exactly the same free if not-zero-regardless-of-return-code.) */ + dsReleaseContinueData(pDirRef, hCtx); + dsDataBufferDeAllocate(pDirRef, pTmpBuf); + } + else + dsErr = eDSAllocationFailed; + + return dsErr; +} + +tDirStatus userAuthInfo(tDirReference pDirRef, tDirNodeReference pNodeRef, const char *pszUsername, tDataListPtr *ppAuthNodeListOut) +{ + tDirStatus dsErr = eDSNoErr; + tDirStatus dsCleanErr = eDSNoErr; + /* Create a buffer for the resulting authentication info */ + tDataBufferPtr pTmpBuf = dsDataBufferAllocate(pDirRef, s_cBufferSize); + if (pTmpBuf) + { + /* Create the necessary lists for kDSNAttrMetaNodeLocation and kDSNAttrRecordName. */ + tDataListPtr pRecordType = dsBuildListFromStrings(pDirRef, kDSStdRecordTypeUsers, NULL); + tDataListPtr pRecordName = dsBuildListFromStrings(pDirRef, pszUsername, NULL); + tDataListPtr pRequestedAttributes = dsBuildListFromStrings(pDirRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, NULL); + if (!( pRecordType == NULL + || pRecordName == NULL + || pRequestedAttributes == NULL)) + { + /* Now search for the first matching record */ + UInt32 cRecords = 1; + tContextData hCtx = 0; + dsErr = dsGetRecordList(pNodeRef, + pTmpBuf, + pRecordName, + eDSExact, + pRecordType, + pRequestedAttributes, + false, + &cRecords, + &hCtx); + if ( dsErr == eDSNoErr + && cRecords >= 1) + { + /* Process the first found record. Look at any attribute one by one. */ + tAttributeListRef hRecAttrListRef = 0; + tRecordEntryPtr pRecEntry = NULL; + tDataListPtr pAuthNodeList = NULL; + dsErr = dsGetRecordEntry(pNodeRef, pTmpBuf, 1, &hRecAttrListRef, &pRecEntry); + if (dsErr == eDSNoErr) + { + for (size_t i = 1; i <= pRecEntry->fRecordAttributeCount; ++i) + { + tAttributeValueListRef hAttrValueListRef = 0; + tAttributeEntryPtr pAttrEntry = NULL; + /* Get the information for this attribute. */ + dsErr = dsGetAttributeEntry(pNodeRef, pTmpBuf, hRecAttrListRef, i, + &hAttrValueListRef, &pAttrEntry); + if (dsErr == eDSNoErr) + { + tAttributeValueEntryPtr pValueEntry = NULL; + /* Has any value? */ + if (pAttrEntry->fAttributeValueCount > 0) + { + dsErr = dsGetAttributeValue(pNodeRef, pTmpBuf, 1, hAttrValueListRef, &pValueEntry); + if (dsErr == eDSNoErr) + { + /* Check for kDSNAttrMetaNodeLocation */ + if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) + { + /* Convert the meta location attribute to a path node list */ + pAuthNodeList = dsBuildFromPath(pDirRef, + pValueEntry->fAttributeValueData.fBufferData, + "/"); + if (pAuthNodeList == NULL) + dsErr = eDSAllocationFailed; + } + } + } + + if (pValueEntry != NULL) + dsDeallocAttributeValueEntry(pDirRef, pValueEntry); + if (hAttrValueListRef) + dsCloseAttributeValueList(hAttrValueListRef); + if (pAttrEntry != NULL) + dsDeallocAttributeEntry(pDirRef, pAttrEntry); + + if (dsErr != eDSNoErr) + break; + } + } + } + /* Copy the results */ + if (dsErr == eDSNoErr) + { + if (pAuthNodeList != NULL) + { + /* Copy out results. */ + *ppAuthNodeListOut = pAuthNodeList; + pAuthNodeList = NULL; + } + else + dsErr = eDSAttributeNotFound; + } + + if (pAuthNodeList != NULL) + { + dsCleanErr = dsDataListDeallocate(pDirRef, pAuthNodeList); + if (dsCleanErr == eDSNoErr) + free(pAuthNodeList); + } + if (hRecAttrListRef) + dsCloseAttributeList(hRecAttrListRef); + if (pRecEntry != NULL) + dsDeallocRecordEntry(pDirRef, pRecEntry); + } + else + dsErr = eDSRecordNotFound; + if (hCtx) + dsReleaseContinueData(pDirRef, hCtx); + } + else + dsErr = eDSAllocationFailed; + if (pRequestedAttributes != NULL) + { + dsCleanErr = dsDataListDeallocate(pDirRef, pRequestedAttributes); + if (dsCleanErr == eDSNoErr) + free(pRequestedAttributes); + } + if (pRecordName != NULL) + { + dsCleanErr = dsDataListDeallocate(pDirRef, pRecordName); + if (dsCleanErr == eDSNoErr) + free(pRecordName); + } + if (pRecordType != NULL) + { + dsCleanErr = dsDataListDeallocate(pDirRef, pRecordType); + if (dsCleanErr == eDSNoErr) + free(pRecordType); + } + dsDataBufferDeAllocate(pDirRef, pTmpBuf); + } + else + dsErr = eDSAllocationFailed; + + return dsErr; +} + +tDirStatus authWithNode(tDirReference pDirRef, tDataListPtr pAuthNodeList, const char *pszUsername, const char *pszPassword) +{ + tDirStatus dsErr = eDSNoErr; + /* Open the authentication node. */ + tDirNodeReference hAuthNodeRef = 0; + dsErr = dsOpenDirNode(pDirRef, pAuthNodeList, &hAuthNodeRef); + if (dsErr == eDSNoErr) + { + /* How like we to authenticate! */ + tDataNodePtr pAuthMethod = dsDataNodeAllocateString(pDirRef, kDSStdAuthNodeNativeClearTextOK); + if (pAuthMethod) + { + /* Create the memory holding the authentication data. The data + * structure consists of 4 byte length of the username + zero byte, + * the username itself, a 4 byte length of the password & the + * password itself + zero byte. */ + tDataBufferPtr pAuthOutBuf = dsDataBufferAllocate(pDirRef, s_cBufferSize); + if (pAuthOutBuf) + { + size_t cUserName = strlen(pszUsername) + 1; + size_t cPassword = strlen(pszPassword) + 1; + unsigned long cLen = 0; + tDataBufferPtr pAuthInBuf = dsDataBufferAllocate(pDirRef, sizeof(cLen) + cUserName + sizeof(cLen) + cPassword); + if (pAuthInBuf) + { + /* Move the data into the buffer. */ + pAuthInBuf->fBufferLength = 0; + /* Length of the username */ + cLen = cUserName; + memcpy(&pAuthInBuf->fBufferData[pAuthInBuf->fBufferLength], &cLen, sizeof(cLen)); + pAuthInBuf->fBufferLength += sizeof(cLen); + /* The username itself */ + memcpy(&pAuthInBuf->fBufferData[pAuthInBuf->fBufferLength], pszUsername, cUserName); + pAuthInBuf->fBufferLength += cUserName; + /* Length of the password */ + cLen = cPassword; + memcpy(&pAuthInBuf->fBufferData[pAuthInBuf->fBufferLength], &cLen, sizeof(cLen)); + pAuthInBuf->fBufferLength += sizeof(cLen); + /* The password itself */ + memcpy(&pAuthInBuf->fBufferData[pAuthInBuf->fBufferLength], pszPassword, cPassword); + pAuthInBuf->fBufferLength += cPassword; + /* Now authenticate */ + dsErr = dsDoDirNodeAuth(hAuthNodeRef, pAuthMethod, true, pAuthInBuf, pAuthOutBuf, NULL); + /* Clean up. */ + dsDataBufferDeAllocate(pDirRef, pAuthInBuf); + } + else + dsErr = eDSAllocationFailed; + dsDataBufferDeAllocate(pDirRef, pAuthOutBuf); + } + else + dsErr = eDSAllocationFailed; + dsDataNodeDeAllocate(pDirRef, pAuthMethod); + } + else + dsErr = eDSAllocationFailed; + dsCloseDirNode(hAuthNodeRef); + } + + return dsErr; +} + +RT_C_DECLS_BEGIN +DECLEXPORT(FNAUTHENTRY3) AuthEntry; +RT_C_DECLS_END + +DECLEXPORT(AuthResult) AUTHCALL AuthEntry(const char *pszCaller, + PAUTHUUID pUuid, + AuthGuestJudgement guestJudgement, + const char *pszUser, + const char *pszPassword, + const char *pszDomain, + int fLogon, + unsigned clientId) +{ + RT_NOREF(pszCaller, pUuid, guestJudgement, pszDomain, clientId); + + /* Validate input */ + AssertPtrReturn(pszUser, AuthResultAccessDenied); + AssertPtrReturn(pszPassword, AuthResultAccessDenied); + + /* Result to a default value */ + AuthResult result = AuthResultAccessDenied; + + /* Only process logon requests. */ + if (!fLogon) + return result; /* Return value is ignored by the caller. */ + + tDirStatus dsErr = eDSNoErr; + tDirStatus dsCleanErr = eDSNoErr; + tDirReference hDirRef = 0; + /* Connect to the Directory Service. */ + dsErr = dsOpenDirService(&hDirRef); + if (dsErr == eDSNoErr) + { + /* Fetch the default search node */ + tDataListPtr pSearchNodeList = NULL; + dsErr = defaultSearchNodePath(hDirRef, &pSearchNodeList); + if (dsErr == eDSNoErr) + { + /* Open the default search node */ + tDirNodeReference hSearchNodeRef = 0; + dsErr = dsOpenDirNode(hDirRef, pSearchNodeList, &hSearchNodeRef); + if (dsErr == eDSNoErr) + { + /* Search for the user info, fetch the authentication node & + * the authentication user name. This allows the client to + * specify a long user name even if the name which is used to + * authenticate has the short form. */ + tDataListPtr pAuthNodeList = NULL; + dsErr = userAuthInfo(hDirRef, hSearchNodeRef, pszUser, &pAuthNodeList); + if (dsErr == eDSNoErr) + { + /* Open the authentication node and do the authentication. */ + dsErr = authWithNode(hDirRef, pAuthNodeList, pszUser, pszPassword); + if (dsErr == eDSNoErr) + result = AuthResultAccessGranted; + dsCleanErr = dsDataListDeallocate(hDirRef, pAuthNodeList); + if (dsCleanErr == eDSNoErr) + free(pAuthNodeList); + } + dsCloseDirNode(hSearchNodeRef); + } + dsCleanErr = dsDataListDeallocate(hDirRef, pSearchNodeList); + if (dsCleanErr == eDSNoErr) + free(pSearchNodeList); + } + dsCloseDirService(hDirRef); + } + + return result; +} + diff --git a/src/VBox/HostServices/auth/pam/Makefile.kup b/src/VBox/HostServices/auth/pam/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostServices/auth/pam/Makefile.kup diff --git a/src/VBox/HostServices/auth/pam/VBoxAuthPAM.c b/src/VBox/HostServices/auth/pam/VBoxAuthPAM.c new file mode 100644 index 00000000..5b7f14a3 --- /dev/null +++ b/src/VBox/HostServices/auth/pam/VBoxAuthPAM.c @@ -0,0 +1,417 @@ +/** @file + * + * VirtualBox External Authentication Library: + * Linux PAM Authentication. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/* The PAM service name. + * + * The service name is the name of a file in the /etc/pam.d which contains + * authentication rules. It is possible to use an existing service + * name, like "login" for example. But if different set of rules + * is required, one can create a new file /etc/pam.d/vrdpauth + * specially for VRDP authentication. Note that the name of the + * service must be lowercase. See PAM documentation for details. + * + * The Auth module takes the PAM service name from the + * environment variable VBOX_AUTH_PAM_SERVICE. If the variable + * is not specified, then the 'login' PAM service is used. + */ +#define VBOX_AUTH_PAM_SERVICE_NAME_ENV_OLD "VRDP_AUTH_PAM_SERVICE" +#define VBOX_AUTH_PAM_SERVICE_NAME_ENV "VBOX_AUTH_PAM_SERVICE" +#define VBOX_AUTH_PAM_DEFAULT_SERVICE_NAME "login" + + +/* The debug log file name. + * + * If defined, debug messages will be written to the file specified in the + * VBOX_AUTH_DEBUG_FILENAME (or deprecated VRDP_AUTH_DEBUG_FILENAME) environment + * variable: + * + * export VBOX_AUTH_DEBUG_FILENAME=pam.log + * + * The above will cause writing to the pam.log. + */ +#define VBOX_AUTH_DEBUG_FILENAME_ENV_OLD "VRDP_AUTH_DEBUG_FILENAME" +#define VBOX_AUTH_DEBUG_FILENAME_ENV "VBOX_AUTH_DEBUG_FILENAME" + + +/* Dynamic loading of the PAM library. + * + * If defined, the libpam.so is loaded dynamically. + * Enabled by default since it is often required, + * and does not harm. + */ +#define VBOX_AUTH_USE_PAM_DLLOAD + + +#ifdef VBOX_AUTH_USE_PAM_DLLOAD +/* The name of the PAM library */ +# ifdef RT_OS_SOLARIS +# define PAM_LIB_NAME "libpam.so.1" +# elif defined(RT_OS_FREEBSD) +# define PAM_LIB_NAME "libpam.so" +# else +# define PAM_LIB_NAME "libpam.so.0" +# endif +#endif /* VBOX_AUTH_USE_PAM_DLLOAD */ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#ifndef RT_OS_FREEBSD +# include <malloc.h> +#endif + +#include <security/pam_appl.h> + +#include <VBox/VBoxAuth.h> + +#ifdef VBOX_AUTH_USE_PAM_DLLOAD +#include <dlfcn.h> + +static int (*fn_pam_start)(const char *service_name, + const char *user, + const struct pam_conv *pam_conversation, + pam_handle_t **pamh); +static int (*fn_pam_authenticate)(pam_handle_t *pamh, int flags); +static int (*fn_pam_acct_mgmt)(pam_handle_t *pamh, int flags); +static int (*fn_pam_end)(pam_handle_t *pamh, int pam_status); +static const char * (*fn_pam_strerror)(pam_handle_t *pamh, int errnum); +#else +#define fn_pam_start pam_start +#define fn_pam_authenticate pam_authenticate +#define fn_pam_acct_mgmt pam_acct_mgmt +#define fn_pam_end pam_end +#define fn_pam_strerror pam_strerror +#endif /* VBOX_AUTH_USE_PAM_DLLOAD */ + +static void debug_printf(const char *fmt, ...) +{ +#if defined(VBOX_AUTH_DEBUG_FILENAME_ENV) || defined(VBOX_AUTH_DEBUG_FILENAME_ENV_OLD) + va_list va; + + char buffer[1024]; + + const char *filename = NULL; + + va_start(va, fmt); + +#if defined(VBOX_AUTH_DEBUG_FILENAME_ENV) + filename = getenv (VBOX_AUTH_DEBUG_FILENAME_ENV); +#endif /* VBOX_AUTH_DEBUG_FILENAME_ENV */ + +#if defined(VBOX_AUTH_DEBUG_FILENAME_ENV_OLD) + if (filename == NULL) + { + filename = getenv (VBOX_AUTH_DEBUG_FILENAME_ENV_OLD); + } +#endif /* VBOX_AUTH_DEBUG_FILENAME_ENV_OLD */ + + if (filename) + { + FILE *f; + + vsnprintf (buffer, sizeof (buffer), fmt, va); + + f = fopen (filename, "ab"); + if (f != NULL) + { + fprintf (f, "%s", buffer); + fclose (f); + } + } + + va_end (va); +#endif /* VBOX_AUTH_DEBUG_FILENAME_ENV || VBOX_AUTH_DEBUG_FILENAME_ENV_OLD */ +} + +#ifdef VBOX_AUTH_USE_PAM_DLLOAD + +static void *gpvLibPam = NULL; + +typedef struct _SymMap +{ + void **ppfn; + const char *pszName; +} SymMap; + +static SymMap symmap[] = +{ + { (void **)&fn_pam_start, "pam_start" }, + { (void **)&fn_pam_authenticate, "pam_authenticate" }, + { (void **)&fn_pam_acct_mgmt, "pam_acct_mgmt" }, + { (void **)&fn_pam_end, "pam_end" }, + { (void **)&fn_pam_strerror, "pam_strerror" }, + { NULL, NULL } +}; + +static int auth_pam_init(void) +{ + SymMap *iter; + + gpvLibPam = dlopen(PAM_LIB_NAME, RTLD_LAZY | RTLD_GLOBAL); + + if (!gpvLibPam) + { + debug_printf("auth_pam_init: dlopen %s failed\n", PAM_LIB_NAME); + return PAM_SYSTEM_ERR; + } + + iter = &symmap[0]; + + while (iter->pszName != NULL) + { + void *pv = dlsym (gpvLibPam, iter->pszName); + + if (pv == NULL) + { + debug_printf("auth_pam_init: dlsym %s failed\n", iter->pszName); + + dlclose(gpvLibPam); + gpvLibPam = NULL; + + return PAM_SYSTEM_ERR; + } + + *iter->ppfn = pv; + + iter++; + } + + return PAM_SUCCESS; +} + +static void auth_pam_close(void) +{ + if (gpvLibPam) + { + dlclose(gpvLibPam); + gpvLibPam = NULL; + } + + return; +} +#else +static int auth_pam_init(void) +{ + return PAM_SUCCESS; +} + +static void auth_pam_close(void) +{ + return; +} +#endif /* VBOX_AUTH_USE_PAM_DLLOAD */ + +static const char *auth_get_pam_service (void) +{ + const char *service = getenv (VBOX_AUTH_PAM_SERVICE_NAME_ENV); + + if (service == NULL) + { + service = getenv (VBOX_AUTH_PAM_SERVICE_NAME_ENV_OLD); + + if (service == NULL) + { + service = VBOX_AUTH_PAM_DEFAULT_SERVICE_NAME; + } + } + + debug_printf ("Using PAM service: %s\n", service); + + return service; +} + +typedef struct _PamContext +{ + char *pszUser; + char *pszPassword; +} PamContext; + +#if defined(RT_OS_SOLARIS) +static int conv (int num_msg, struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +#else +static int conv (int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +#endif +{ + int i; + struct pam_response *r; + + PamContext *ctx = (PamContext *)appdata_ptr; + + if (ctx == NULL) + { + debug_printf("conv: ctx is NULL\n"); + return PAM_CONV_ERR; + } + + debug_printf("conv: num %d u[%s] p[%d]\n", num_msg, ctx->pszUser, ctx->pszPassword? strlen (ctx->pszPassword): 0); + + r = (struct pam_response *) calloc (num_msg, sizeof (struct pam_response)); + + if (r == NULL) + { + return PAM_CONV_ERR; + } + + for (i = 0; i < num_msg; i++) + { + r[i].resp_retcode = 0; + + if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) + { + r[i].resp = strdup (ctx->pszPassword); + debug_printf("conv: %d returning password [%d]\n", i, r[i].resp? strlen (r[i].resp): 0); + } + else if (msg[i]->msg_style == PAM_PROMPT_ECHO_ON) + { + r[i].resp = strdup (ctx->pszUser); + debug_printf("conv: %d returning name [%s]\n", i, r[i].resp); + } + else + { + debug_printf("conv: %d style %d: [%s]\n", i, msg[i]->msg_style, msg[i]->msg? msg[i]->msg: "(null)"); + r[i].resp = NULL; + } + } + + *resp = r; + return PAM_SUCCESS; +} + +/* The entry point must be visible. */ +#if defined(_MSC_VER) || defined(__OS2__) +# define DECLEXPORT(type) __declspec(dllexport) type +#else +# ifdef VBOX_HAVE_VISIBILITY_HIDDEN +# define DECLEXPORT(type) __attribute__((visibility("default"))) type +# else +# define DECLEXPORT(type) type +# endif +#endif + +/* prototype to prevent gcc warning */ +DECLEXPORT(AUTHENTRY3) AuthEntry; + +DECLEXPORT(AuthResult) AUTHCALL AuthEntry(const char *pszCaller, + PAUTHUUID pUuid, + AuthGuestJudgement guestJudgement, + const char *pszUser, + const char *pszPassword, + const char *pszDomain, + int fLogon, + unsigned clientId) +{ + AuthResult result = AuthResultAccessDenied; + int rc; + PamContext ctx; + struct pam_conv pam_conversation; + pam_handle_t *pam_handle = NULL; + + (void)pszCaller; + (void)pUuid; + (void)guestJudgement; + (void)clientId; + + /* Only process logon requests. */ + if (!fLogon) + return result; /* Return value is ignored by the caller. */ + + debug_printf("u[%s], d[%s], p[%d]\n", pszUser, pszDomain, pszPassword ? strlen(pszPassword) : 0); + + ctx.pszUser = (char *)pszUser; + ctx.pszPassword = (char *)pszPassword; + + pam_conversation.conv = conv; + pam_conversation.appdata_ptr = &ctx; + + rc = auth_pam_init (); + + if (rc == PAM_SUCCESS) + { + debug_printf("init ok\n"); + + rc = fn_pam_start(auth_get_pam_service (), pszUser, &pam_conversation, &pam_handle); + + if (rc == PAM_SUCCESS) + { + debug_printf("start ok\n"); + + rc = fn_pam_authenticate(pam_handle, 0); + + if (rc == PAM_SUCCESS) + { + debug_printf("auth ok\n"); + + rc = fn_pam_acct_mgmt(pam_handle, 0); + if (rc == PAM_AUTHINFO_UNAVAIL + && + getenv("VBOX_PAM_ALLOW_INACTIVE") != NULL) + { + debug_printf("PAM_AUTHINFO_UNAVAIL\n"); + rc = PAM_SUCCESS; + } + + if (rc == PAM_SUCCESS) + { + debug_printf("access granted\n"); + + result = AuthResultAccessGranted; + } + else + { + debug_printf("pam_acct_mgmt failed %d. %s\n", rc, fn_pam_strerror (pam_handle, rc)); + } + } + else + { + debug_printf("pam_authenticate failed %d. %s\n", rc, fn_pam_strerror (pam_handle, rc)); + } + + fn_pam_end(pam_handle, rc); + } + else + { + debug_printf("pam_start failed %d\n", rc); + } + + auth_pam_close (); + + debug_printf("auth_pam_close completed\n"); + } + else + { + debug_printf("auth_pam_init failed %d\n", rc); + } + + return result; +} + diff --git a/src/VBox/HostServices/auth/simple/Makefile.kup b/src/VBox/HostServices/auth/simple/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostServices/auth/simple/Makefile.kup diff --git a/src/VBox/HostServices/auth/simple/VBoxAuthSimple.cpp b/src/VBox/HostServices/auth/simple/VBoxAuthSimple.cpp new file mode 100644 index 00000000..671f2f00 --- /dev/null +++ b/src/VBox/HostServices/auth/simple/VBoxAuthSimple.cpp @@ -0,0 +1,147 @@ +/* $Id: VBoxAuthSimple.cpp $ */ +/** @file + * VirtualBox External Authentication Library - Simple Authentication. + */ + +/* + * Copyright (C) 2006-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 + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <iprt/cdefs.h> +#include <iprt/uuid.h> +#include <iprt/sha.h> + +#include <VBox/VBoxAuth.h> + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/VirtualBox.h> + +using namespace com; + +/* If defined, debug messages will be written to the specified file. */ +//#define AUTH_DEBUG_FILE_NAME "/tmp/VBoxAuth.log" + + +static void dprintf(const char *pszFormat, ...) +{ +#ifdef AUTH_DEBUG_FILE_NAME + FILE *f = fopen(AUTH_DEBUG_FILE_NAME, "ab"); + if (f) + { + va_list va; + va_start(va, pszFormat); + vfprintf(f, pszFormat, va); + va_end(va); + fclose(f); + } +#else + RT_NOREF(pszFormat); +#endif +} + +RT_C_DECLS_BEGIN +DECLEXPORT(FNAUTHENTRY3) AuthEntry; +RT_C_DECLS_END + +DECLEXPORT(AuthResult) AUTHCALL AuthEntry(const char *pszCaller, + PAUTHUUID pUuid, + AuthGuestJudgement guestJudgement, + const char *pszUser, + const char *pszPassword, + const char *pszDomain, + int fLogon, + unsigned clientId) +{ + RT_NOREF(pszCaller, guestJudgement, pszDomain, clientId); + + /* default is failed */ + AuthResult result = AuthResultAccessDenied; + + /* only interested in logon */ + if (!fLogon) + /* return value ignored */ + return result; + + char uuid[RTUUID_STR_LENGTH] = {0}; + if (pUuid) + RTUuidToStr((PCRTUUID)pUuid, (char*)uuid, RTUUID_STR_LENGTH); + + /* the user might contain a domain name, split it */ + const char *user = strchr(pszUser, '\\'); + if (user) + user++; + else + user = (char*)pszUser; + + dprintf("VBoxAuth: uuid: %s, user: %s, pszPassword: %s\n", uuid, user, pszPassword); + + ComPtr<IVirtualBoxClient> virtualBoxClient; + ComPtr<IVirtualBox> virtualBox; + HRESULT rc; + + rc = virtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (SUCCEEDED(rc)) + { + rc = virtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam()); + if (SUCCEEDED(rc)) + { + Bstr key = BstrFmt("VBoxAuthSimple/users/%s", user); + Bstr password; + + /* lookup in VM's extra data? */ + if (pUuid) + { + ComPtr<IMachine> machine; + virtualBox->FindMachine(Bstr(uuid).raw(), machine.asOutParam()); + if (machine) + machine->GetExtraData(key.raw(), password.asOutParam()); + } + else + /* lookup global extra data */ + virtualBox->GetExtraData(key.raw(), password.asOutParam()); + + if (!password.isEmpty()) + { + /* calculate hash */ + uint8_t abDigest[RTSHA256_HASH_SIZE]; + RTSha256(pszPassword, strlen(pszPassword), abDigest); + char pszDigest[RTSHA256_DIGEST_LEN + 1]; + RTSha256ToString(abDigest, pszDigest, sizeof(pszDigest)); + + if (password == pszDigest) + result = AuthResultAccessGranted; + } + } + else + dprintf("VBoxAuth: failed to get VirtualBox object reference: %#x\n", rc); + } + else + dprintf("VBoxAuth: failed to get VirtualBoxClient object reference: %#x\n", rc); + + return result; +} + diff --git a/src/VBox/HostServices/auth/simple/VBoxAuthSimple.rc b/src/VBox/HostServices/auth/simple/VBoxAuthSimple.rc new file mode 100644 index 00000000..9f466a46 --- /dev/null +++ b/src/VBox/HostServices/auth/simple/VBoxAuthSimple.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxAuthSimple.rc $ */ +/** @file + * VBoxAuthSimple - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Simple Authentication Host Service\0" + VALUE "InternalName", "VBoxAuthSimple\0" + VALUE "OriginalFilename", "VBoxAuthSimple.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/auth/winlogon/Makefile.kup b/src/VBox/HostServices/auth/winlogon/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostServices/auth/winlogon/Makefile.kup diff --git a/src/VBox/HostServices/auth/winlogon/VBoxAuth.rc b/src/VBox/HostServices/auth/winlogon/VBoxAuth.rc new file mode 100644 index 00000000..800733f7 --- /dev/null +++ b/src/VBox/HostServices/auth/winlogon/VBoxAuth.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxAuth.rc $ */ +/** @file + * VBoxAuth - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Authentication Host Service\0" + VALUE "InternalName", "VBoxAuth\0" + VALUE "OriginalFilename", "VBoxAuth.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/auth/winlogon/winlogon.cpp b/src/VBox/HostServices/auth/winlogon/winlogon.cpp new file mode 100644 index 00000000..63277194 --- /dev/null +++ b/src/VBox/HostServices/auth/winlogon/winlogon.cpp @@ -0,0 +1,181 @@ +/* $Id: winlogon.cpp $ */ +/** @file + * VirtualBox External Authentication Library - Windows Logon Authentication. + */ + +/* + * Copyright (C) 2006-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 + */ + +/* If defined, debug messages will be written to the debugger. */ +// #define AUTH_DEBUG + +#include <iprt/win/windows.h> +#include <VBox/VBoxAuth.h> +#include <iprt/cdefs.h> + +#ifdef AUTH_DEBUG +# include <stdio.h> + +static void dprintfw(const WCHAR *fmt, ...) +{ + va_list va; + va_start(va, fmt); + + WCHAR buffer[1024]; + + _vsnwprintf(buffer, sizeof (buffer), fmt, va); + + OutputDebugStringW(buffer); + + va_end(va); +} +# define DBGAUTH(a) dprintfw a +#else +# define DBGAUTH(a) +#endif + +static WCHAR g_wszEmpty[] = { L"" }; + +static void freeWideChar(WCHAR *pwszString) +{ + if (pwszString && pwszString != &g_wszEmpty[0]) + { + size_t cb = (wcslen(pwszString) + 1) * sizeof(WCHAR); + SecureZeroMemory(pwszString, cb); + free(pwszString); + } +} + +static WCHAR *utf8ToWideChar(const char *pszString) +{ + /* + * Shortcut for empty strings. + */ + if (!pszString || *pszString == 0) + return &g_wszEmpty[0]; + + /* + * Return NULL on errors. + */ + WCHAR *pwszString = NULL; + + /* + * First calc result string length. + */ + const DWORD dwFlags = MB_ERR_INVALID_CHARS; + int cwc = MultiByteToWideChar(CP_UTF8, dwFlags, pszString, -1, NULL, 0); + if (cwc > 0) + { + /* + * Alloc space for result buffer. + */ + pwszString = (WCHAR *)malloc(cwc * sizeof(WCHAR)); + if (pwszString) + { + /* + * Do the translation. + */ + if (MultiByteToWideChar(CP_UTF8, dwFlags, pszString, -1, pwszString, cwc) <= 0) + { + /* translation error */ + free(pwszString); + pwszString = NULL; + } + } + } + + return pwszString; +} + +/* Prototype it to make sure we've got the right prototype. */ +#if defined(_MSC_VER) +extern "C" __declspec(dllexport) FNAUTHENTRY3 AuthEntry; +#else +extern "C" FNAUTHENTRY3 AuthEntry; +#endif + +/** + * @callback_method_impl{FNAUTHENTRY3} + */ +extern "C" DECLEXPORT(AuthResult) AUTHCALL +AuthEntry(const char *pszCaller, + PAUTHUUID pUuid, + AuthGuestJudgement guestJudgement, + const char *pszUser, + const char *pszPassword, + const char *pszDomain, + int fLogon, + unsigned clientId) +{ + RT_NOREF4(pszCaller, pUuid, guestJudgement, clientId); + if (!fLogon) + { + /* Nothing to cleanup. The return code does not matter. */ + return AuthResultAccessDenied; + } + + LPWSTR pwszUsername = utf8ToWideChar(pszUser); + LPWSTR pwszDomain = utf8ToWideChar(pszDomain); + LPWSTR pwszPassword = utf8ToWideChar(pszPassword); + + DBGAUTH((L"u[%ls], d[%ls], p[%ls]\n", lpwszUsername, lpwszDomain, lpwszPassword)); + + AuthResult result = AuthResultAccessDenied; + + if (pwszUsername && pwszDomain && pwszPassword) + { + /* LOGON32_LOGON_INTERACTIVE is intended for users who will be interactively using the computer, + * such as a user being logged on by a terminal server, remote shell, or similar process. + */ + DWORD dwLogonType = LOGON32_LOGON_INTERACTIVE; + DWORD dwLogonProvider = LOGON32_PROVIDER_DEFAULT; + + HANDLE hToken; + + BOOL fSuccess = LogonUserW(pwszUsername, + pwszDomain, + pwszPassword, + dwLogonType, + dwLogonProvider, + &hToken); + + if (fSuccess) + { + DBGAUTH((L"LogonUser success. hToken = %p\n", hToken)); + + result = AuthResultAccessGranted; + + CloseHandle(hToken); + } + else + { + DBGAUTH((L"LogonUser failed %08X\n", GetLastError())); + } + } + + freeWideChar(pwszUsername); + freeWideChar(pwszDomain); + freeWideChar(pwszPassword); + + return result; +} + diff --git a/src/VBox/HostServices/common/client.cpp b/src/VBox/HostServices/common/client.cpp new file mode 100644 index 00000000..67a03b8f --- /dev/null +++ b/src/VBox/HostServices/common/client.cpp @@ -0,0 +1,241 @@ +/* $Id: client.cpp $ */ +/** @file + * Base class for a host-guest service. + */ + +/* + * 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 + */ + +#include <VBox/log.h> +#include <VBox/hgcmsvc.h> + +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/cpp/utils.h> + +#include <VBox/HostServices/Service.h> + +using namespace HGCM; + +Client::Client(uint32_t idClient) + : m_idClient(idClient) + , m_fDeferred(false) +{ + RT_ZERO(m_Deferred); + RT_ZERO(m_SvcCtx); +} + +Client::~Client(void) +{ + +} + +/** + * Completes a guest call by returning the control back to the guest side, + * together with a status code, internal version. + * + * @returns IPRT status code. + * @param hHandle Call handle to complete guest call for. + * @param rcOp Return code to return to the guest side. + */ +int Client::completeInternal(VBOXHGCMCALLHANDLE hHandle, int rcOp) RT_NOEXCEPT +{ + LogFlowThisFunc(("idClient=%RU32\n", m_idClient)); + + if ( m_SvcCtx.pHelpers + && m_SvcCtx.pHelpers->pfnCallComplete) + { + m_SvcCtx.pHelpers->pfnCallComplete(hHandle, rcOp); + + reset(); + return VINF_SUCCESS; + } + + return VERR_NOT_AVAILABLE; +} + +/** + * Resets the client's internal state. + */ +void Client::reset(void) RT_NOEXCEPT +{ + m_fDeferred = false; + + RT_ZERO(m_Deferred); +} + +/** + * Completes a guest call by returning the control back to the guest side, + * together with a status code. + * + * @returns IPRT status code. + * @param hHandle Call handle to complete guest call for. + * @param rcOp Return code to return to the guest side. + */ +int Client::Complete(VBOXHGCMCALLHANDLE hHandle, int rcOp /* = VINF_SUCCESS */) RT_NOEXCEPT +{ + return completeInternal(hHandle, rcOp); +} + +/** + * Completes a deferred guest call by returning the control back to the guest side, + * together with a status code. + * + * @returns IPRT status code. VERR_INVALID_STATE if the client is not in deferred mode. + * @param rcOp Return code to return to the guest side. + */ +int Client::CompleteDeferred(int rcOp) RT_NOEXCEPT +{ + if (m_fDeferred) + { + Assert(m_Deferred.hHandle != NULL); + + int rc = completeInternal(m_Deferred.hHandle, rcOp); + if (RT_SUCCESS(rc)) + m_fDeferred = false; + + return rc; + } + + AssertMsg(m_fDeferred, ("Client %RU32 is not in deferred mode\n", m_idClient)); + return VERR_INVALID_STATE; +} + +/** + * Returns the HGCM call handle of the client. + * + * @returns HGCM handle. + */ +VBOXHGCMCALLHANDLE Client::GetHandle(void) const RT_NOEXCEPT +{ + return m_Deferred.hHandle; +} + +/** + * Returns the HGCM call handle of the client. + * + * @returns HGCM handle. + */ +uint32_t Client::GetMsgType(void) const RT_NOEXCEPT +{ + return m_Deferred.uType; +} + +uint32_t Client::GetMsgParamCount(void) const RT_NOEXCEPT +{ + return m_Deferred.cParms; +} + +/** + * Returns the client's (HGCM) ID. + * + * @returns The client's (HGCM) ID. + */ +uint32_t Client::GetClientID(void) const RT_NOEXCEPT +{ + return m_idClient; +} + +/** + * Returns whether the client currently is in deferred mode or not. + * + * @returns \c True if in deferred mode, \c False if not. + */ +bool Client::IsDeferred(void) const RT_NOEXCEPT +{ + return m_fDeferred; +} + +/** + * Set the client's status to deferred, meaning that it does not return to the caller + * until CompleteDeferred() has been called. + * + * @returns VBox status code. + * @param hHandle Call handle to save. + * @param u32Function Function number to save. + * @param cParms Number of HGCM parameters to save. + * @param paParms HGCM parameters to save. + */ +void Client::SetDeferred(VBOXHGCMCALLHANDLE hHandle, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) RT_NOEXCEPT +{ + LogFlowThisFunc(("uClient=%RU32\n", m_idClient)); + + m_fDeferred = true; + + m_Deferred.hHandle = hHandle; + m_Deferred.uType = u32Function; + m_Deferred.cParms = cParms; + m_Deferred.paParms = paParms; +} + +/** + * Sets the HGCM service context. + * + * @param SvcCtx Service context to set. + */ +void Client::SetSvcContext(const VBOXHGCMSVCTX &SvcCtx) RT_NOEXCEPT +{ + m_SvcCtx = SvcCtx; +} + +/** + * Sets the deferred parameters to a specific message type and + * required parameters. That way the client can re-request that message with + * the right amount of parameters from the service. + * + * @returns IPRT status code. + * @param uMsg Message type (number) to set. + * @param cParms Number of parameters the message needs. + */ +int Client::SetDeferredMsgInfo(uint32_t uMsg, uint32_t cParms) RT_NOEXCEPT +{ + if (m_fDeferred) + { + if (m_Deferred.cParms < 2) + return VERR_INVALID_PARAMETER; + + AssertPtrReturn(m_Deferred.paParms, VERR_BUFFER_OVERFLOW); + + HGCMSvcSetU32(&m_Deferred.paParms[0], uMsg); + HGCMSvcSetU32(&m_Deferred.paParms[1], cParms); + + return VINF_SUCCESS; + } + + AssertFailed(); + return VERR_INVALID_STATE; +} + +/** + * Sets the deferred parameters to a specific message type and + * required parameters. That way the client can re-request that message with + * the right amount of parameters from the service. + * + * @returns IPRT status code. + * @param pMessage Message to get message type and required parameters from. + */ +int Client::SetDeferredMsgInfo(const Message *pMessage) RT_NOEXCEPT +{ + AssertPtrReturn(pMessage, VERR_INVALID_POINTER); + return SetDeferredMsgInfo(pMessage->GetType(), pMessage->GetParamCount()); +} + diff --git a/src/VBox/HostServices/common/message.cpp b/src/VBox/HostServices/common/message.cpp new file mode 100644 index 00000000..cb295563 --- /dev/null +++ b/src/VBox/HostServices/common/message.cpp @@ -0,0 +1,301 @@ +/* $Id: message.cpp $ */ +/** @file + * Base class for wrapping HCGM messages. + */ + +/* + * Copyright (C) 2018-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 + */ + +#include <VBox/HostServices/Service.h> +#include <VBox/VMMDev.h> /* For VMMDEV_MAX_HGCM_PARMS. */ + +using namespace HGCM; + +Message::Message(void) + : m_uMsg(0) + , m_cParms(0) + , m_paParms(NULL) +{ +} + +Message::Message(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) + : m_uMsg(0) + , m_cParms(0) + , m_paParms(NULL) +{ + initData(uMsg, cParms, aParms); +} + +Message::~Message(void) +{ + reset(); +} + +/** + * Resets the message by free'ing all allocated parameters and resetting the rest. + */ +void Message::reset(void) RT_NOEXCEPT +{ + if (m_paParms) + { + for (uint32_t i = 0; i < m_cParms; ++i) + { + switch (m_paParms[i].type) + { + case VBOX_HGCM_SVC_PARM_PTR: + if (m_paParms[i].u.pointer.size) + RTMemFree(m_paParms[i].u.pointer.addr); + break; + } + } + RTMemFree(m_paParms); + m_paParms = 0; + } + m_cParms = 0; + m_uMsg = 0; +} + +/** + * Returns the parameter count of this message. + * + * @returns Parameter count. + */ +uint32_t Message::GetParamCount(void) const RT_NOEXCEPT +{ + return m_cParms; +} + +/** + * Retrieves the raw HGCM parameter data + * + * @returns IPRT status code. + * @param uMsg Message type to retrieve the parameter data for. Needed for sanity. + * @param cParms Size (in parameters) of @a aParms array. + * @param aParms Where to store the HGCM parameter data. + */ +int Message::GetData(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) const RT_NOEXCEPT +{ + if (m_uMsg != uMsg) + { + LogFlowFunc(("Stored message type (%RU32) does not match request (%RU32)\n", m_uMsg, uMsg)); + return VERR_INVALID_PARAMETER; + } + + if (m_cParms == 0) /* Nothing to copy, take a shortcut. */ + return VINF_SUCCESS; + + if (m_cParms > cParms) + { + LogFlowFunc(("Stored parameter count (%RU32) exceeds request buffer (%RU32)\n", m_cParms, cParms)); + return VERR_INVALID_PARAMETER; + } + + return Message::CopyParms(&aParms[0], cParms, m_paParms, m_cParms, false /* fDeepCopy */); +} + +/** + * Retrieves a specific parameter value as uint32_t. + * + * @returns IPRT status code. + * @param uParm Index of parameter to retrieve. + * @param pu32Info Where to store the parameter value. + */ +int Message::GetParmU32(uint32_t uParm, uint32_t *pu32Info) const RT_NOEXCEPT +{ + AssertPtrReturn(pu32Info, VERR_INVALID_PARAMETER); + AssertReturn(uParm < m_cParms, VERR_INVALID_PARAMETER); + AssertReturn(m_paParms[uParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_INVALID_PARAMETER); + + *pu32Info = m_paParms[uParm].u.uint32; + + return VINF_SUCCESS; +} + +/** + * Retrieves a specific parameter value as uint64_t. + * + * @returns IPRT status code. + * @param uParm Index of parameter to retrieve. + * @param pu64Info Where to store the parameter value. + */ +int Message::GetParmU64(uint32_t uParm, uint64_t *pu64Info) const RT_NOEXCEPT +{ + AssertPtrReturn(pu64Info, VERR_INVALID_PARAMETER); + AssertReturn(uParm < m_cParms, VERR_INVALID_PARAMETER); + AssertReturn(m_paParms[uParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_INVALID_PARAMETER); + + *pu64Info = m_paParms[uParm].u.uint64; + + return VINF_SUCCESS; +} + +/** + * Retrieves a specific parameter value as a data address + size. + * + * @returns IPRT status code. + * @param uParm Index of parameter to retrieve. + * @param ppvAddr Where to store the data address. + * @param pcbSize Where to store the data size (in bytes). + * + * @remarks Does not copy (store) the actual content of the pointer (deep copy). + */ +int Message::GetParmPtr(uint32_t uParm, void **ppvAddr, uint32_t *pcbSize) const RT_NOEXCEPT +{ + AssertPtrReturn(ppvAddr, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbSize, VERR_INVALID_PARAMETER); + AssertReturn(uParm < m_cParms, VERR_INVALID_PARAMETER); + AssertReturn(m_paParms[uParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_INVALID_PARAMETER); + + *ppvAddr = m_paParms[uParm].u.pointer.addr; + *pcbSize = m_paParms[uParm].u.pointer.size; + + return VINF_SUCCESS; +} + +/** + * Returns the type of this message. + * + * @returns Message type. + */ +uint32_t Message::GetType(void) const RT_NOEXCEPT +{ + return m_uMsg; +} + +/** + * Copies HGCM parameters from source to destination. + * + * @returns IPRT status code. + * @param paParmsDst Destination array to copy parameters to. + * @param cParmsDst Size (in parameters) of destination array. + * @param paParmsSrc Source array to copy parameters from. + * @param cParmsSrc Size (in parameters) of source array. + * @param fDeepCopy Whether to perform a deep copy of pointer parameters or not. + * + * @remark Static convenience function. + */ +/* static */ +int Message::CopyParms(PVBOXHGCMSVCPARM paParmsDst, uint32_t cParmsDst, + PVBOXHGCMSVCPARM paParmsSrc, uint32_t cParmsSrc, + bool fDeepCopy) RT_NOEXCEPT +{ + AssertPtrReturn(paParmsSrc, VERR_INVALID_POINTER); + AssertPtrReturn(paParmsDst, VERR_INVALID_POINTER); + + if (cParmsSrc > cParmsDst) + return VERR_BUFFER_OVERFLOW; + + for (uint32_t i = 0; i < cParmsSrc; i++) + { + paParmsDst[i].type = paParmsSrc[i].type; + switch (paParmsSrc[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + { + paParmsDst[i].u.uint32 = paParmsSrc[i].u.uint32; + break; + } + case VBOX_HGCM_SVC_PARM_64BIT: + { + paParmsDst[i].u.uint64 = paParmsSrc[i].u.uint64; + break; + } + case VBOX_HGCM_SVC_PARM_PTR: + { + /* Do we have to perform a deep copy? */ + if (fDeepCopy) + { + /* Yes, do so. */ + paParmsDst[i].u.pointer.size = paParmsSrc[i].u.pointer.size; + if (paParmsDst[i].u.pointer.size > 0) + { + paParmsDst[i].u.pointer.addr = RTMemAlloc(paParmsDst[i].u.pointer.size); + if (!paParmsDst[i].u.pointer.addr) + return VERR_NO_MEMORY; + } + } + else + { + /* No, but we have to check if there is enough room. */ + if (paParmsDst[i].u.pointer.size < paParmsSrc[i].u.pointer.size) + return VERR_BUFFER_OVERFLOW; + } + + if (paParmsSrc[i].u.pointer.size) + { + if ( paParmsDst[i].u.pointer.addr + && paParmsDst[i].u.pointer.size) + memcpy(paParmsDst[i].u.pointer.addr, + paParmsSrc[i].u.pointer.addr, + RT_MIN(paParmsDst[i].u.pointer.size, paParmsSrc[i].u.pointer.size)); + else + return VERR_INVALID_POINTER; + } + break; + } + default: + { + AssertMsgFailed(("Unknown HGCM type %u\n", paParmsSrc[i].type)); + return VERR_INVALID_PARAMETER; + } + } + } + return VINF_SUCCESS; +} + +/** + * Initializes the message with a message type and parameters. + * + * @returns IPRT status code. + * @param uMsg Message type to set. + * @param cParms Number of parameters to set. + * @param aParms Array of parameters to set. + */ +int Message::initData(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) RT_NOEXCEPT +{ + AssertReturn(cParms < VMMDEV_MAX_HGCM_PARMS, VERR_INVALID_PARAMETER); + AssertReturn(cParms == 0 || aParms != NULL, VERR_INVALID_POINTER); + + /* Cleanup any eventual old stuff. */ + reset(); + + m_uMsg = uMsg; + m_cParms = cParms; + + int rc = VINF_SUCCESS; + + if (cParms) + { + m_paParms = (VBOXHGCMSVCPARM*)RTMemAllocZ(sizeof(VBOXHGCMSVCPARM) * m_cParms); + if (m_paParms) + { + rc = Message::CopyParms(m_paParms, m_cParms, &aParms[0], cParms, true /* fDeepCopy */); + if (RT_FAILURE(rc)) + reset(); + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + diff --git a/src/VBox/HostServices/testcase/Makefile.kmk b/src/VBox/HostServices/testcase/Makefile.kmk new file mode 100644 index 00000000..a6b63610 --- /dev/null +++ b/src/VBox/HostServices/testcase/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the HGCM service testcase. +# + +# +# Copyright (C) 2009-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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + # + # Set this in LocalConfig.kmk if you are working on HGCM service internals + # to automatically run the unit test at build time: + # OTHERS += $(tstHGCMSvc_0_OUTDIR)/tstHGCMSvc.run + # + PROGRAMS += tstHGCMSvc + TESTING += $(tstHGCMSvc_0_OUTDIR)/tstHGCMSvc.run + tstHGCMSvc_TEMPLATE = VBoxR3TstExe + tstHGCMSvc_DEFS = VBOX_WITH_HGCM VBOX_TEST_HGCM_PARMS + tstHGCMSvc_SOURCES = tstHGCMSvc.cpp + tstHGCMSvc_CLEAN = $(tstHGCMSvc_0_OUTDIR)/tstHGCMSvc.run + + $$(tstHGCMSvc_0_OUTDIR)/tstHGCMSvc.run: $$(tstHGCMSvc_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstHGCMSvc_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif # VBOX_WITH_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/testcase/tstHGCMSvc.cpp b/src/VBox/HostServices/testcase/tstHGCMSvc.cpp new file mode 100644 index 00000000..9c2ee782 --- /dev/null +++ b/src/VBox/HostServices/testcase/tstHGCMSvc.cpp @@ -0,0 +1,119 @@ +/* $Id: tstHGCMSvc.cpp $ */ +/** @file + * HGCM Service Testcase. + */ + +/* + * Copyright (C) 2009-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/hgcmsvc.h> +#include <iprt/initterm.h> +#include <iprt/test.h> + +/** Test the getString member function. Indirectly tests the getPointer + * and getBuffer APIs. + * @param hTest an running IPRT test + * @param type the type that the parameter should be set to before + * calling getString + * @param pcch the value that the parameter should be set to before + * calling getString, and also the address (!) which we + * expect getString to return. Stricter than needed of + * course, but I was feeling lazy. + * @param cb the size that the parameter should be set to before + * calling getString, and also the size which we expect + * getString to return. + * @param rcExp the expected return value of the call to getString. + */ +void doTestGetString(VBOXHGCMSVCPARM *pParm, RTTEST hTest, uint32_t type, + const char *pcch, uint32_t cb, int rcExp) +{ + /* An RTTest API like this, which would print out an additional line + * of context if a test failed, would be nice. This is because the + * line number alone doesn't help much here, given that this is a + * subroutine called many times. */ + /* + RTTestContextF(hTest, + ("doTestGetString, type=%u, pcch=%p, acp=%u, rcExp=%Rrc", + type, pcch, acp, rcExp)); + */ + HGCMSvcSetPv(pParm, (void *)pcch, cb); + pParm->type = type; /* in case we don't want VBOX_HGCM_SVC_PARM_PTR */ + const char *pcch2 = NULL; + uint32_t cb2 = 0; + int rc = HGCMSvcGetCStr(pParm, &pcch2, &cb2); + RTTEST_CHECK_RC(hTest, rc, rcExp); + if (RT_SUCCESS(rcExp)) + { + RTTEST_CHECK_MSG_RETV(hTest, (pcch2 == pcch), + (hTest, "expected %p, got %p", pcch, pcch2)); + RTTEST_CHECK_MSG_RETV(hTest, (cb2 == cb), + (hTest, "expected %u, got %u", cb, cb2)); + } +} + +/** Run some unit tests on the getString method and indirectly test + * getPointer and getBuffer as well. */ +void testGetString(VBOXHGCMSVCPARM *pParm, RTTEST hTest) +{ + RTTestSub(hTest, "HGCM string parameter handling"); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_32BIT, "test", 3, + VERR_INVALID_PARAMETER); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_PTR, "test", 5, + VINF_SUCCESS); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_PTR, "test", 3, + VERR_BUFFER_OVERFLOW); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_PTR, "test\xf0", 6, + VERR_INVALID_UTF8_ENCODING); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_PTR, "test", 0, + VERR_INVALID_PARAMETER); + doTestGetString(pParm, hTest, VBOX_HGCM_SVC_PARM_PTR, (const char *)0x1, 5, + VERR_INVALID_PARAMETER); + RTTestSubDone(hTest); +} + +int main() +{ + /* + * Init the runtime, test and say hello. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("tstHGCMSvc", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + + /* + * Run the test. + */ + VBOXHGCMSVCPARM parm; + testGetString(&parm, hTest); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + |