diff options
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard')
16 files changed, 8668 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk new file mode 100644 index 00000000..4a4d2fbc --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -0,0 +1,109 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service. +# + +# +# Copyright (C) 2006-2020 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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 = VBOXR3 +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 + VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_HOST + VBoxSharedClipboard_SOURCES += \ + VBoxSharedClipboardSvc-transfers.cpp \ + VBoxSharedClipboardSvc-utils.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardArea.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_VMM) \ + $(LIB_RUNTIME) \ + $(LIB_REM) +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..20a1038f --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp @@ -0,0 +1,320 @@ +/* $Id: VBoxSharedClipboardSvc-darwin.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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) + 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 ShClSvcImplInit(void) +{ + 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 ShClSvcImplDestroy(void) +{ + /* + * 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 ShClSvcImplConnect(PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(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 ShClSvcImplSync(PSHCLCLIENT pClient) +{ + /* Sync the host clipboard content with the client. */ + ShClSvcLock(); + + int rc = vboxClipboardChanged(pClient->State.pCtx); + + ShClSvcUnlock(); + + return rc; +} + +int ShClSvcImplDisconnect(PSHCLCLIENT pClient) +{ + ShClSvcLock(); + + pClient->State.pCtx->pClient = NULL; + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +int ShClSvcImplFormatAnnounce(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + LogFlowFunc(("fFormats=%02X\n", fFormats)); + + /** @todo r=bird: BUGBUG: The following is probably a mistake. */ + 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 ShClSvcDataReadRequest(pClient, fFormats, NULL /* pidEvent */); +} + +int ShClSvcImplReadData(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(pCmdCtx); + + ShClSvcLock(); + + /* Default to no data available. */ + *pcbActual = 0; + + int rc = readFromPasteboard(pClient->State.pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); + + ShClSvcUnlock(); + + return rc; +} + +int ShClSvcImplWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pCmdCtx); + + ShClSvcLock(); + + writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClSvcImplTransferReadDir(PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClSvcImplTransferWriteDir(PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClSvcImplTransferReadFileHdr(PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClSvcImplTransferWriteFileHdr(PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClSvcImplTransferReadFileData(PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(pClient, pFileData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClSvcImplTransferWriteFileData(PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(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..74dfceec --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -0,0 +1,405 @@ +/* $Id: VBoxSharedClipboardSvc-internal.h $ */ +/** @file + * Shared Clipboard Service - Internal header. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 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; + + 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; + +typedef struct _SHCLCLIENT +{ + /** 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 cAllocatedMessages; + /** 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 + /** Transfer contextdata. */ + SHCLTRANSFERCTX TransferCtx; +#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; + +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 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 ShClSvcDataReadRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENTID pidEvent); +int ShClSvcDataReadSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +uint32_t ShClSvcGetMode(void); +bool ShClSvcGetHeadless(void); +bool ShClSvcLock(void); +void ShClSvcUnlock(void); +/** @} */ + + +/** @name Platform-dependent implementations for the Shared Clipboard host service, called *only* by the host service. + * @{ + */ +/** + * Called on initialization. + */ +int ShClSvcImplInit(void); +/** + * Called on destruction. + */ +void ShClSvcImplDestroy(void); +/** + * Called when a new HGCM client connects. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + * @param fHeadless Whether this is a headless connection or not. + */ +int ShClSvcImplConnect(PSHCLCLIENT pClient, bool fHeadless); +/** + * Called when a HGCM client disconnects. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + */ +int ShClSvcImplDisconnect(PSHCLCLIENT pClient); +/** + * Called when the guest reported available clipboard formats to the host OS. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + * @param fFormats The announced formats from the guest, + * VBOX_SHCL_FMT_XXX. + */ +int ShClSvcImplFormatAnnounce(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +/** @todo Document: Can return VINF_HGCM_ASYNC_EXECUTE to defer returning read data.*/ +/** + * Called when the guest wants to read host clipboard data. + * + * @returns VBox status code. + * @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. + */ +int ShClSvcImplReadData(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 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 ShClSvcImplWriteData(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 pClient Shared Clipboard client context. + */ +int ShClSvcImplSync(PSHCLCLIENT pClient); +/** @} */ + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Host implementations for Shared Clipboard transfers. + * @{ + */ +/** + * Called when a transfer gets created. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer created. + */ +int ShClSvcImplTransferCreate(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called when a transfer gets destroyed. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to destroy. + */ +int ShClSvcImplTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called when getting (determining) the transfer roots on the host side. + * + * @returns VBox status code. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to get roots for. + */ +int ShClSvcImplTransferGetRoots(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** @} */ +#endif + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Internal Shared Clipboard transfer host service functions. + * @{ + */ +int shClSvcTransferAreaDetach(PSHCLCLIENTSTATE pClientState, PSHCLTRANSFER pTransfer); +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. + * @{ + */ +int shClSvcTransferIfaceOpen(PSHCLPROVIDERCTX pCtx); +int shClSvcTransferIfaceClose(PSHCLPROVIDERCTX pCtx); + +int shClSvcTransferIfaceGetRoots(PSHCLPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList); + +int shClSvcTransferIfaceListOpen(PSHCLPROVIDERCTX pCtx, PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList); +int shClSvcTransferIfaceListClose(PSHCLPROVIDERCTX pCtx, SHCLLISTHANDLE hList); +int shClSvcTransferIfaceListHdrRead(PSHCLPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListHdrWrite(PSHCLPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListEntryRead(PSHCLPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); +int shClSvcTransferIfaceListEntryWrite(PSHCLPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); + +int shClSvcTransferIfaceObjOpen(PSHCLPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj); +int shClSvcTransferIfaceObjClose(PSHCLPROVIDERCTX pCtx, SHCLOBJHANDLE hObj); +int shClSvcTransferIfaceObjRead(PSHCLPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead); +int shClSvcTransferIfaceObjWrite(PSHCLPROVIDERCTX 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(PSHCLTRANSFERCALLBACKDATA pData); +DECLCALLBACK(void) VBoxSvcClipboardDataHeaderCompleteCallback(PSHCLTRANSFERCALLBACKDATA pData); +DECLCALLBACK(void) VBoxSvcClipboardDataCompleteCallback(PSHCLTRANSFERCALLBACKDATA pData); +DECLCALLBACK(void) VBoxSvcClipboardTransferCompleteCallback(PSHCLTRANSFERCALLBACKDATA pData, int rc); +DECLCALLBACK(void) VBoxSvcClipboardTransferCanceledCallback(PSHCLTRANSFERCALLBACKDATA pData); +DECLCALLBACK(void) VBoxSvcClipboardTransferErrorCallback(PSHCLTRANSFERCALLBACKDATA pData, 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..bd505b08 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -0,0 +1,2254 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp $ */ +/** @file + * Shared Clipboard Service - Internal code for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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(); + + const uint32_t cTransfers = ShClTransferCtxGetTotalTransfers(&pClient->TransferCtx); + for (uint32_t i = 0; i < cTransfers; i++) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransfer(&pClient->TransferCtx, i); + if (pTransfer) + shClSvcTransferAreaDetach(&pClient->State, pTransfer); + } + + ShClTransferCtxDestroy(&pClient->TransferCtx); +} + + +/********************************************************************************************************************************* +* Provider implementation * +*********************************************************************************************************************************/ + +DECLCALLBACK(int) shClSvcTransferIfaceOpen(PSHCLPROVIDERCTX pCtx) +{ + LogFlowFuncEnter(); + + RT_NOREF(pCtx); + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) shClSvcTransferIfaceClose(PSHCLPROVIDERCTX pCtx) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc = shClSvcTransferStop(pClient, pCtx->pTransfer); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceGetRoots(PSHCLPROVIDERCTX 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) + { + SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsgHdr->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, idEvent)); + HGCMSvcSetU32(&pMsgHdr->aParms[1], 0 /* fRoots */); + + shClSvcMsgAdd(pClient, pMsgHdr, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayloadHdr; + rc = ShClEventWait(&pCtx->pTransfer->Events, idEvent, + 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); + + idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsgEntry->aParms[0], + VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uClientID, + pCtx->pTransfer->State.uID, idEvent)); + HGCMSvcSetU32(&pMsgEntry->aParms[1], 0 /* fRoots */); + HGCMSvcSetU32(&pMsgEntry->aParms[2], i /* uIndex */); + + shClSvcMsgAdd(pClient, pMsgEntry, true /* fAppend */); + + PSHCLEVENTPAYLOAD pPayloadEntry; + rc = ShClEventWait(&pCtx->pTransfer->Events, idEvent, + 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); + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + 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; + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeave(); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListOpen(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + 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(&pCtx->pTransfer->Events, idEvent, 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); + } + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListClose(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + 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(&pCtx->pTransfer->Events, idEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + ShClPayloadFree(pPayload); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrRead(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, 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(&pCtx->pTransfer->Events, idEvent, + pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTHDR)); + + *pListHdr = *(PSHCLLISTHDR)pPayload->pvData; + + ShClPayloadFree(pPayload); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrWrite(PSHCLPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + RT_NOREF(pCtx, hList, pListHdr); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryRead(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, 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(&pCtx->pTransfer->Events, idEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTENTRY)); + + rc = ShClTransferListEntryCopy(pListEntry, (PSHCLLISTENTRY)pPayload->pvData); + + ShClPayloadFree(pPayload); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryWrite(PSHCLPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry) +{ + RT_NOREF(pCtx, hList, pListEntry); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +int shClSvcTransferIfaceObjOpen(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + 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, 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(&pCtx->pTransfer->Events, idEvent, 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); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjClose(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(&pCtx->pTransfer->Events, idEvent, 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); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjRead(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, 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(&pCtx->pTransfer->Events, idEvent, 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); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjWrite(PSHCLPROVIDERCTX 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) + { + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pCtx->pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, 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(&pCtx->pTransfer->Events, idEvent, 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); + } + } + + ShClEventUnregister(&pCtx->pTransfer->Events, idEvent); + } + 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) + { + uint32_t cbPayload = 0; + + /* 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 = HGCMSvcGetU32(&aParms[3], &cbPayload); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[4], &pReply->pvPayload, &pReply->cbPayload); + AssertReturn(cbPayload == pReply->cbPayload, VERR_INVALID_PARAMETER); + } + + if (RT_SUCCESS(rc)) + { + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + switch (pReply->uType) + { + case VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS: + { + if (cParms >= 6) + rc = HGCMSvcGetU32(&aParms[5], &pReply->u.TransferStatus.uStatus); + + LogFlowFunc(("uTransferStatus=%RU32\n", pReply->u.TransferStatus.uStatus)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN: + { + if (cParms >= 6) + rc = HGCMSvcGetU64(&aParms[5], &pReply->u.ListOpen.uHandle); + + LogFlowFunc(("hListOpen=%RU64\n", pReply->u.ListOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE: + { + if (cParms >= 6) + rc = HGCMSvcGetU64(&aParms[5], &pReply->u.ListClose.uHandle); + + LogFlowFunc(("hListClose=%RU64\n", pReply->u.ListClose.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN: + { + if (cParms >= 6) + rc = HGCMSvcGetU64(&aParms[5], &pReply->u.ObjOpen.uHandle); + + LogFlowFunc(("hObjOpen=%RU64\n", pReply->u.ObjOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE: + { + if (cParms >= 6) + rc = HGCMSvcGetU64(&aParms[5], &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) + { + uint32_t cbPath = 0; + uint32_t cbFilter = 0; + + rc = HGCMSvcGetU32(&aParms[1], &pOpenParms->fList); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &cbFilter); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetStr(&aParms[3], &pOpenParms->pszFilter, &pOpenParms->cbFilter); + AssertReturn(cbFilter == pOpenParms->cbFilter, VERR_INVALID_PARAMETER); + } + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[4], &cbPath); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetStr(&aParms[5], &pOpenParms->pszPath, &pOpenParms->cbPath); + AssertReturn(cbPath == pOpenParms->cbPath, VERR_INVALID_PARAMETER); + } + + /** @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); + HGCMSvcSetU32(&aParms[2], pOpenParms->cbFilter); + HGCMSvcSetPv (&aParms[3], pOpenParms->pszFilter, pOpenParms->cbFilter); + HGCMSvcSetU32(&aParms[4], pOpenParms->cbPath); + HGCMSvcSetPv (&aParms[5], pOpenParms->pszPath, pOpenParms->cbPath); + HGCMSvcSetU64(&aParms[6], 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); + + 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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + LogFlowFunc(("uCID=%RU64 -> idEvent=%RU32\n", uCID, idEvent)); + + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + } + 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; + } + + /* A (valid) service extension is needed because VBoxSVC needs to keep track of the + * clipboard areas cached on the host. */ + if (!g_ExtState.pfnExtension) + { +#ifdef DEBUG_andy + AssertPtr(g_ExtState.pfnExtension); +#endif + LogFunc(("Invalid / no service extension set, skipping transfer handling\n")); + return VERR_NOT_SUPPORTED; + } + + 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->TransferCtx)) + { + 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 = ShClTransferCtxGetTransfer(&pClient->TransferCtx, 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 = ShClSvcImplTransferGetRoots(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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + 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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + 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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + } + 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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + } + 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); + + uint32_t cbPath; + rc = HGCMSvcGetU32(&aParms[2], &cbPath); /** @todo r=bird: This is an pointless parameter. */ + if (RT_SUCCESS(rc)) + { + /** @todo r=bird: This is the wrong way of getting a string! */ + rc = HGCMSvcGetPv(&aParms[3], (void **)&openCreateParms.pszPath, &openCreateParms.cbPath); + if (cbPath != openCreateParms.cbPath) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[4], &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, &cbRead, 0 /* fFlags */); + 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 SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uCID); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + + 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; +} + +/** + * Registers an clipboard transfer area. + * + * @returns VBox status code. + * @param pClientState Client state to use. + * @param pTransfer Shared Clipboard transfer to register a clipboard area for. + */ +int shClSvcTransferAreaRegister(PSHCLCLIENTSTATE pClientState, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + AssertMsgReturn(pTransfer->pArea == NULL, ("An area already is registered for this transfer\n"), + VERR_WRONG_ORDER); + + pTransfer->pArea = new SharedClipboardArea(); + if (!pTransfer->pArea) + return VERR_NO_MEMORY; + + int rc; + + if (g_ExtState.pfnExtension) + { + SHCLEXTAREAPARMS parms; + RT_ZERO(parms); + + parms.uID = NIL_SHCLAREAID; + + /* As the meta data is now complete, register a new clipboard on the host side. */ + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_AREA_REGISTER, &parms, sizeof(parms)); + if (RT_SUCCESS(rc)) + { + /* Note: Do *not* specify SHCLAREA_OPEN_FLAGS_MUST_NOT_EXIST as flags here, as VBoxSVC took care of the + * clipboard area creation already. */ + rc = pTransfer->pArea->OpenTemp(parms.uID /* Area ID */, + SHCLAREA_OPEN_FLAGS_NONE); + } + + LogFlowFunc(("Registered new clipboard area (%RU32) by client %RU32 with rc=%Rrc\n", + parms.uID, pClientState->uClientID, rc)); + } + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Unregisters an clipboard transfer area. + * + * @returns VBox status code. + * @param pClientState Client state to use. + * @param pTransfer Shared Clipboard transfer to unregister a clipboard area from. + */ +int shClSvcTransferAreaUnregister(PSHCLCLIENTSTATE pClientState, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + if (!pTransfer->pArea) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + if (g_ExtState.pfnExtension) + { + SHCLEXTAREAPARMS parms; + RT_ZERO(parms); + + parms.uID = pTransfer->pArea->GetID(); + + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_AREA_UNREGISTER, &parms, sizeof(parms)); + if (RT_SUCCESS(rc)) + { + rc = pTransfer->pArea->Close(); + if (RT_SUCCESS(rc)) + { + delete pTransfer->pArea; + pTransfer->pArea = NULL; + } + } + + LogFlowFunc(("Unregistered clipboard area (%RU32) by client %RU32 with rc=%Rrc\n", + parms.uID, pClientState->uClientID, rc)); + } + + delete pTransfer->pArea; + pTransfer->pArea = NULL; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Attaches to an existing (registered) clipboard transfer area. + * + * @returns VBox status code. + * @param pClientState Client state to use. + * @param pTransfer Shared Clipboard transfer to attach a clipboard area to. + * @param uID ID of clipboard area to to attach to. Specify 0 to attach to the most recent one. + */ +int shClSvcTransferAreaAttach(PSHCLCLIENTSTATE pClientState, PSHCLTRANSFER pTransfer, + SHCLAREAID uID) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + AssertMsgReturn(pTransfer->pArea == NULL, ("An area already is attached to this transfer\n"), + VERR_WRONG_ORDER); + + pTransfer->pArea = new SharedClipboardArea(); + if (!pTransfer->pArea) + return VERR_NO_MEMORY; + + int rc = VINF_SUCCESS; + + if (g_ExtState.pfnExtension) + { + SHCLEXTAREAPARMS parms; + RT_ZERO(parms); + + parms.uID = uID; /* 0 means most recent clipboard area. */ + + /* The client now needs to attach to the most recent clipboard area + * to keep a reference to it. The host does the actual book keeping / cleanup then. + * + * This might fail if the host does not have a most recent clipboard area (yet). */ + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_AREA_ATTACH, &parms, sizeof(parms)); + if (RT_SUCCESS(rc)) + rc = pTransfer->pArea->OpenTemp(parms.uID /* Area ID */); + + LogFlowFunc(("Attached client %RU32 to clipboard area %RU32 with rc=%Rrc\n", + pClientState->uClientID, parms.uID, rc)); + } + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Detaches from an clipboard transfer area. + * + * @returns VBox status code. + * @param pClientState Client state to use. + * @param pTransfer Shared Clipboard transfer to detach a clipboard area from. + */ +int shClSvcTransferAreaDetach(PSHCLCLIENTSTATE pClientState, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + if (!pTransfer->pArea) + return VINF_SUCCESS; + + const uint32_t uAreaID = pTransfer->pArea->GetID(); + + int rc = VINF_SUCCESS; + + if (g_ExtState.pfnExtension) + { + SHCLEXTAREAPARMS parms; + RT_ZERO(parms); + parms.uID = uAreaID; + + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_AREA_DETACH, &parms, sizeof(parms)); + + LogFlowFunc(("Detached client %RU32 from clipboard area %RU32 with rc=%Rrc\n", + pClientState->uClientID, uAreaID, rc)); + } + + delete pTransfer->pArea; + pTransfer->pArea = NULL; + + 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 pidEvent Where to store the created wait event. Optional. + */ +int shClSvcTransferSendStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS uStatus, + int rcTransfer, PSHCLEVENTID pidEvent) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + /* pidEvent is optional. */ + + PSHCLCLIENTMSG pMsgReadData = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_STATUS, + VBOX_SHCL_CPARMS_TRANSFER_STATUS); + if (!pMsgReadData) + return VERR_NO_MEMORY; + + int rc; + + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pTransfer->Events); + if (idEvent != NIL_SHCLEVENTID) + { + HGCMSvcSetU64(&pMsgReadData->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pTransfer->State.uID, 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 (pidEvent) + { + *pidEvent = idEvent; + } + else /* If event is not consumed by the caller, unregister event again. */ + ShClEventUnregister(&pTransfer->Events, idEvent); + } + else + ShClEventUnregister(&pTransfer->Events, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Starts a new transfer, waiting for acknowledgement by the guest side. + * + * @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(); + + ShClTransferCtxCleanup(&pClient->TransferCtx); + + int rc; + + if (!ShClTransferCtxTransfersMaximumReached(&pClient->TransferCtx)) + { + LogRel2(("Shared Clipboard: Starting %s transfer ...\n", enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "read" : "write")); + + PSHCLTRANSFER pTransfer; + rc = ShClTransferCreate(&pTransfer); + if (RT_SUCCESS(rc)) + { + rc = ShClSvcImplTransferCreate(pClient, pTransfer); + if (RT_SUCCESS(rc)) + { + SHCLPROVIDERCREATIONCTX creationCtx; + RT_ZERO(creationCtx); + + if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) + { + rc = shClSvcTransferAreaRegister(&pClient->State, pTransfer); + if (RT_SUCCESS(rc)) + { + creationCtx.Interface.pfnTransferOpen = shClSvcTransferIfaceOpen; + creationCtx.Interface.pfnTransferClose = shClSvcTransferIfaceClose; + + 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; + + uint32_t uTransferID = 0; + + rc = ShClTransferSetInterface(pTransfer, &creationCtx); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxTransferRegister(&pClient->TransferCtx, pTransfer, &uTransferID); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferInit(pTransfer, uTransferID, enmDir, enmSource); + if (RT_SUCCESS(rc)) + { + if (RT_SUCCESS(rc)) + rc = ShClTransferStart(pTransfer); + + if (RT_SUCCESS(rc)) + { + SHCLEVENTID idEvent; + rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS, + &idEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for start of transfer %RU32 on guest ...\n", + pTransfer->State.uID)); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(&pTransfer->Events, idEvent, 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)); + } + else + LogRel(("Shared Clipboard: Unable to start transfer %RU32 on guest, rc=%Rrc\n", + pTransfer->State.uID, rc)); + } + } + } + + if (RT_FAILURE(rc)) + ShClTransferCtxTransferUnregister(&pClient->TransferCtx, uTransferID); + } + } + } + + if (RT_FAILURE(rc)) + { + ShClSvcImplTransferDestroy(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) +{ + SHCLEVENTID idEvent; + int rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_STOPPED, VINF_SUCCESS, + &idEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for stop of transfer %RU32 on guest ...\n", pTransfer->State.uID)); + + rc = ShClEventWait(&pTransfer->Events, idEvent, pTransfer->uTimeoutMs, NULL); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Stopped transfer %RU32 on guest\n", pTransfer->State.uID)); + } + + 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->TransferCtx, ShClTransferGetID(pTransfer)); + if (RT_SUCCESS(rc2)) + { + 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; + + 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..7d5dc118 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -0,0 +1,25 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.h $ */ +/** @file + * Shared Clipboard Service - Internal header for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef 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..1edf10a7 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp @@ -0,0 +1,32 @@ +/* $Id: VBoxSharedClipboardSvc-utils.cpp $ */ +/** @file + * Shared Clipboard Service - Host service utility functions. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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..2a16389e --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp @@ -0,0 +1,874 @@ +/* $Id: VBoxSharedClipboardSvc-win.cpp $ */ +/** @file + * Shared Clipboard Service - Win32 host. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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 <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; +}; + + +/** @todo Someone please explain the protocol wrt overflows... */ +static void vboxClipboardSvcWinGetData(uint32_t u32Format, const void *pvSrc, uint32_t cbSrc, + void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst) +{ + 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; + } + memcpy(pvDst, pszBuf, cbBuf); + RTMemFree(pszBuf); + } + else + *pcbActualDst = 0; + } + else + { + *pcbActualDst = cbSrc; + + if (cbSrc > cbDst) + { + /* Do not copy data. The dst buffer is not enough. */ + return; + } + + memcpy(pvDst, pvSrc, cbSrc); + } + +#ifdef LOG_ENABLED + ShClDbgDumpData(pvDst, cbSrc, u32Format); +#endif + + return; +} + +static int vboxClipboardSvcWinDataSet(PSHCLCONTEXT pCtx, UINT cfFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn (cbData, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + + HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbData); + + LogFlowFunc(("hMem=%p\n", hMem)); + + if (hMem) + { + void *pMem = GlobalLock(hMem); + + LogFlowFunc(("pMem=%p, GlobalSize=%zu\n", pMem, GlobalSize(hMem))); + + if (pMem) + { + LogFlowFunc(("Setting data\n")); + + memcpy(pMem, pvData, cbData); + + /* The memory must be unlocked before inserting to the Clipboard. */ + GlobalUnlock(hMem); + + /* 'hMem' contains the host clipboard data. + * size is 'cb' and format is 'format'. + */ + HANDLE hClip = SetClipboardData(cfFormat, hMem); + + LogFlowFunc(("hClip=%p\n", hClip)); + + if (hClip) + { + /* The hMem ownership has gone to the system. Nothing to do. */ + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_ACCESS_DENIED; + + GlobalFree(hMem); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +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 Clipbaord: Windows format %u not supported, ingoring\n", uFormat)); + return VERR_NOT_SUPPORTED; + } + + SHCLEVENTID idEvent = 0; + int rc = ShClSvcDataReadRequest(pCtx->pClient, fFormat, &idEvent); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(&pCtx->pClient->EventSrc, idEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + *ppvData = pPayload ? pPayload->pvData : NULL; + *pcbData = pPayload ? pPayload->cbData : 0; + } + + ShClEventRelease(&pCtx->pClient->EventSrc, idEvent); + ShClEventUnregister(&pCtx->pClient->EventSrc, idEvent); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, + HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + 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) + { + rc = vboxClipboardSvcWinDataSet(pCtx, uFormat, pvData, cbData); + + 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=0x%x\n", 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 = SharedClipboardWinOpen(hWnd); + if (RT_SUCCESS(rc)) + { + SharedClipboardWinClear(); + + rc = SharedClipboardWinAnnounceFormats(pWinCtx, fFormats); + + SharedClipboardWinClose(); + } +#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: + break; + } + + LogFlowFunc(("LEAVE hWnd=%p, WM_ %u\n", hWnd, uMsg)); + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +/** + * Static helper function for having a per-client proxy window instances. + */ +static LRESULT CALLBACK vboxClipboardSvcWinWndProcInstance(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) +{ + 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) +{ + /* 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. */ + 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 ShClSvcImplInit(void) +{ +#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 ShClSvcImplDestroy(void) +{ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ + OleUninitialize(); +#endif +} + +int ShClSvcImplConnect(PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(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 ShClSvcImplSync(PSHCLCLIENT pClient) +{ + /* Sync the host clipboard content with the client. */ + return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); +} + +int ShClSvcImplDisconnect(PSHCLCLIENT pClient) +{ + 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 ShClSvcImplFormatAnnounce(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + 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 ShClSvcImplReadData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, 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(pCmdCtx); + + AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("uFormat=%02X\n", uFormat)); + + 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)) + { + LogFunc(("Clipboard opened\n")); + + if (uFormat & VBOX_SHCL_FMT_BITMAP) + { + hClip = GetClipboardData(CF_DIB); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + + if (lp != NULL) + { + LogFunc(("CF_DIB\n")); + + vboxClipboardSvcWinGetData(VBOX_SHCL_FMT_BITMAP, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); + + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + hClip = GetClipboardData(CF_UNICODETEXT); + if (hClip != NULL) + { + LPWSTR uniString = (LPWSTR)GlobalLock(hClip); + + if (uniString != NULL) + { + LogFunc(("CF_UNICODETEXT\n")); + + vboxClipboardSvcWinGetData(VBOX_SHCL_FMT_UNICODETEXT, uniString, (lstrlenW(uniString) + 1) * 2, + pvData, cbData, pcbActual); + + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFormat & VBOX_SHCL_FMT_HTML) + { + UINT format = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML); + if (format != 0) + { + hClip = GetClipboardData(format); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + if (lp != NULL) + { + /** @todo r=andy Add data overflow handling. */ + vboxClipboardSvcWinGetData(VBOX_SHCL_FMT_HTML, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); +#ifdef VBOX_STRICT + LogFlowFunc(("Raw HTML clipboard data from host:")); + ShClDbgDumpHtml((char *)pvData, cbData); +#endif + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (uFormat & VBOX_SHCL_FMT_URI_LIST) + { + AssertFailed(); /** @todo */ + } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + SharedClipboardWinClose(); + } + + if (hClip == NULL) + { + /* Reply with empty data. */ + vboxClipboardSvcWinGetData(0, NULL, 0, pvData, cbData, pcbActual); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + LogFlowFuncEnter(); + + int rc = ShClSvcDataReadSignal(pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClSvcImplTransferCreate(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClient, pTransfer); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +int ShClSvcImplTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + LogFlowFuncEnter(); + + SharedClipboardWinTransferDestroy(&pClient->State.pCtx->Win, pTransfer); + + return VINF_SUCCESS; +} + +int ShClSvcImplTransferGetRoots(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + 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..936b4203 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp @@ -0,0 +1,129 @@ +/* $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-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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 ShClSvcImplInit(void) +{ + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/** Terminate the host side of the shared clipboard - called by the hgcm layer. */ +void ShClSvcImplDestroy(void) +{ + LogFlowFunc(("called, returning\n")); +} + +/** + * Enable the shared clipboard - called by the hgcm clipboard subsystem. + * + * @returns RT status code + * @param pClient Structure containing context information about the guest system + * @param fHeadless Whether headless. + */ +int ShClSvcImplConnect(PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(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 ShClSvcImplSync(PSHCLCLIENT pClient) +{ + RT_NOREF(pClient); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/** + * Shut down the shared clipboard subsystem and "disconnect" the guest. + * + * @param pClient Structure containing context information about the guest system + */ +int ShClSvcImplDisconnect(PSHCLCLIENT pClient) +{ + RT_NOREF(pClient); + return VINF_SUCCESS; +} + +/** + * The guest is taking possession of the shared clipboard. Called by the HGCM clipboard + * subsystem. + * + * @param pClient Context data for the guest system. + * @param fFormats Clipboard formats the guest is offering. + */ +int ShClSvcImplFormatAnnounce(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pClient, fFormats); + return VINF_SUCCESS; +} + +/** + * Called by the HGCM clipboard subsystem when the guest wants to read the host clipboard. + * + * @param pClient Context information about the guest VM + * @param pCmdCtx Command context to use. + * @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 store the actual amount of data available. + */ +int ShClSvcImplReadData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pClient, pCmdCtx, uFormat, pvData, cbData); + + /* No data available. */ + *pcbActual = 0; + + return VINF_SUCCESS; +} + +int ShClSvcImplWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(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..21dd3c65 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp @@ -0,0 +1,446 @@ +/* $Id: VBoxSharedClipboardSvc-x11.cpp $ */ +/** @file + * Shared Clipboard Service - Linux host. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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 <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <iprt/errcore.h> + +#include "VBoxSharedClipboardSvc-internal.h" + + +/********************************************************************************************************************************* +* 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; +}; + + +int ShClSvcImplInit(void) +{ + LogFlowFuncEnter(); + return VINF_SUCCESS; +} + +void ShClSvcImplDestroy(void) +{ + LogFlowFuncEnter(); +} + +/** + * @note On the host, we assume that some other application already owns + * the clipboard and leave ownership to X11. + */ +int ShClSvcImplConnect(PSHCLCLIENT pClient, bool fHeadless) +{ + int rc; + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = RTCritSectInit(&pCtx->CritSect); + if (RT_SUCCESS(rc)) + { + rc = ShClX11Init(&pCtx->X11, 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); + } + else + RTMemFree(pCtx); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplSync(PSHCLCLIENT pClient) +{ + 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. */ + return ShClSvcHostReportFormats(pClient, VBOX_SHCL_FMT_NONE); +} + +/** + * Shut down the shared clipboard service and "disconnect" the guest. + * @note Host glue code + */ +int ShClSvcImplDisconnect(PSHCLCLIENT pClient) +{ + 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); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplFormatAnnounce(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + 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 ShClSvcImplReadData(PSHCLCLIENT pClient, + PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, 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(pCmdCtx); + + LogFlowFunc(("pClient=%p, uFormat=%02X, pv=%p, cb=%u, pcbActual=%p\n", + pClient, uFormat, pvData, cbData, pcbActual)); + + int rc = VINF_SUCCESS; + + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->pv = pvData; + pReq->cb = cbData; + pReq->pcbActual = pcbActual; + const SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pClient->EventSrc); + pReq->idEvent = idEvent; + if (idEvent != NIL_SHCLEVENTID) + { + /* Note: ShClX11ReadDataFromX11() will consume pReq on success. */ + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, uFormat, pReq); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(&pClient->EventSrc, idEvent, 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; + } + } + + ShClEventUnregister(&pClient->EventSrc, idEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + if (RT_FAILURE(rc)) + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplWriteData(PSHCLCLIENT pClient, + PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + LogFlowFunc(("pClient=%p, pv=%p, cb=%RU32, uFormat=%02X\n", + pClient, pvData, cbData, uFormat)); + + int rc = ShClSvcDataReadSignal(pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports formats available in the X11 clipboard to VBox. + * + * @note Runs in Xt event thread. + * + * @param pCtx Opaque context pointer for the glue code. + * @param fFormats The formats available. + */ +DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats) +{ + LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); + + /** @todo r=bird: BUGBUG: Revisit this */ + if (fFormats == VBOX_SHCL_FMT_NONE) /* No formats to report? Bail out early. */ + return; + + int rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + RT_NOREF(rc); + + LogFlowFuncLeaveRC(rc); +} + +/** + * Completes a request from the host service for reading the X11 clipboard data. + * The data should be written to the buffer provided in the initial request. + * + * @note Runs in Xt event thread. + * + * @param pCtx Request context information. + * @param rcCompletion The completion status of the request. + * @param pReq Request to complete. Will be free'd by the callback. + * @param pv Address of data from completed request. Optional. + * @param cb Size (in bytes) of data from completed request. Optional. + * + * @todo Change this to deal with the buffer issues rather than offloading them onto the caller. + */ +DECLCALLBACK(void) ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx, int rcCompletion, + CLIPREADCBREQ *pReq, void *pv, uint32_t cb) +{ + AssertPtrReturnVoid(pCtx); + RT_NOREF(rcCompletion); + AssertPtrReturnVoid(pReq); + + LogFlowFunc(("rcCompletion=%Rrc, pReq=%p, pv=%p, cb=%RU32, idEvent=%RU32\n", rcCompletion, pReq, pv, cb, pReq->idEvent)); + + if (pReq->idEvent != NIL_SHCLEVENTID) + { + int rc2; + + PSHCLEVENTPAYLOAD pPayload = NULL; + if ( RT_SUCCESS(rcCompletion) + && pv + && cb) + { + rc2 = ShClPayloadAlloc(pReq->idEvent, pv, cb, &pPayload); + AssertRC(rc2); + } + + rc2 = RTCritSectEnter(&pCtx->pClient->CritSect); + if (RT_SUCCESS(rc2)) + { + ShClEventSignal(&pCtx->pClient->EventSrc, pReq->idEvent, pPayload); + /* Note: Skip checking if signalling the event is successful, as it could be gone already by now. */ + RTCritSectLeave(&pCtx->pClient->CritSect); + } + } + + if (pReq) + RTMemFree(pReq); +} + +/** + * Callback implementation for reading clipboard data from the guest. + * + * @note Runs in Xt event thread. + * + * @returns VBox status code. VERR_NO_DATA if no data available. + * @param pCtx Pointer to the host clipboard structure. + * @param fFormat The format in which the data should be transferred + * (VBOX_SHCL_FMT_XXX). + * @param ppv On success and if pcb > 0, this will point to a buffer + * to be freed with RTMemFree containing the data read. + * @param pcb On success, this contains the number of bytes of data returned. + */ +DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, SHCLFORMAT fFormat, void **ppv, uint32_t *pcb) +{ + LogFlowFunc(("pCtx=%p, Format=0x%x\n", pCtx, fFormat)); + + 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; + } + + int rc; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormat == VBOX_SHCL_FMT_URI_LIST) + rc = VINF_SUCCESS; + else +#endif + { + /* Request data from the guest. */ + SHCLEVENTID idEvent; + rc = ShClSvcDataReadRequest(pCtx->pClient, fFormat, &idEvent); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(&pCtx->pClient->EventSrc, idEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if ( !pPayload + || !pPayload->cbData) + { + rc = VERR_NO_DATA; + } + else + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + } + } + + ShClEventRelease(&pCtx->pClient->EventSrc, idEvent); + ShClEventUnregister(&pCtx->pClient->EventSrc, idEvent); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClSvcImplTransferCreate(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClient, pTransfer); + + int rc = VINF_SUCCESS; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pClient, pTransfer); + + int rc = VINF_SUCCESS; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcImplTransferGetRoots(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + LogFlowFuncEnter(); + + int rc; + + SHCLEVENTID idEvent = ShClEventIdGenerateAndRegister(&pClient->EventSrc); + if (idEvent != NIL_SHCLEVENTID) + { + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->idEvent = 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(&pClient->EventSrc, idEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootsSet(pTransfer, + (char *)pPayload->pvData, pPayload->cbData + 1 /* Include termination */); + } + } + } + else + rc = VERR_NO_MEMORY; + + ShClEventUnregister(&pClient->EventSrc, idEvent); + } + 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..dfec88d1 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -0,0 +1,2542 @@ +/* $Id: VBoxSharedClipboardSvc.cpp $ */ +/** @file + * Shared Clipboard Service - Host service entry points. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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_transfers_areas Clipboard areas. + * + * For larger / longer transfers there might be file data + * temporarily cached on the host, which has not been transferred to the + * destination yet. Such a cache is called a "Shared Clipboard Area", which + * in turn is identified by a unique ID across all VMs running on the same + * host. To control the access (and needed cleanup) of such clipboard areas, + * VBoxSVC (Main) is used for this task. A Shared Clipboard client can register, + * unregister, attach to and detach from a clipboard area. If all references + * to a clipboard area are released, a clipboard area gets detroyed automatically + * by VBoxSVC. + * + * By default a clipboard area lives in the user's temporary directory in the + * sub folder "VirtualBox Shared Clipboards/clipboard-<ID>". VBoxSVC does not + * do any file locking in a clipboard area, but keeps the clipboard areas's + * directory open to prevent deletion by third party processes. + * + * @todo We might use some VFS / container (IPRT?) for this instead of the + * host's file system directly? + * bird> Yes, but may take some work as we don't have the pick and choose + * kind of VFS containers implemented yet. + * + * @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. + * + * A Shared Clipboard transfer can maintain its own clipboard area; for the host + * service such a clipboard area is coupled to a clipboard area registered or + * attached with VBoxSVC. This is needed because multiple transfers from + * multiple VMs (n:n) can rely on the same clipboard area, so there needs a + * master keeping tracking of a clipboard area. To minimize IPC traffic only the + * minimum de/attaching is done at the moment. A clipboard area gets cleaned up + * (i.e. physically deleted) if no references are held to it anymore, or if + * VBoxSVC goes down. + * + * @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/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_VER_6_1RC1 +/** 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 * +*********************************************************************************************************************************/ +PVBOXHGCMSVCHELPERS g_pHelpers; + +static RTCRITSECT g_CritSect; +/** 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; +} + +/** + * 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; +} + +bool ShClSvcLock(void) +{ + return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); +} + +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); + } +} + +/** + * 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->cAllocatedMessages); + 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->cAllocatedMessages); + 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->cAllocatedMessages); + 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(RTCritSectIsOwned(&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(RTCritSectIsOwned(&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->cAllocatedMessages = 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->TransferCtx); +#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); + + 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(); +} + +/** + * 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; + Log(("[Client %RU32] features: %#RX64 %#RX64\n", pClient->State.uClientID, fFeatures0, fFeatures1)); + } + 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 pidEvent Event ID for waiting for new data. Optional. + * Must be released by the caller with ShClEventRelease() before unregistering then. + */ +int ShClSvcDataReadRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENTID pidEvent) +{ + LogFlowFuncEnter(); + if (pidEvent) + *pidEvent = NIL_SHCLEVENTID; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + int rc = VERR_NOT_SUPPORTED; + + SHCLEVENTID idEvent = NIL_SHCLEVENTID; + + 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; + + /* + * 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); + + idEvent = ShClEventIdGenerateAndRegister(&pClient->EventSrc); + if (idEvent != NIL_SHCLEVENTID) + { + LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, idEvent)); + + /* + * Format the message. + */ + if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) + HGCMSvcSetU64(&pMsg->aParms[0], + VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, idEvent)); + else + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); + HGCMSvcSetU32(&pMsg->aParms[1], fFormat); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = VINF_SUCCESS; + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + RTCritSectLeave(&pClient->CritSect); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + break; + } + + if (RT_SUCCESS(rc)) + { + RTCritSectEnter(&pClient->CritSect); + + /* Retain the last event generated (in case there were multiple clipboard formats) + * if we need to return the event ID to the caller. */ + if (pidEvent) + { + ShClEventRetain(&pClient->EventSrc, idEvent); + *pidEvent = idEvent; + } + + shClSvcClientWakeup(pClient); + + RTCritSectLeave(&pClient->CritSect); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClSvcDataReadSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + RT_NOREF(uFormat); + + LogFlowFuncEnter(); + + SHCLEVENTID idEvent; + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) /* Legacy, Guest Additions < 6.1. */ + { + /* Older Guest Additions (<= VBox 6.0) did not have any context ID handling, so we ASSUME that the last event registered + * is the one we want to handle (as this all was a synchronous protocol anyway). */ + idEvent = ShClEventGetLast(&pClient->EventSrc); + } + else + idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); + + int rc = VINF_SUCCESS; + + PSHCLEVENTPAYLOAD pPayload = NULL; + if (cbData) + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + + if (RT_SUCCESS(rc)) + { + RTCritSectEnter(&pClient->CritSect); + rc = ShClEventSignal(&pClient->EventSrc, idEvent, pPayload); + RTCritSectLeave(&pClient->CritSect); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports available VBox clipboard formats to the guest. + * + * @returns VBox status code. + * @param pClient Client to request to read data form. + * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero + * is okay (empty the clipboard). + */ +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + LogFlowFunc(("fFormats=%#x\n", fFormats)); + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + +#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)) + { + LogFlowFunc(("fFormats=%#x -> %#x\n", fFormats, fFormats & ~VBOX_SHCL_FMT_URI_LIST)); + fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + } +#endif + LogRel2(("Shared Clipboard: Reporting formats %#x to guest\n", fFormats)); + + /* + * 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; + + 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)) + { + 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 + rc = ShClSvcImplFormatAnnounce(pClient, fFormats); + } + } + + return rc; +} + +/** + * Handles the VBOX_SHCL_GUEST_FN_DATA_READ message from the guest. + */ +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; + } + + LogRel2(("Shared Clipboard: Guest wants to read %RU32 bytes host clipboard data in format %RU32\n", cbData, uFormat)); + + /* + * Do the reading. + */ + int rc; + uint32_t cbActual = 0; + + /* 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)); + LogRelFlowFunc(("Shared Clipboard: DATA/Ext: fDelayedAnnouncement=%RTbool fDelayedFormats=%#x cbData=%RU32->%RU32 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 = ShClSvcImplReadData(pClient, &cmdCtx, uFormat, pvData, cbData, &cbActual); + LogRelFlowFunc(("Shared Clipboard: DATA/Host: cbData=%RU32->%RU32 rc=%Rrc\n", cbData, cbActual, 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; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +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; + + /* + * 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(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + ? 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; + 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); + iParm++; + } + else + { + /** @todo supply CID from client state? Setting it in ShClSvcDataReadRequest? */ + } + 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); + + /* + * 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; + } + + LogRel2(("Shared Clipboard: Guest writes %RU32 bytes clipboard data in format %RU32 to host\n", cbData, uFormat)); + + /* + * Write the data to the active host side clipboard. + */ + int 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 + rc = ShClSvcImplWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); + + 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(void) +{ + int rc = RTCritSectInit(&g_CritSect); + + if (RT_SUCCESS(rc)) + { + shClSvcModeSet(VBOX_SHCL_MODE_OFF); + + rc = ShClSvcImplInit(); + + /* 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(); + + ShClSvcImplDestroy(); + + RTCritSectDelete(&g_CritSect); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect(void *, uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF(u32ClientID); + + LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + + ShClSvcImplDisconnect(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)) + { + rc = ShClSvcImplConnect(pClient, ShClSvcGetHeadless()); + if (RT_SUCCESS(rc)) + { + /* Sync the host clipboard content with the client. */ + rc = ShClSvcImplSync(pClient); + if (rc == VINF_NO_CHANGE) + { + /* + * The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older + * Guest Additions rely on the fact that only VINF_SUCCESS indicates a successful connect + * to the host service (instead of using RT_SUCCESS()). + * + * So implicitly set VINF_SUCCESS here to not break older Guest Additions. + */ + rc = VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + /* Assign weak pointer to client map .*/ + g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */ + + /* For now we ASSUME that the first client ever connected is in charge for + * communicating withe the service extension. + * + ** @todo This needs to be fixed ASAP w/o breaking older guest / host combos. */ + if (g_ExtState.uClientID == 0) + g_ExtState.uClientID = u32ClientID; + } + } + } + + 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 + LogFunc(("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: + LogFunc((" paParms[%RU32]: type uint32_t - value %RU32\n", i, paParms[i].u.uint32)); + break; + case VBOX_HGCM_SVC_PARM_64BIT: + LogFunc((" paParms[%RU32]: type uint64_t - value %RU64\n", i, paParms[i].u.uint64)); + break; + case VBOX_HGCM_SVC_PARM_PTR: + LogFunc((" 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: + LogFunc((" paParms[%RU32]: type pages - cb=%RU32, cPages=%RU16\n", + i, paParms[i].u.Pages.cb, paParms[i].u.Pages.cPages)); + break; + default: + AssertFailed(); + } + } + LogFunc(("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 from guest side: %Rrc\n", rcGuest)); + + /* Reset client state and start over. */ + shclSvcClientStateReset(&pClient->State); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + } + 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 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) +{ + 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. */ + SSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + + int rc = SSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); + AssertRCReturn(rc, rc); + + rc = SSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL); + AssertRCReturn(rc, rc); + + rc = SSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL); + AssertRCReturn(rc, rc); + + /* Serialize the client's internal message queue. */ + uint32_t cMsgs = 0; + PSHCLCLIENTMSG pMsg; + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + { + cMsgs += 1; + } + + rc = SSMR3PutU64(pSSM, cMsgs); + AssertRCReturn(rc, rc); + + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + { + SSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); + SSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); + + for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++) + HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM); + } + +#else /* UNIT_TEST */ + RT_NOREF3(u32ClientID, pvClient, pSSM); +#endif /* UNIT_TEST */ + return VINF_SUCCESS; +} + +#ifndef UNIT_TEST +static int svcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion) +{ + RT_NOREF(u32ClientID, pvClient, pSSM, uVersion); + + uint32_t uMarker; + int rc = SSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ + AssertRC(rc); + Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */ + AssertRCReturn(rc, rc); + + bool fValue; + rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgQuit */ + AssertRCReturn(rc, rc); + + rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgReadData */ + AssertRCReturn(rc, rc); + + rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgFormats */ + AssertRCReturn(rc, rc); + + uint32_t fFormats; + rc = SSMR3GetU32(pSSM, &fFormats); /* u32RequestedFormat */ + AssertRCReturn(rc, rc); + + rc = SSMR3GetU32(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, 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 = SSMR3GetU32(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, 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) + { + SSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, &s_aShClSSMClientState[0], NULL); + SSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, + &s_aShClSSMClientPODState[0], NULL); + } + else + SSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, &s_aShClSSMClientState61B1[0], NULL); + rc = SSMR3GetStructEx(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 = SSMR3GetU64(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; + + SSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); + rc = SSMR3GetStructEx(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); + AssertRCReturnStmt(rc, shClSvcMsgFree(pClient, pMsg), rc); + } + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + } + } + 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). */ + ShClSvcImplSync(pClient); + +#else /* UNIT_TEST */ + RT_NOREF(u32ClientID, pvClient, pSSM, 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 = ShClSvcDataReadRequest(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); + + 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)); + } + else + { + if (g_ExtState.pfnExtension) + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + + /* Uninstall extension. */ + g_ExtState.pfnExtension = NULL; + g_ExtState.pvExtension = NULL; + } + + return VINF_SUCCESS; +} + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pTable=%p\n", pTable)); + + if (!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); + + 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(); + } + } + + 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..0773920a --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc @@ -0,0 +1,51 @@ +/* $Id: VBoxSharedClipboardSvc.rc $ */ +/** @file + * Shared Clipboard Service - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..1c8b338f --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp @@ -0,0 +1,708 @@ +/* $Id: darwin-pasteboard.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Includes contributions from François Revol + * + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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..0ebd038b --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h @@ -0,0 +1,37 @@ +/* $Id: darwin-pasteboard.h $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef 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/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk new file mode 100644 index 00000000..f62ff560 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service testcases. +# + +# +# Copyright (C) 2011-2020 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + 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 + + 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 \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardArea.cpp + endif + + 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 + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp new file mode 100644 index 00000000..5f0d4af7 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -0,0 +1,324 @@ +/* $Id: tstClipboardServiceHost.cpp $ */ +/** @file + * Shared Clipboard host service test case. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 = ShClSvcDataReadRequest(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); + + rc = shClSvcClientInit(&g_Client, 1 /* clientId */); + RTTESTI_CHECK_RC_OK(rc); + + RTTestISub("Testing one format, waiting guest call."); + 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."); + 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."); + 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."); + 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 ShClSvcImplInit() { return VINF_SUCCESS; } +void ShClSvcImplDestroy() { } +int ShClSvcImplDisconnect(PSHCLCLIENT) { return VINF_SUCCESS; } +int ShClSvcImplConnect(PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClSvcImplFormatAnnounce(PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } +int ShClSvcImplReadData(PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +int ShClSvcImplWriteData(PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } +int ShClSvcImplSync(PSHCLCLIENT) { return VINF_SUCCESS; } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClSvcImplTransferCreate(PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClSvcImplTransferDestroy(PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClSvcImplTransferGetRoots(PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp new file mode 100644 index 00000000..9dba7bb6 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp @@ -0,0 +1,360 @@ +/* $Id: tstClipboardTransfers.cpp $ */ +/** @file + * Shared Clipboard transfers test case. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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, 0 /* ID */, 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 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); + + testTransferRootsSet(hTest); + testTransferObjOpen(hTest); + + int rc = testRemoveTempDir(hTest); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + |