/* $Id: VBoxSharedClipboardSvc.cpp 80990 2019-09-25 06:20:09Z vboxsync $ */ /** @file * Shared Clipboard Service - Host service entry points. */ /* * Copyright (C) 2006-2019 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 provides a proxy between the host's * clipboard and a similar proxy running on a guest. 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. * * @section sec_hostclip_guest_proto The guest communication protocol * * The guest clipboard service communicates with the host service via HGCM * (the host service runs as an HGCM service). The guest clipboard must * connect to the host service before all else (Windows hosts currently only * support one simultaneous connection). Once it has connected, it can send * HGCM messages to the host services, some of which will receive replies from * the host. The host can only reply to a guest message, it cannot initiate * any communication. The guest can in theory send any number of messages in * parallel (see the descriptions of the messages for the practice), and the * host will receive these in sequence, and may reply to them at once * (releasing the caller in the guest) or defer the reply until later. * * There are currently four messages defined. The first is * VBOX_SHCL_FN_GET_HOST_MSG, 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 VBOX_SHCL_FN_GET_HOST_MSG call from one guest. * * The second guest message is VBOX_SHCL_FN_FORMATS, which tells * the host that the guest has new clipboard data available. The third is * VBOX_SHCL_FN_READ_DATA, which asks the host to send its * clipboard data and waits until it arrives. The host supports at most one * simultaneous VBOX_SHCL_FN_READ_DATA 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_FN_WRITE_DATA, 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_GET_HOST_MSG_OLD 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 orfails 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 are not yet handled. * - 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-". 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? * * @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. * * An 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "VBoxSharedClipboardSvc-internal.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include "VBoxSharedClipboardSvc-transfers.h" #endif using namespace HGCM; /********************************************************************************************************************************* * Prototypes * *********************************************************************************************************************************/ static int sharedClipboardSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID); static int sharedClipboardSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState); static void sharedClipboardSvcClientStateReset(PSHCLCLIENTSTATE pClientState); /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ PVBOXHGCMSVCHELPERS g_pHelpers; static RTCRITSECT g_CritSect; static uint32_t g_uMode; /** 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; uint32_t sharedClipboardSvcGetMode(void) { return g_uMode; } #ifdef UNIT_TEST /** Testing interface, getter for clipboard mode */ uint32_t TestClipSvcGetMode(void) { return sharedClipboardSvcGetMode(); } #endif /** Getter for headless setting. Also needed by testcase. */ bool VBoxSvcClipboardGetHeadless(void) { return g_fHeadless; } static int sharedClipboardSvcModeSet(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 VBoxSvcClipboardLock(void) { return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); } void VBoxSvcClipboardUnlock(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. */ void sharedClipboardSvcMsgQueueReset(PSHCLCLIENT pClient) { LogFlowFuncEnter(); while (!pClient->queueMsg.isEmpty()) { RTMemFree(pClient->queueMsg.last()); pClient->queueMsg.removeLast(); } } /** * Allocates a new clipboard message. * * @returns Allocated clipboard message, or NULL on failure. * @param uMsg Message type of message to allocate. * @param cParms Number of HGCM parameters to allocate. */ PSHCLCLIENTMSG sharedClipboardSvcMsgAlloc(uint32_t uMsg, uint32_t cParms) { PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAlloc(sizeof(SHCLCLIENTMSG)); if (pMsg) { pMsg->paParms = (PVBOXHGCMSVCPARM)RTMemAllocZ(sizeof(VBOXHGCMSVCPARM) * cParms); if (pMsg->paParms) { pMsg->cParms = cParms; pMsg->uMsg = uMsg; return pMsg; } } RTMemFree(pMsg); return NULL; } /** * Frees a formerly allocated clipboard message. * * @param pMsg Clipboard message to free. * The pointer will be invalid after calling this function. */ void sharedClipboardSvcMsgFree(PSHCLCLIENTMSG pMsg) { if (!pMsg) return; if (pMsg->paParms) RTMemFree(pMsg->paParms); RTMemFree(pMsg); pMsg = NULL; } /** * 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. */ void sharedClipboardSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) { Assert(cDstParms >= 2); if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) paDstParms[0].u.uint32 = pMsg->uMsg; else paDstParms[0].u.uint64 = pMsg->uMsg; paDstParms[1].u.uint32 = pMsg->cParms; uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); while (i-- > 2) switch (pMsg->paParms[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->paParms[i - 2].u.pointer.size; break; } } /** * Sets the VBOX_SHCL_GUEST_FN_GET_HOST_MSG_OLD return parameters. * * This function does the necessary translation between the legacy protocol (v0) and the new protocols (>= v1), * as messages are always stored as >= v1 messages in the message queue. * * @returns VBox status code. * @param pMsg Message to set return parameters to. * @param paDstParms The peek parameter vector. * @param cDstParms The number of peek parameters (at least two). */ int sharedClipboardSvcMsgSetGetHostMsgOldReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) { AssertPtrReturn(pMsg, VERR_INVALID_POINTER); AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); AssertReturn (cDstParms >= 2, VERR_INVALID_PARAMETER); int rc = VINF_SUCCESS; switch (pMsg->uMsg) { case VBOX_SHCL_HOST_MSG_QUIT: { HGCMSvcSetU32(&paDstParms[0], VBOX_SHCL_HOST_MSG_QUIT); HGCMSvcSetU32(&paDstParms[1], 0 /* Not used */); break; } case VBOX_SHCL_HOST_MSG_READ_DATA: { HGCMSvcSetU32(&paDstParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); AssertBreakStmt(pMsg->cParms >= 2, rc = VERR_INVALID_PARAMETER); /* Paranoia. */ uint32_t uFmt; rc = HGCMSvcGetU32(&pMsg->paParms[1] /* uFormat */, &uFmt); if (RT_SUCCESS(rc)) HGCMSvcSetU32(&paDstParms[1], uFmt); break; } case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: { HGCMSvcSetU32(&paDstParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); AssertBreakStmt(pMsg->cParms >= 2, rc = VERR_INVALID_PARAMETER); /* Paranoia. */ uint32_t uFmts; rc = HGCMSvcGetU32(&pMsg->paParms[1] /* uFormats */, &uFmts); if (RT_SUCCESS(rc)) HGCMSvcSetU32(&paDstParms[1], uFmts); break; } default: AssertFailed(); /* Not supported by legacy protocol. */ rc = VERR_NOT_SUPPORTED; break; } LogFlowFuncLeaveRC(rc); return rc; } /** * Adds a new message to a client'S message queue. * * @returns IPRT status code. * @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. */ int sharedClipboardSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) { AssertPtrReturn(pMsg, VERR_INVALID_POINTER); LogFlowFunc(("uMsg=%RU32 (%s), cParms=%RU32, fAppend=%RTbool\n", pMsg->uMsg, VBoxShClHostMsgToStr(pMsg->uMsg), pMsg->cParms, fAppend)); if (fAppend) pClient->queueMsg.append(pMsg); else pClient->queueMsg.prepend(pMsg); /** @todo Catch / handle OOM? */ return VINF_SUCCESS; } /** * 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. */ int sharedClipboardSvcMsgPeek(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. */ if (!pClient->queueMsg.isEmpty()) { PSHCLCLIENTMSG pFirstMsg = pClient->queueMsg.first(); if (pFirstMsg) { sharedClipboardSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms); LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%u (%s), cParms=%u)\n", pClient->State.uClientID, pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), 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_GET_HOST_MSG_OLD. * * @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. */ int sharedClipboardSvcMsgGetOld(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) { int rc; if (cParms != VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* msg */ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT) /* formats */ { rc = VERR_INVALID_PARAMETER; } else { if (!pClient->queueMsg.isEmpty()) { PSHCLCLIENTMSG pFirstMsg = pClient->queueMsg.first(); AssertPtr(pFirstMsg); LogFlowFunc(("[Client %RU32] uMsg=%RU32 (%s), cParms=%RU32\n", pClient->State.uClientID, pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), pFirstMsg->cParms)); rc = sharedClipboardSvcMsgSetGetHostMsgOldReturn(pFirstMsg, paParms, cParms); if (RT_SUCCESS(rc)) { AssertPtr(g_pHelpers); rc = g_pHelpers->pfnCallComplete(hCall, rc); if (rc != VERR_CANCELLED) { pClient->queueMsg.removeFirst(); sharedClipboardSvcMsgFree(pFirstMsg); rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ } } } 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_GET_HOST_MSG_OLD; 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. */ int sharedClipboardSvcMsgGet(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. */ if (!pClient->queueMsg.isEmpty()) { PSHCLCLIENTMSG pFirstMsg = pClient->queueMsg.first(); if (pFirstMsg) { LogFlowFunc(("First message is: %RU32 (%s), cParms=%RU32\n", pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), pFirstMsg->cParms)); ASSERT_GUEST_MSG_RETURN(pFirstMsg->uMsg == idMsgExpected || idMsgExpected == UINT32_MAX, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), pFirstMsg->cParms, idMsgExpected, VBoxShClHostMsgToStr(idMsgExpected), cParms), VERR_MISMATCH); ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), pFirstMsg->cParms, idMsgExpected, VBoxShClHostMsgToStr(idMsgExpected), cParms), VERR_WRONG_PARAMETER_COUNT); /* Check the parameter types. */ for (uint32_t i = 0; i < cParms; i++) ASSERT_GUEST_MSG_RETURN(pFirstMsg->paParms[i].type == paParms[i].type, ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->paParms[i].type, paParms[i].type, pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg)), 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->paParms[i].type) { case VBOX_HGCM_SVC_PARM_32BIT: paParms[i].u.uint32 = pFirstMsg->paParms[i].u.uint32; break; case VBOX_HGCM_SVC_PARM_64BIT: paParms[i].u.uint64 = pFirstMsg->paParms[i].u.uint64; break; case VBOX_HGCM_SVC_PARM_PTR: { uint32_t const cbSrc = pFirstMsg->paParms[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->paParms[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->paParms[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) { pClient->queueMsg.removeFirst(); sharedClipboardSvcMsgFree(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; } /** * 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. */ int sharedClipboardSvcClientWakeup(PSHCLCLIENT pClient) { int rc = VINF_NO_CHANGE; if (pClient->Pending.uType) { LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); rc = VINF_SUCCESS; if (!pClient->queueMsg.isEmpty()) { PSHCLCLIENTMSG pFirstMsg = pClient->queueMsg.first(); if (pFirstMsg) { LogFunc(("[Client %RU32] Current host message is %RU32 (%s), cParms=%RU32\n", pClient->State.uClientID, pFirstMsg->uMsg, VBoxShClHostMsgToStr(pFirstMsg->uMsg), pFirstMsg->cParms)); bool fDonePending = false; if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) { sharedClipboardSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); fDonePending = true; } else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_GET_HOST_MSG_OLD) /* Legacy */ { rc = sharedClipboardSvcMsgSetGetHostMsgOldReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); if (RT_SUCCESS(rc)) { /* The old (legacy) protocol gets the message right when returning from peeking, so * remove the actual message from our queue right now. */ pClient->queueMsg.removeFirst(); sharedClipboardSvcMsgFree(pFirstMsg); fDonePending = true; } } if (fDonePending) { rc = g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); pClient->Pending.hHandle = NULL; pClient->Pending.paParms = NULL; pClient->Pending.cParms = 0; pClient->Pending.uType = 0; } } else AssertFailed(); } else AssertMsgFailed(("Waking up client ID=%RU32 with no host message in queue is a bad idea\n", pClient->State.uClientID)); return rc; } else LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); return VINF_NO_CHANGE; } /** * Requests to read clipboard data from the guest. * * @returns VBox status code. * @param pClient Client to request to read data form. * @param pDataReq Data request to send to the guest. * @param puEvent Event ID for waiting for new data. Optional. */ int sharedClipboardSvcDataReadRequest(PSHCLCLIENT pClient, PSHCLDATAREQ pDataReq, PSHCLEVENTID puEvent) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); AssertPtrReturn(pDataReq, VERR_INVALID_POINTER); /* puEvent is optional. */ int rc; PSHCLCLIENTMSG pMsgReadData = sharedClipboardSvcMsgAlloc(VBOX_SHCL_HOST_MSG_READ_DATA, VBOX_SHCL_CPARMS_READ_DATA); if (pMsgReadData) { const SHCLEVENTID uEvent = SharedClipboardEventIDGenerate(&pClient->Events); HGCMSvcSetU32(&pMsgReadData->paParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->Events.uID, uEvent)); HGCMSvcSetU32(&pMsgReadData->paParms[1], pDataReq->uFmt); HGCMSvcSetU32(&pMsgReadData->paParms[2], pClient->State.cbChunkSize); rc = sharedClipboardSvcMsgAdd(pClient, pMsgReadData, true /* fAppend */); if (RT_SUCCESS(rc)) { rc = SharedClipboardEventRegister(&pClient->Events, uEvent); if (RT_SUCCESS(rc)) { rc = sharedClipboardSvcClientWakeup(pClient); if (RT_SUCCESS(rc)) { if (puEvent) *puEvent = uEvent; } else SharedClipboardEventUnregister(&pClient->Events, uEvent); } } } else rc = VERR_NO_MEMORY; LogFlowFuncLeaveRC(rc); return rc; } int sharedClipboardSvcDataReadSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, PSHCLDATABLOCK pData) { SHCLEVENTID uEvent; if (pClient->State.uProtocolVer == 0) { /* Protocol v0 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). */ uEvent = SharedClipboardEventGetLast(&pClient->Events); } else uEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); int rc = VINF_SUCCESS; PSHCLEVENTPAYLOAD pPayload = NULL; if (pData->cbData) rc = SharedClipboardPayloadAlloc(uEvent, pData->pvData, pData->cbData, &pPayload); if (RT_SUCCESS(rc)) { rc = SharedClipboardEventSignal(&pClient->Events, uEvent, pPayload); if (RT_FAILURE(rc)) SharedClipboardPayloadFree(pPayload); } LogFlowFuncLeaveRC(rc); return rc; } int sharedClipboardSvcFormatsReport(PSHCLCLIENT pClient, PSHCLFORMATDATA pFormats) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); AssertPtrReturn(pFormats, VERR_INVALID_POINTER); int rc; PSHCLCLIENTMSG pMsg = sharedClipboardSvcMsgAlloc(VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 3); if (pMsg) { const SHCLEVENTID uEvent = SharedClipboardEventIDGenerate(&pClient->Events); HGCMSvcSetU32(&pMsg->paParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->Events.uID, uEvent)); HGCMSvcSetU32(&pMsg->paParms[1], pFormats->uFormats); HGCMSvcSetU32(&pMsg->paParms[2], 0 /* fFlags */); rc = sharedClipboardSvcMsgAdd(pClient, pMsg, true /* fAppend */); if (RT_SUCCESS(rc)) { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /* If this is an URI list, create a transfer locally and also tell the guest to create * a transfer on the guest side. */ if (pFormats->uFormats & VBOX_SHCL_FMT_URI_LIST) { rc = sharedClipboardSvcTransferStart(pClient, SHCLTRANSFERDIR_WRITE, SHCLSOURCE_LOCAL, NULL /* pTransfer */); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing host write transfer failed with %Rrc\n", rc)); } else { #endif rc = sharedClipboardSvcClientWakeup(pClient); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS } #endif } } else rc = VERR_NO_MEMORY; LogFlowFuncLeaveRC(rc); return rc; } int sharedClipboardSvcGetDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) { LogFlowFuncEnter(); if ( sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_GUEST_TO_HOST && sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_BIDIRECTIONAL) { return VERR_NOT_SUPPORTED; } int rc; SHCLDATABLOCK dataBlock; RT_ZERO(dataBlock); SHCLCLIENTCMDCTX cmdCtx; RT_ZERO(cmdCtx); if (pClient->State.uProtocolVer == 0) /* Legacy protocol */ { if (cParms < 2) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR) /* ptr */ { rc = VERR_INVALID_PARAMETER; } else { rc = HGCMSvcGetU32(&paParms[0], &dataBlock.uFormat); if (RT_SUCCESS(rc)) rc = HGCMSvcGetBuf(&paParms[1], &dataBlock.pvData, &dataBlock.cbData); } } else { if (cParms < VBOX_SHCL_CPARMS_WRITE_DATA) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* uContext */ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* uFormat */ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* cbData */ || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR) /* pvData */ { rc = VERR_INVALID_PARAMETER; } else { rc = HGCMSvcGetU32(&paParms[0], &cmdCtx.uContextID); if (RT_SUCCESS(rc)) rc = HGCMSvcGetU32(&paParms[1], &dataBlock.uFormat); if (RT_SUCCESS(rc)) rc = HGCMSvcGetBuf(&paParms[3], &dataBlock.pvData, &dataBlock.cbData); /** @todo Handle the rest. */ } } if (RT_SUCCESS(rc)) { if (g_ExtState.pfnExtension) { SHCLEXTPARMS parms; RT_ZERO(parms); parms.uFormat = dataBlock.uFormat; parms.u.pvData = dataBlock.pvData; parms.cbData = dataBlock.cbData; g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); } rc = SharedClipboardSvcImplWriteData(pClient, &cmdCtx, &dataBlock); } LogFlowFuncLeaveRC(rc); return rc; } int sharedClipboardSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource) { if (!pClient) /* If no client connected (anymore), bail out. */ return VINF_SUCCESS; int rc = VINF_SUCCESS; if (VBoxSvcClipboardLock()) { pClient->State.enmSource = enmSource; LogFlowFunc(("Source of client %RU32 is now %RU32\n", pClient->State.uClientID, pClient->State.enmSource)); VBoxSvcClipboardUnlock(); } LogFlowFuncLeaveRC(rc); return rc; } static int svcInit(void) { int rc = RTCritSectInit(&g_CritSect); if (RT_SUCCESS(rc)) { sharedClipboardSvcModeSet(VBOX_SHCL_MODE_OFF); rc = SharedClipboardSvcImplInit(); /* 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(); SharedClipboardSvcImplDestroy(); RTCritSectDelete(&g_CritSect); return VINF_SUCCESS; } static DECLCALLBACK(int) svcDisconnect(void *, uint32_t u32ClientID, void *pvClient) { RT_NOREF(u32ClientID, pvClient); LogFunc(("u32ClientID=%RU32\n", u32ClientID)); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS PSHCLTRANSFER pTransfer = SharedClipboardTransferCtxGetTransfer(&pClient->TransferCtx, 0 /* Index*/); if (pTransfer) sharedClipboardSvcTransferAreaDetach(&pClient->State, pTransfer); SharedClipboardTransferCtxDestroy(&pClient->TransferCtx); #endif SharedClipboardSvcImplDisconnect(pClient); sharedClipboardSvcClientStateReset(&pClient->State); sharedClipboardSvcClientStateDestroy(&pClient->State); SharedClipboardEventSourceDestroy(&pClient->Events); ClipboardClientMap::iterator itClient = g_mapClients.find(u32ClientID); if (itClient != g_mapClients.end()) { g_mapClients.erase(itClient); } else AssertFailed(); 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); /* Assign the client ID. */ pClient->State.uClientID = u32ClientID; /* Create the client's own event source. */ int rc = SharedClipboardEventSourceCreate(&pClient->Events, 0 /* ID, ignored */); if (RT_SUCCESS(rc)) { LogFlowFunc(("[Client %RU32] Using event source %RU32\n", u32ClientID, pClient->Events.uID)); /* Reset the client state. */ sharedClipboardSvcClientStateReset(&pClient->State); /* (Re-)initialize the client state. */ rc = sharedClipboardSvcClientStateInit(&pClient->State, u32ClientID); if (RT_SUCCESS(rc)) { rc = SharedClipboardSvcImplConnect(pClient, VBoxSvcClipboardGetHeadless()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (RT_SUCCESS(rc)) rc = SharedClipboardTransferCtxInit(&pClient->TransferCtx); #endif 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); int rc = VINF_SUCCESS; PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); LogFunc(("u32ClientID=%RU32 (proto %RU32), fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", u32ClientID, pClient->State.uProtocolVer, u32Function, VBoxShClGuestMsgToStr(u32Function), cParms, paParms)); #ifdef DEBUG uint32_t i; for (i = 0; i < cParms; i++) { /** @todo parameters other than 32 bit */ LogFunc((" paParms[%d]: type %RU32 - value %RU32\n", i, paParms[i].type, paParms[i].u.uint32)); } #endif switch (u32Function) { case VBOX_SHCL_GUEST_FN_GET_HOST_MSG_OLD: { rc = sharedClipboardSvcMsgGetOld(pClient, callHandle, cParms, paParms); break; } case VBOX_SHCL_GUEST_FN_CONNECT: { if (cParms != VBOX_SHCL_CPARMS_CONNECT) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* uProtocolVer */ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* uProtocolFlags */ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* cbChunkSize */ || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* enmCompression */ || paParms[4].type != VBOX_HGCM_SVC_PARM_32BIT) /* enmChecksumType */ { rc = VERR_INVALID_PARAMETER; } else if (sharedClipboardSvcGetMode() == VBOX_SHCL_MODE_OFF) { rc = VERR_ACCESS_DENIED; } else { /* Update the protocol version and tell the guest. */ pClient->State.uProtocolVer = 1; LogFlowFunc(("Now using protocol v%RU32\n", pClient->State.uProtocolVer)); HGCMSvcSetU32(&paParms[0], pClient->State.uProtocolVer); HGCMSvcSetU32(&paParms[1], 0 /* Procotol flags, not used yet */); HGCMSvcSetU32(&paParms[2], pClient->State.cbChunkSize); HGCMSvcSetU32(&paParms[3], 0 /* Compression type, not used yet */); HGCMSvcSetU32(&paParms[4], 0 /* Checksum type, not used yet */); rc = VINF_SUCCESS; } break; } case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT: { rc = sharedClipboardSvcMsgPeek(pClient, callHandle, cParms, paParms, false /*fWait*/); break; } case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT: { rc = sharedClipboardSvcMsgPeek(pClient, callHandle, cParms, paParms, true /*fWait*/); break; } case VBOX_SHCL_GUEST_FN_MSG_GET: { rc = sharedClipboardSvcMsgGet(pClient, callHandle, cParms, paParms); break; } case VBOX_SHCL_GUEST_FN_FORMATS_REPORT: { uint32_t uFormats = 0; if (pClient->State.uProtocolVer == 0) { if (cParms != 1) { rc = VERR_INVALID_PARAMETER; } else if (paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT) /* uFormats */ { rc = VERR_INVALID_PARAMETER; } else { rc = HGCMSvcGetU32(&paParms[0], &uFormats); } } else { if (cParms != 3) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* uContextID */ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* uFormats */ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT) /* fFlags */ { rc = VERR_INVALID_PARAMETER; } else { rc = HGCMSvcGetU32(&paParms[1], &uFormats); /** @todo Handle rest. */ } } if (RT_SUCCESS(rc)) { if ( sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_GUEST_TO_HOST && sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_BIDIRECTIONAL) { rc = VERR_ACCESS_DENIED; } else if (uFormats != VBOX_SHCL_FMT_NONE) /* Only announce formats if we actually *have* formats to announce! */ { rc = sharedClipboardSvcSetSource(pClient, SHCLSOURCE_REMOTE); if (RT_SUCCESS(rc)) { if (g_ExtState.pfnExtension) { SHCLEXTPARMS parms; RT_ZERO(parms); parms.uFormat = uFormats; g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof(parms)); } SHCLCLIENTCMDCTX cmdCtx; RT_ZERO(cmdCtx); SHCLFORMATDATA formatData; RT_ZERO(formatData); formatData.uFormats = uFormats; Assert(formatData.uFormats != VBOX_SHCL_FMT_NONE); /* Sanity. */ rc = SharedClipboardSvcImplFormatAnnounce(pClient, &cmdCtx, &formatData); } } } break; } case VBOX_SHCL_GUEST_FN_DATA_READ: { if (cParms != VBOX_SHCL_CPARMS_READ_DATA) { rc = VERR_INVALID_PARAMETER; } else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* ptr */ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* size */ ) { rc = VERR_INVALID_PARAMETER; } else { if ( sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_HOST_TO_GUEST && sharedClipboardSvcGetMode() != VBOX_SHCL_MODE_BIDIRECTIONAL) { rc = VERR_ACCESS_DENIED; break; } uint32_t uFormat; rc = HGCMSvcGetU32(&paParms[0], &uFormat); if (RT_SUCCESS(rc)) { void *pv; uint32_t cb; rc = HGCMSvcGetBuf(&paParms[1], &pv, &cb); if (RT_SUCCESS(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 = pv; parms.cbData = cb; 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)); LogFlowFunc(("g_ExtState.fDelayedAnnouncement=%RTbool, g_ExtState.uDelayedFormats=0x%x\n", g_ExtState.fDelayedAnnouncement, g_ExtState.uDelayedFormats)); /* Did the extension send the clipboard formats yet? * Otherwise, do this now. */ if (g_ExtState.fDelayedAnnouncement) { SHCLFORMATDATA formatData; RT_ZERO(formatData); formatData.uFormats = g_ExtState.uDelayedFormats; Assert(formatData.uFormats != VBOX_SHCL_FMT_NONE); /* There better is *any* format here now. */ int rc2 = sharedClipboardSvcFormatsReport(pClient, &formatData); AssertRC(rc2); g_ExtState.fDelayedAnnouncement = false; g_ExtState.uDelayedFormats = 0; } g_ExtState.fReadingData = false; if (RT_SUCCESS(rc)) { cbActual = parms.cbData; } } /* Note: The host clipboard *always* has precedence over the service extension above, * so data which has been read above might get overridden by the host clipboard eventually. */ SHCLCLIENTCMDCTX cmdCtx; RT_ZERO(cmdCtx); /* Release any other pending read, as we only * support one pending read at one time. */ if (RT_SUCCESS(rc)) { SHCLDATABLOCK dataBlock; RT_ZERO(dataBlock); dataBlock.pvData = pv; dataBlock.cbData = cb; dataBlock.uFormat = uFormat; rc = SharedClipboardSvcImplReadData(pClient, &cmdCtx, &dataBlock, &cbActual); if (RT_SUCCESS(rc)) HGCMSvcSetU32(&paParms[2], cbActual); } } } } break; } case VBOX_SHCL_GUEST_FN_DATA_WRITE: { rc = sharedClipboardSvcGetDataWrite(pClient, cParms, paParms); break; } default: { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS rc = sharedClipboardSvcTransferHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival); #else rc = VERR_NOT_IMPLEMENTED; #endif 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. */ static int sharedClipboardSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID) { LogFlowFuncEnter(); sharedClipboardSvcClientStateReset(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. */ static int sharedClipboardSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState) { RT_NOREF(pClientState); LogFlowFuncEnter(); return VINF_SUCCESS; } /** * Resets a Shared Clipboard service's client state. * * @param pClientState Client state to reset. */ static void sharedClipboardSvcClientStateReset(PSHCLCLIENTSTATE pClientState) { LogFlowFuncEnter(); pClientState->uProtocolVer = 0; pClientState->cbChunkSize = _64K; /** Make this configurable. */ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS pClientState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN; #else RT_NOREF(pClientState); #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, VBoxShClHostFunctionToStr(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 = sharedClipboardSvcModeSet(u32Mode); } break; } 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 = sharedClipboardSvcTransferHostHandler(u32Function, cParms, paParms); #else rc = VERR_NOT_IMPLEMENTED; #endif break; } } LogFlowFuncLeaveRC(rc); return rc; } #ifndef UNIT_TEST /** * SSM descriptor table for the SHCLCLIENTSTATE structure. */ static SSMFIELD const s_aShClSSMClientState[] = { SSMFIELD_ENTRY(SHCLCLIENTSTATE, uProtocolVer), SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize), SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource), SSMFIELD_ENTRY_TERM() }; /** * SSM descriptor table for the SHCLCLIENTURISTATE structure. */ static SSMFIELD const s_aShClSSMClientURIState[] = { 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, uMsg), SSMFIELD_ENTRY(SHCLCLIENTMSG, cParms), SSMFIELD_ENTRY_TERM() }; /** * SSM descriptor table for the VBOXSHCLMSGCTX structure. */ static SSMFIELD const s_aShClSSMClientMsgCtx[] = { SSMFIELD_ENTRY(SHCLMSGCTX, uContextID), SSMFIELD_ENTRY_TERM() }; #endif /* !UNIT_TEST */ static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM) { RT_NOREF(u32ClientID); #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. */ LogFunc(("u32ClientID=%RU32\n", u32ClientID)); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); /* Write Shared Clipboard saved state version. */ SSMR3PutU32(pSSM, VBOX_SHCL_SSM_VER_1); int rc = SSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); AssertRCReturn(rc, rc); rc = SSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientURIState[0], NULL); AssertRCReturn(rc, rc); /* Serialize the client's internal message queue. */ rc = SSMR3PutU64(pSSM, (uint64_t)pClient->queueMsg.size()); AssertRCReturn(rc, rc); for (size_t i = 0; i < pClient->queueMsg.size(); i++) { PSHCLCLIENTMSG pMsg = pClient->queueMsg.at(i); AssertPtr(pMsg); rc = SSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); AssertRCReturn(rc, rc); rc = SSMR3PutStructEx(pSSM, &pMsg->Ctx, sizeof(SHCLMSGCTX), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); AssertRCReturn(rc, rc); for (uint32_t p = 0; p < pMsg->cParms; p++) { rc = HGCMSvcSSMR3Put(&pMsg->paParms[p], pSSM); AssertRCReturn(rc, rc); } } #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) { #ifndef UNIT_TEST RT_NOREF(u32ClientID, uVersion); LogFunc(("u32ClientID=%RU32\n", u32ClientID)); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); /* Restore the client data. */ uint32_t lenOrVer; int rc = SSMR3GetU32(pSSM, &lenOrVer); AssertRCReturn(rc, rc); if (lenOrVer == VBOX_SHCL_SSM_VER_0) { return svcLoadStateV0(u32ClientID, pvClient, pSSM, uVersion); } else if (lenOrVer == VBOX_SHCL_SSM_VER_1) { rc = SSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); AssertRCReturn(rc, rc); rc = SSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientURIState[0], NULL); AssertRCReturn(rc, rc); /* Load the client's internal message queue. */ uint64_t cMsg; rc = SSMR3GetU64(pSSM, &cMsg); AssertRCReturn(rc, rc); for (uint64_t i = 0; i < cMsg; i++) { PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAlloc(sizeof(SHCLCLIENTMSG)); AssertPtrReturn(pMsg, VERR_NO_MEMORY); rc = SSMR3GetStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); AssertRCReturn(rc, rc); rc = SSMR3GetStructEx(pSSM, &pMsg->Ctx, sizeof(SHCLMSGCTX), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); AssertRCReturn(rc, rc); pMsg->paParms = (PVBOXHGCMSVCPARM)RTMemAllocZ(sizeof(VBOXHGCMSVCPARM) * pMsg->cParms); AssertPtrReturn(pMsg->paParms, VERR_NO_MEMORY); for (uint32_t p = 0; p < pMsg->cParms; p++) { rc = HGCMSvcSSMR3Get(&pMsg->paParms[p], pSSM); AssertRCReturn(rc, rc); } rc = sharedClipboardSvcMsgAdd(pClient, pMsg, true /* fAppend */); AssertRCReturn(rc, rc); } } else { LogRel(("Shared Clipboard: Unknown saved state version (%#x)\n", lenOrVer)); return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; } /* Actual host data are to be reported to guest (SYNC). */ SharedClipboardSvcImplSync(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) { g_ExtState.fDelayedAnnouncement = true; g_ExtState.uDelayedFormats = u32Format; } else if (u32Format != VBOX_SHCL_FMT_NONE) { SHCLFORMATDATA formatData; RT_ZERO(formatData); formatData.uFormats = u32Format; rc = sharedClipboardSvcFormatsReport(pClient, &formatData); } break; } /* The service extension wants read data from the guest. */ case VBOX_CLIPBOARD_EXT_FN_DATA_READ: { SHCLDATAREQ dataReq; RT_ZERO(dataReq); dataReq.uFmt = u32Format; dataReq.cbSize = _64K; /** @todo Make this more dynamic. */ rc = sharedClipboardSvcDataReadRequest(pClient, &dataReq, NULL /* puEvent */); 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 (!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_INVALID_PARAMETER; } 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(); } } return rc; }