VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp@ 86147

Last change on this file since 86147 was 86008, checked in by vboxsync, 4 years ago

Shared Clipboard/Host Service: The legacy Guest Additions context ID handling was missing the requested format. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 95.0 KB
Line 
1/* $Id: VBoxSharedClipboardSvc.cpp 86008 2020-09-02 17:49:55Z vboxsync $ */
2/** @file
3 * Shared Clipboard Service - Host service entry points.
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/** @page pg_hostclip The Shared Clipboard Host Service
20 *
21 * The shared clipboard host service is the host half of the clibpoard proxying
22 * between the host and the guest. The guest parts live in VBoxClient, VBoxTray
23 * and VBoxService depending on the OS, with code shared between host and guest
24 * under src/VBox/GuestHost/SharedClipboard/.
25 *
26 * The service is split into a platform-independent core and platform-specific
27 * backends. The service defines two communication protocols - one to
28 * communicate with the clipboard service running on the guest, and one to
29 * communicate with the backend. These will be described in a very skeletal
30 * fashion here.
31 *
32 * r=bird: The "two communication protocols" does not seems to be factual, there
33 * is only one protocol, the first one mentioned. It cannot be backend
34 * specific, because the guest/host protocol is platform and backend agnostic in
35 * nature. You may call it versions, but I take a great dislike to "protocol
36 * versions" here, as you've just extended the existing protocol with a feature
37 * that allows to transfer files and directories too. See @bugref{9437#c39}.
38 *
39 *
40 * @section sec_hostclip_guest_proto The guest communication protocol
41 *
42 * The guest clipboard service communicates with the host service over HGCM
43 * (the host is a HGCM service). HGCM is connection based, so the guest side
44 * has to connect before anything else can be done. (Windows hosts currently
45 * only support one simultaneous connection.) Once it has connected, it can
46 * send messages to the host services, some of which will receive immediate
47 * replies from the host, others which will block till a reply becomes
48 * available. The latter is because HGCM does't allow the host to initiate
49 * communication, it must be guest triggered. The HGCM service is single
50 * threaded, so it doesn't matter if the guest tries to send lots of requests in
51 * parallel, the service will process them one at the time.
52 *
53 * There are currently four messages defined. The first is
54 * VBOX_SHCL_GUEST_FN_MSG_GET / VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, which waits
55 * for a message from the host. If a host message is sent while the guest is
56 * not waiting, it will be queued until the guest requests it. The host code
57 * only supports a single simultaneous GET call from one client guest.
58 *
59 * The second guest message is VBOX_SHCL_GUEST_FN_REPORT_FORMATS, which tells
60 * the host that the guest has new clipboard data available. The third is
61 * VBOX_SHCL_GUEST_FN_DATA_READ, which asks the host to send its clipboard data
62 * and waits until it arrives. The host supports at most one simultaneous
63 * VBOX_SHCL_GUEST_FN_DATA_READ call from a guest - if a second call is made
64 * before the first has returned, the first will be aborted.
65 *
66 * The last guest message is VBOX_SHCL_GUEST_FN_DATA_WRITE, which is used to
67 * send the contents of the guest clipboard to the host. This call should be
68 * used after the host has requested data from the guest.
69 *
70 *
71 * @section sec_hostclip_backend_proto The communication protocol with the
72 * platform-specific backend
73 *
74 * The initial protocol implementation (called protocol v0) was very simple,
75 * and could only handle simple data (like copied text and so on). It also
76 * was limited to two (2) fixed parameters at all times.
77 *
78 * Since VBox 6.1 a newer protocol (v1) has been established to also support
79 * file transfers. This protocol uses a (per-client) message queue instead
80 * (see VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT vs. VBOX_SHCL_GUEST_FN_GET_HOST_MSG).
81 *
82 * To distinguish the old (legacy) or new(er) protocol, the VBOX_SHCL_GUEST_FN_CONNECT
83 * message has been introduced. If an older guest does not send this message,
84 * an appropriate translation will be done to serve older Guest Additions (< 6.1).
85 *
86 * The protocol also support out-of-order messages by using so-called "context IDs",
87 * which are generated by the host. A context ID consists of a so-called "source event ID"
88 * and a so-called "event ID". Each HGCM client has an own, random, source event ID and
89 * generates non-deterministic event IDs so that the guest side does not known what
90 * comes next; the guest side has to reply with the same conext ID which was sent by
91 * the host request.
92 *
93 * Also see the protocol changelog at VBoxShClSvc.h.
94 *
95 *
96 * @section sec_uri_intro Transferring files
97 *
98 * Since VBox x.x.x transferring files via Shared Clipboard is supported.
99 * See the VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS define for supported / enabled
100 * platforms. This is called "Shared Clipboard transfers".
101 *
102 * Copying files / directories from guest A to guest B requires the host
103 * service to act as a proxy and cache, as we don't allow direct VM-to-VM
104 * communication. Copying from / to the host also is taken into account.
105 *
106 * At the moment a transfer is a all-or-nothing operation, e.g. it either
107 * completes or fails completely. There might be callbacks in the future
108 * to e.g. skip failing entries.
109 *
110 * Known limitations:
111 *
112 * - Support for VRDE (VRDP) is not implemented yet (see #9498).
113 * - Unicode support on Windows hosts / guests is not enabled (yet).
114 * - Symbolic links / Windows junctions are not allowed.
115 * - Windows alternate data streams (ADS) are not allowed.
116 * - No support for ACLs yet.
117 * - No (maybe never) support for NT4.
118 *
119 * @section sec_transfers_areas Clipboard areas.
120 *
121 * For larger / longer transfers there might be file data
122 * temporarily cached on the host, which has not been transferred to the
123 * destination yet. Such a cache is called a "Shared Clipboard Area", which
124 * in turn is identified by a unique ID across all VMs running on the same
125 * host. To control the access (and needed cleanup) of such clipboard areas,
126 * VBoxSVC (Main) is used for this task. A Shared Clipboard client can register,
127 * unregister, attach to and detach from a clipboard area. If all references
128 * to a clipboard area are released, a clipboard area gets detroyed automatically
129 * by VBoxSVC.
130 *
131 * By default a clipboard area lives in the user's temporary directory in the
132 * sub folder "VirtualBox Shared Clipboards/clipboard-<ID>". VBoxSVC does not
133 * do any file locking in a clipboard area, but keeps the clipboard areas's
134 * directory open to prevent deletion by third party processes.
135 *
136 * @todo We might use some VFS / container (IPRT?) for this instead of the
137 * host's file system directly?
138 * bird> Yes, but may take some work as we don't have the pick and choose
139 * kind of VFS containers implemented yet.
140 *
141 * @section sec_transfer_structure Transfer handling structure
142 *
143 * All structures / classes are designed for running on both, on the guest
144 * (via VBoxTray / VBoxClient) or on the host (host service) to avoid code
145 * duplication where applicable.
146 *
147 * Per HGCM client there is a so-called "transfer context", which in turn can
148 * have one or mulitple so-called "Shared Clipboard transfer" objects. At the
149 * moment we only support on concurrent Shared Clipboard transfer per transfer
150 * context. It's being used for reading from a source or writing to destination,
151 * depening on its direction. An Shared Clipboard transfer can have optional
152 * callbacks which might be needed by various implementations. Also, transfers
153 * optionally can run in an asynchronous thread to prevent blocking the UI while
154 * running.
155 *
156 * A Shared Clipboard transfer can maintain its own clipboard area; for the host
157 * service such a clipboard area is coupled to a clipboard area registered or
158 * attached with VBoxSVC. This is needed because multiple transfers from
159 * multiple VMs (n:n) can rely on the same clipboard area, so there needs a
160 * master keeping tracking of a clipboard area. To minimize IPC traffic only the
161 * minimum de/attaching is done at the moment. A clipboard area gets cleaned up
162 * (i.e. physically deleted) if no references are held to it anymore, or if
163 * VBoxSVC goes down.
164 *
165 * @section sec_transfer_providers Transfer providers
166 *
167 * For certain implementations (for example on Windows guests / hosts, using
168 * IDataObject and IStream objects) a more flexible approach reqarding reading /
169 * writing is needed. For this so-called transfer providers abstract the way of how
170 * data is being read / written in the current context (host / guest), while
171 * the rest of the code stays the same.
172 *
173 * @section sec_transfer_protocol Transfer protocol
174 *
175 * The host service issues commands which the guest has to respond with an own
176 * message to. The protocol itself is designed so that it has primitives to list
177 * directories and open/close/read/write file system objects.
178 *
179 * Note that this is different from the DnD approach, as Shared Clipboard transfers
180 * need to be deeper integrated within the host / guest OS (i.e. for progress UI),
181 * and this might require non-monolithic / random access APIs to achieve.
182 *
183 * As there can be multiple file system objects (fs objects) selected for transfer,
184 * a transfer can be queried for its root entries, which then contains the top-level
185 * elements. Based on these elements, (a) (recursive) listing(s) can be performed
186 * to (partially) walk down into directories and query fs object information. The
187 * provider provides appropriate interface for this, even if not all implementations
188 * might need this mechanism.
189 *
190 * An Shared Clipboard transfer has three stages:
191 * - 1. Announcement: An Shared Clipboard transfer-compatible format (currently only one format available)
192 * has been announced, the destination side creates a transfer object, which then,
193 * depending on the actual implementation, can be used to tell the OS that
194 * there is transfer (file) data available.
195 * At this point this just acts as a (kind-of) promise to the OS that we
196 * can provide (file) data at some later point in time.
197 *
198 * - 2. Initialization: As soon as the OS requests the (file) data, mostly triggered
199 * by the user starting a paste operation (CTRL + V), the transfer get initialized
200 * on the destination side, which in turn lets the source know that a transfer
201 * is going to happen.
202 *
203 * - 3. Transfer: At this stage the actual transfer from source to the destination takes
204 * place. How the actual transfer is structurized (e.g. which files / directories
205 * are transferred in which order) depends on the destination implementation. This
206 * is necessary in order to fulfill requirements on the destination side with
207 * regards to ETA calculation or other dependencies.
208 * Both sides can abort or cancel the transfer at any time.
209 */
210
211
212/*********************************************************************************************************************************
213* Header Files *
214*********************************************************************************************************************************/
215#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
216#include <VBox/log.h>
217
218#include <VBox/GuestHost/clipboard-helper.h>
219#include <VBox/HostServices/Service.h>
220#include <VBox/HostServices/VBoxClipboardSvc.h>
221#include <VBox/HostServices/VBoxClipboardExt.h>
222
223#include <VBox/AssertGuest.h>
224#include <VBox/err.h>
225#include <VBox/VMMDev.h>
226#include <VBox/vmm/ssm.h>
227
228#include <iprt/mem.h>
229#include <iprt/string.h>
230#include <iprt/assert.h>
231#include <iprt/critsect.h>
232#include <iprt/rand.h>
233
234#include "VBoxSharedClipboardSvc-internal.h"
235#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
236# include "VBoxSharedClipboardSvc-transfers.h"
237#endif
238
239using namespace HGCM;
240
241
242/*********************************************************************************************************************************
243* Defined Constants And Macros *
244*********************************************************************************************************************************/
245/** @name The saved state versions for the shared clipboard service.
246 *
247 * @note We set bit 31 because prior to version 0x80000002 there would be a
248 * structure size rather than a version number. Setting bit 31 dispells
249 * any possible ambiguity.
250 *
251 * @{ */
252/** The current saved state version. */
253#define VBOX_SHCL_SAVED_STATE_VER_CURRENT VBOX_SHCL_SAVED_STATE_LEGACY_CID
254/** Adds the legacy context ID list. */
255#define VBOX_SHCL_SAVED_STATE_LEGACY_CID UINT32_C(0x80000005)
256/** Adds the client's POD state and client state flags.
257 * @since 6.1 RC1 */
258#define VBOX_SHCL_SAVED_STATE_VER_6_1RC1 UINT32_C(0x80000004)
259/** First attempt saving state during @bugref{9437} development.
260 * @since 6.1 BETA 2 */
261#define VBOX_SHCL_SAVED_STATE_VER_6_1B2 UINT32_C(0x80000003)
262/** First structured version.
263 * @since 3.1 / r53668 */
264#define VBOX_SHCL_SAVED_STATE_VER_3_1 UINT32_C(0x80000002)
265/** This was just a state memory dump, including pointers and everything.
266 * @note This is not supported any more. Sorry. */
267#define VBOX_SHCL_SAVED_STATE_VER_NOT_SUPP (ARCH_BITS == 64 ? UINT32_C(72) : UINT32_C(48))
268/** @} */
269
270
271/*********************************************************************************************************************************
272* Global Variables *
273*********************************************************************************************************************************/
274PVBOXHGCMSVCHELPERS g_pHelpers;
275
276static RTCRITSECT g_CritSect;
277/** Global Shared Clipboard mode. */
278static uint32_t g_uMode = VBOX_SHCL_MODE_OFF;
279#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
280/** Global Shared Clipboard (file) transfer mode. */
281uint32_t g_fTransferMode = VBOX_SHCL_TRANSFER_MODE_DISABLED;
282#endif
283
284/** Is the clipboard running in headless mode? */
285static bool g_fHeadless = false;
286
287/** Holds the service extension state. */
288SHCLEXTSTATE g_ExtState = { 0 };
289
290/** Global map of all connected clients. */
291ClipboardClientMap g_mapClients;
292
293/** Global list of all clients which are queued up (deferred return) and ready
294 * to process new commands. The key is the (unique) client ID. */
295ClipboardClientQueue g_listClientsDeferred;
296
297/** Host feature mask (VBOX_SHCL_HF_0_XXX) for VBOX_SHCL_GUEST_FN_REPORT_FEATURES
298 * and VBOX_SHCL_GUEST_FN_QUERY_FEATURES. */
299static uint64_t const g_fHostFeatures0 = VBOX_SHCL_HF_0_CONTEXT_ID
300#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
301 | VBOX_SHCL_HF_0_TRANSFERS
302#endif
303 ;
304
305
306/**
307 * Returns the current Shared Clipboard service mode.
308 *
309 * @returns Current Shared Clipboard service mode.
310 */
311uint32_t ShClSvcGetMode(void)
312{
313 return g_uMode;
314}
315
316/**
317 * Getter for headless setting. Also needed by testcase.
318 *
319 * @returns Whether service currently running in headless mode or not.
320 */
321bool ShClSvcGetHeadless(void)
322{
323 return g_fHeadless;
324}
325
326static int shClSvcModeSet(uint32_t uMode)
327{
328 int rc = VERR_NOT_SUPPORTED;
329
330 switch (uMode)
331 {
332 case VBOX_SHCL_MODE_OFF:
333 RT_FALL_THROUGH();
334 case VBOX_SHCL_MODE_HOST_TO_GUEST:
335 RT_FALL_THROUGH();
336 case VBOX_SHCL_MODE_GUEST_TO_HOST:
337 RT_FALL_THROUGH();
338 case VBOX_SHCL_MODE_BIDIRECTIONAL:
339 {
340 g_uMode = uMode;
341
342 rc = VINF_SUCCESS;
343 break;
344 }
345
346 default:
347 {
348 g_uMode = VBOX_SHCL_MODE_OFF;
349 break;
350 }
351 }
352
353 LogFlowFuncLeaveRC(rc);
354 return rc;
355}
356
357bool ShClSvcLock(void)
358{
359 return RT_SUCCESS(RTCritSectEnter(&g_CritSect));
360}
361
362void ShClSvcUnlock(void)
363{
364 int rc2 = RTCritSectLeave(&g_CritSect);
365 AssertRC(rc2);
366}
367
368/**
369 * Resets a client's state message queue.
370 *
371 * @param pClient Pointer to the client data structure to reset message queue for.
372 * @note Caller enters pClient->CritSect.
373 */
374void shClSvcMsgQueueReset(PSHCLCLIENT pClient)
375{
376 Assert(RTCritSectIsOwner(&pClient->CritSect));
377 LogFlowFuncEnter();
378
379 while (!RTListIsEmpty(&pClient->MsgQueue))
380 {
381 PSHCLCLIENTMSG pMsg = RTListRemoveFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry);
382 shClSvcMsgFree(pClient, pMsg);
383 }
384 pClient->cMsgAllocated = 0;
385
386 while (!RTListIsEmpty(&pClient->Legacy.lstCID))
387 {
388 PSHCLCLIENTLEGACYCID pCID = RTListRemoveFirst(&pClient->Legacy.lstCID, SHCLCLIENTLEGACYCID, Node);
389 RTMemFree(pCID);
390 }
391 pClient->Legacy.cCID = 0;
392}
393
394/**
395 * Allocates a new clipboard message.
396 *
397 * @returns Allocated clipboard message, or NULL on failure.
398 * @param pClient The client which is target of this message.
399 * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use
400 * @param cParms The number of parameters the message takes.
401 */
402PSHCLCLIENTMSG shClSvcMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms)
403{
404 RT_NOREF(pClient);
405 PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms]));
406 if (pMsg)
407 {
408 uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated);
409 if (cAllocated <= 4096)
410 {
411 RTListInit(&pMsg->ListEntry);
412 pMsg->cParms = cParms;
413 pMsg->idMsg = idMsg;
414 return pMsg;
415 }
416 AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated));
417 ASMAtomicDecU32(&pClient->cMsgAllocated);
418 RTMemFree(pMsg);
419 }
420 return NULL;
421}
422
423/**
424 * Frees a formerly allocated clipboard message.
425 *
426 * @param pClient The client which was the target of this message.
427 * @param pMsg Clipboard message to free.
428 */
429void shClSvcMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg)
430{
431 RT_NOREF(pClient);
432 /** @todo r=bird: Do accounting. */
433 if (pMsg)
434 {
435 pMsg->idMsg = UINT32_C(0xdeadface);
436 RTMemFree(pMsg);
437
438 uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated);
439 Assert(cAllocated < UINT32_MAX / 2);
440 RT_NOREF(cAllocated);
441 }
442}
443
444/**
445 * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT
446 * return parameters.
447 *
448 * @param pMsg Message to set return parameters to.
449 * @param paDstParms The peek parameter vector.
450 * @param cDstParms The number of peek parameters (at least two).
451 * @remarks ASSUMES the parameters has been cleared by clientMsgPeek.
452 */
453static void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms)
454{
455 Assert(cDstParms >= 2);
456 if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT)
457 paDstParms[0].u.uint32 = pMsg->idMsg;
458 else
459 paDstParms[0].u.uint64 = pMsg->idMsg;
460 paDstParms[1].u.uint32 = pMsg->cParms;
461
462 uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2);
463 while (i-- > 2)
464 switch (pMsg->aParms[i - 2].type)
465 {
466 case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break;
467 case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break;
468 case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break;
469 }
470}
471
472/**
473 * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters.
474 *
475 * @returns VBox status code.
476 * @param pMsg The message which parameters to return to the guest.
477 * @param paDstParms The peek parameter vector.
478 * @param cDstParms The number of peek parameters should be exactly two
479 */
480static int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms)
481{
482 /*
483 * Assert sanity.
484 */
485 AssertPtr(pMsg);
486 AssertPtrReturn(paDstParms, VERR_INVALID_POINTER);
487 AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER);
488
489 Assert(pMsg->cParms == 2);
490 Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg);
491 switch (pMsg->idMsg)
492 {
493 case VBOX_SHCL_HOST_MSG_READ_DATA:
494 case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
495 break;
496 default:
497 AssertFailed();
498 }
499
500 /*
501 * Set the parameters.
502 */
503 if (pMsg->cParms > 0)
504 paDstParms[0] = pMsg->aParms[0];
505 if (pMsg->cParms > 1)
506 paDstParms[1] = pMsg->aParms[1];
507 return VINF_SUCCESS;
508}
509
510/**
511 * Adds a new message to a client'S message queue.
512 *
513 * @param pClient Pointer to the client data structure to add new message to.
514 * @param pMsg Pointer to message to add. The queue then owns the pointer.
515 * @param fAppend Whether to append or prepend the message to the queue.
516 *
517 * @note Caller must enter critical section.
518 */
519void shClSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend)
520{
521 Assert(RTCritSectIsOwned(&pClient->CritSect));
522 AssertPtr(pMsg);
523
524 LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n",
525 ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend));
526
527 if (fAppend)
528 RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry);
529 else
530 RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry);
531}
532
533
534/**
535 * Appends a message to the client's queue and wake it up.
536 *
537 * @returns VBox status code, though the message is consumed regardless of what
538 * is returned.
539 * @param pClient The client to queue the message on.
540 * @param pMsg The message to queue. Ownership is always
541 * transfered to the queue.
542 *
543 * @note Caller must enter critical section.
544 */
545int shClSvcMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg)
546{
547 Assert(RTCritSectIsOwned(&pClient->CritSect));
548 AssertPtr(pMsg);
549 AssertPtr(pClient);
550 LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms));
551
552 RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry);
553 return shClSvcClientWakeup(pClient);
554}
555
556/**
557 * Initializes a Shared Clipboard client.
558 *
559 * @param pClient Client to initialize.
560 * @param uClientID HGCM client ID to assign client to.
561 */
562int shClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID)
563{
564 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
565
566 /* Assign the client ID. */
567 pClient->State.uClientID = uClientID;
568
569 RTListInit(&pClient->MsgQueue);
570 pClient->cMsgAllocated = 0;
571
572 RTListInit(&pClient->Legacy.lstCID);
573 pClient->Legacy.cCID = 0;
574
575 LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID));
576
577 int rc = RTCritSectInit(&pClient->CritSect);
578 if (RT_SUCCESS(rc))
579 {
580 /* Create the client's own event source. */
581 rc = ShClEventSourceCreate(&pClient->EventSrc, 0 /* ID, ignored */);
582 if (RT_SUCCESS(rc))
583 {
584 LogFlowFunc(("[Client %RU32] Using event source %RU32\n", uClientID, pClient->EventSrc.uID));
585
586 /* Reset the client state. */
587 shclSvcClientStateReset(&pClient->State);
588
589 /* (Re-)initialize the client state. */
590 rc = shClSvcClientStateInit(&pClient->State, uClientID);
591
592#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
593 if (RT_SUCCESS(rc))
594 rc = ShClTransferCtxInit(&pClient->TransferCtx);
595#endif
596 }
597 }
598
599 LogFlowFuncLeaveRC(rc);
600 return rc;
601}
602
603/**
604 * Destroys a Shared Clipboard client.
605 *
606 * @param pClient Client to destroy.
607 */
608void shClSvcClientDestroy(PSHCLCLIENT pClient)
609{
610 AssertPtrReturnVoid(pClient);
611
612 LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID));
613
614 /* Make sure to send a quit message to the guest so that it can terminate gracefully. */
615 RTCritSectEnter(&pClient->CritSect);
616 if (pClient->Pending.uType)
617 {
618 if (pClient->Pending.cParms > 1)
619 HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_QUIT);
620 if (pClient->Pending.cParms > 2)
621 HGCMSvcSetU32(&pClient->Pending.paParms[1], 0);
622 g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS);
623 pClient->Pending.uType = 0;
624 pClient->Pending.cParms = 0;
625 pClient->Pending.hHandle = NULL;
626 pClient->Pending.paParms = NULL;
627 }
628 RTCritSectLeave(&pClient->CritSect);
629
630 ShClEventSourceDestroy(&pClient->EventSrc);
631
632 shClSvcClientStateDestroy(&pClient->State);
633
634 int rc2 = RTCritSectDelete(&pClient->CritSect);
635 AssertRC(rc2);
636
637 ClipboardClientMap::iterator itClient = g_mapClients.find(pClient->State.uClientID);
638 if (itClient != g_mapClients.end())
639 {
640 g_mapClients.erase(itClient);
641 }
642 else
643 AssertFailed();
644
645 LogFlowFuncLeave();
646}
647
648/**
649 * Resets a Shared Clipboard client.
650 *
651 * @param pClient Client to reset.
652 */
653void shClSvcClientReset(PSHCLCLIENT pClient)
654{
655 if (!pClient)
656 return;
657
658 LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID));
659 RTCritSectEnter(&pClient->CritSect);
660
661 /* Reset message queue. */
662 shClSvcMsgQueueReset(pClient);
663
664 /* Reset event source. */
665 ShClEventSourceReset(&pClient->EventSrc);
666
667 /* Reset pending state. */
668 RT_ZERO(pClient->Pending);
669
670#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
671 shClSvcClientTransfersReset(pClient);
672#endif
673
674 shclSvcClientStateReset(&pClient->State);
675
676 RTCritSectLeave(&pClient->CritSect);
677}
678
679static int shClSvcClientNegogiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall,
680 uint32_t cParms, VBOXHGCMSVCPARM paParms[])
681{
682 /*
683 * Validate the request.
684 */
685 ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_NEGOTIATE_CHUNK_SIZE, VERR_WRONG_PARAMETER_COUNT);
686 ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
687 uint32_t const cbClientMaxChunkSize = paParms[0].u.uint32;
688 ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
689 uint32_t const cbClientChunkSize = paParms[1].u.uint32;
690
691 uint32_t const cbHostMaxChunkSize = VBOX_SHCL_MAX_CHUNK_SIZE; /** @todo Make this configurable. */
692
693 /*
694 * Do the work.
695 */
696 if (cbClientChunkSize == 0) /* Does the client want us to choose? */
697 {
698 paParms[0].u.uint32 = cbHostMaxChunkSize; /* Maximum */
699 paParms[1].u.uint32 = RT_MIN(pClient->State.cbChunkSize, cbHostMaxChunkSize); /* Preferred */
700
701 }
702 else /* The client told us what it supports, so update and report back. */
703 {
704 paParms[0].u.uint32 = RT_MIN(cbClientMaxChunkSize, cbHostMaxChunkSize); /* Maximum */
705 paParms[1].u.uint32 = RT_MIN(cbClientMaxChunkSize, pClient->State.cbChunkSize); /* Preferred */
706 }
707
708 int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
709 if (RT_SUCCESS(rc))
710 {
711 Log(("[Client %RU32] chunk size: %#RU32, max: %#RU32\n",
712 pClient->State.uClientID, paParms[1].u.uint32, paParms[0].u.uint32));
713 }
714 else
715 LogFunc(("pfnCallComplete -> %Rrc\n", rc));
716
717 return VINF_HGCM_ASYNC_EXECUTE;
718}
719
720/**
721 * Implements VBOX_SHCL_GUEST_FN_REPORT_FEATURES.
722 *
723 * @returns VBox status code.
724 * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here).
725 * @retval VERR_ACCESS_DENIED if not master
726 * @retval VERR_INVALID_PARAMETER if bit 63 in the 2nd parameter isn't set.
727 * @retval VERR_WRONG_PARAMETER_COUNT
728 *
729 * @param pClient The client state.
730 * @param hCall The client's call handle.
731 * @param cParms Number of parameters.
732 * @param paParms Array of parameters.
733 */
734static int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall,
735 uint32_t cParms, VBOXHGCMSVCPARM paParms[])
736{
737 /*
738 * Validate the request.
739 */
740 ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT);
741 ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
742 uint64_t const fFeatures0 = paParms[0].u.uint64;
743 ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
744 uint64_t const fFeatures1 = paParms[1].u.uint64;
745 ASSERT_GUEST_RETURN(fFeatures1 & VBOX_SHCL_GF_1_MUST_BE_ONE, VERR_INVALID_PARAMETER);
746
747 /*
748 * Do the work.
749 */
750 paParms[0].u.uint64 = g_fHostFeatures0;
751 paParms[1].u.uint64 = 0;
752
753 int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
754 if (RT_SUCCESS(rc))
755 {
756 pClient->State.fGuestFeatures0 = fFeatures0;
757 pClient->State.fGuestFeatures1 = fFeatures1;
758 Log(("[Client %RU32] features: %#RX64 %#RX64\n", pClient->State.uClientID, fFeatures0, fFeatures1));
759 }
760 else
761 LogFunc(("pfnCallComplete -> %Rrc\n", rc));
762
763 return VINF_HGCM_ASYNC_EXECUTE;
764}
765
766/**
767 * Implements VBOX_SHCL_GUEST_FN_QUERY_FEATURES.
768 *
769 * @returns VBox status code.
770 * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here).
771 * @retval VERR_WRONG_PARAMETER_COUNT
772 *
773 * @param hCall The client's call handle.
774 * @param cParms Number of parameters.
775 * @param paParms Array of parameters.
776 */
777static int shClSvcClientQueryFeatures(VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
778{
779 /*
780 * Validate the request.
781 */
782 ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT);
783 ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
784 ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
785 ASSERT_GUEST(paParms[1].u.uint64 & RT_BIT_64(63));
786
787 /*
788 * Do the work.
789 */
790 paParms[0].u.uint64 = g_fHostFeatures0;
791 paParms[1].u.uint64 = 0;
792 int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
793 if (RT_FAILURE(rc))
794 LogFunc(("pfnCallComplete -> %Rrc\n", rc));
795
796 return VINF_HGCM_ASYNC_EXECUTE;
797}
798
799/**
800 * Implements VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT.
801 *
802 * @returns VBox status code.
803 * @retval VINF_SUCCESS if a message was pending and is being returned.
804 * @retval VERR_TRY_AGAIN if no message pending and not blocking.
805 * @retval VERR_RESOURCE_BUSY if another read already made a waiting call.
806 * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending.
807 *
808 * @param pClient The client state.
809 * @param hCall The client's call handle.
810 * @param cParms Number of parameters.
811 * @param paParms Array of parameters.
812 * @param fWait Set if we should wait for a message, clear if to return
813 * immediately.
814 *
815 * @note Caller takes and leave the client's critical section.
816 */
817static int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait)
818{
819 /*
820 * Validate the request.
821 */
822 ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT);
823
824 uint64_t idRestoreCheck = 0;
825 uint32_t i = 0;
826 if (paParms[i].type == VBOX_HGCM_SVC_PARM_64BIT)
827 {
828 idRestoreCheck = paParms[0].u.uint64;
829 paParms[0].u.uint64 = 0;
830 i++;
831 }
832 for (; i < cParms; i++)
833 {
834 ASSERT_GUEST_MSG_RETURN(paParms[i].type == VBOX_HGCM_SVC_PARM_32BIT, ("#%u type=%u\n", i, paParms[i].type),
835 VERR_WRONG_PARAMETER_TYPE);
836 paParms[i].u.uint32 = 0;
837 }
838
839 /*
840 * Check restore session ID.
841 */
842 if (idRestoreCheck != 0)
843 {
844 uint64_t idRestore = g_pHelpers->pfnGetVMMDevSessionId(g_pHelpers);
845 if (idRestoreCheck != idRestore)
846 {
847 paParms[0].u.uint64 = idRestore;
848 LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VERR_VM_RESTORED (%#RX64 -> %#RX64)\n",
849 pClient->State.uClientID, idRestoreCheck, idRestore));
850 return VERR_VM_RESTORED;
851 }
852 Assert(!g_pHelpers->pfnIsCallRestored(hCall));
853 }
854
855 /*
856 * Return information about the first message if one is pending in the list.
857 */
858 PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry);
859 if (pFirstMsg)
860 {
861 shClSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms);
862 LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%s (%u), cParms=%u)\n",
863 pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms));
864 return VINF_SUCCESS;
865 }
866
867 /*
868 * If we cannot wait, fail the call.
869 */
870 if (!fWait)
871 {
872 LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT -> VERR_TRY_AGAIN\n", pClient->State.uClientID));
873 return VERR_TRY_AGAIN;
874 }
875
876 /*
877 * Wait for the host to queue a message for this client.
878 */
879 ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n",
880 pClient->State.uClientID), VERR_RESOURCE_BUSY);
881 pClient->Pending.hHandle = hCall;
882 pClient->Pending.cParms = cParms;
883 pClient->Pending.paParms = paParms;
884 pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT;
885 LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID));
886 return VINF_HGCM_ASYNC_EXECUTE;
887}
888
889/**
890 * Implements VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT.
891 *
892 * @returns VBox status code.
893 * @retval VINF_SUCCESS if a message was pending and is being returned.
894 * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending.
895 *
896 * @param pClient The client state.
897 * @param hCall The client's call handle.
898 * @param cParms Number of parameters.
899 * @param paParms Array of parameters.
900 *
901 * @note Caller takes and leave the client's critical section.
902 */
903static int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
904{
905 /*
906 * Validate input.
907 */
908 ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD, VERR_WRONG_PARAMETER_COUNT);
909 ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* id32Msg */
910 ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* f32Formats */
911
912 paParms[0].u.uint32 = 0;
913 paParms[1].u.uint32 = 0;
914
915 /*
916 * If there is a message pending we can return immediately.
917 */
918 int rc;
919 PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry);
920 if (pFirstMsg)
921 {
922 LogFlowFunc(("[Client %RU32] uMsg=%s (%RU32), cParms=%RU32\n", pClient->State.uClientID,
923 ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms));
924
925 rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms);
926 AssertPtr(g_pHelpers);
927 rc = g_pHelpers->pfnCallComplete(hCall, rc);
928 if (rc != VERR_CANCELLED)
929 {
930 RTListNodeRemove(&pFirstMsg->ListEntry);
931 shClSvcMsgFree(pClient, pFirstMsg);
932
933 rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
934 }
935 }
936 /*
937 * Otherwise we must wait.
938 */
939 else
940 {
941 ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n", pClient->State.uClientID),
942 VERR_RESOURCE_BUSY);
943
944 pClient->Pending.hHandle = hCall;
945 pClient->Pending.cParms = cParms;
946 pClient->Pending.paParms = paParms;
947 pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT;
948
949 rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
950
951 LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID));
952 }
953
954 LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc));
955 return rc;
956}
957
958/**
959 * Implements VBOX_SHCL_GUEST_FN_MSG_GET.
960 *
961 * @returns VBox status code.
962 * @retval VINF_SUCCESS if message retrieved and removed from the pending queue.
963 * @retval VERR_TRY_AGAIN if no message pending.
964 * @retval VERR_BUFFER_OVERFLOW if a parmeter buffer is too small. The buffer
965 * size was updated to reflect the required size, though this isn't yet
966 * forwarded to the guest. (The guest is better of using peek with
967 * parameter count + 2 parameters to get the sizes.)
968 * @retval VERR_MISMATCH if the incoming message ID does not match the pending.
969 * @retval VINF_HGCM_ASYNC_EXECUTE if message was completed already.
970 *
971 * @param pClient The client state.
972 * @param hCall The client's call handle.
973 * @param cParms Number of parameters.
974 * @param paParms Array of parameters.
975 *
976 * @note Called from within pClient->CritSect.
977 */
978static int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
979{
980 /*
981 * Validate the request.
982 */
983 uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32
984 : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64
985 : UINT32_MAX;
986
987 /*
988 * Return information about the first message if one is pending in the list.
989 */
990 PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry);
991 if (pFirstMsg)
992 {
993 LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms));
994
995 ASSERT_GUEST_MSG_RETURN(pFirstMsg->idMsg == idMsgExpected || idMsgExpected == UINT32_MAX,
996 ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n",
997 pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms,
998 idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms),
999 VERR_MISMATCH);
1000 ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms,
1001 ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n",
1002 pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms,
1003 idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms),
1004 VERR_WRONG_PARAMETER_COUNT);
1005
1006 /* Check the parameter types. */
1007 for (uint32_t i = 0; i < cParms; i++)
1008 ASSERT_GUEST_MSG_RETURN(pFirstMsg->aParms[i].type == paParms[i].type,
1009 ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->aParms[i].type,
1010 paParms[i].type, pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg)),
1011 VERR_WRONG_PARAMETER_TYPE);
1012 /*
1013 * Copy out the parameters.
1014 *
1015 * No assertions on buffer overflows, and keep going till the end so we can
1016 * communicate all the required buffer sizes.
1017 */
1018 int rc = VINF_SUCCESS;
1019 for (uint32_t i = 0; i < cParms; i++)
1020 switch (pFirstMsg->aParms[i].type)
1021 {
1022 case VBOX_HGCM_SVC_PARM_32BIT:
1023 paParms[i].u.uint32 = pFirstMsg->aParms[i].u.uint32;
1024 break;
1025
1026 case VBOX_HGCM_SVC_PARM_64BIT:
1027 paParms[i].u.uint64 = pFirstMsg->aParms[i].u.uint64;
1028 break;
1029
1030 case VBOX_HGCM_SVC_PARM_PTR:
1031 {
1032 uint32_t const cbSrc = pFirstMsg->aParms[i].u.pointer.size;
1033 uint32_t const cbDst = paParms[i].u.pointer.size;
1034 paParms[i].u.pointer.size = cbSrc; /** @todo Check if this is safe in other layers...
1035 * Update: Safe, yes, but VMMDevHGCM doesn't pass it along. */
1036 if (cbSrc <= cbDst)
1037 memcpy(paParms[i].u.pointer.addr, pFirstMsg->aParms[i].u.pointer.addr, cbSrc);
1038 else
1039 {
1040 AssertMsgFailed(("#%u: cbSrc=%RU32 is bigger than cbDst=%RU32\n", i, cbSrc, cbDst));
1041 rc = VERR_BUFFER_OVERFLOW;
1042 }
1043 break;
1044 }
1045
1046 default:
1047 AssertMsgFailed(("#%u: %u\n", i, pFirstMsg->aParms[i].type));
1048 rc = VERR_INTERNAL_ERROR;
1049 break;
1050 }
1051 if (RT_SUCCESS(rc))
1052 {
1053 /*
1054 * Complete the message and remove the pending message unless the
1055 * guest raced us and cancelled this call in the meantime.
1056 */
1057 AssertPtr(g_pHelpers);
1058 rc = g_pHelpers->pfnCallComplete(hCall, rc);
1059
1060 LogFlowFunc(("[Client %RU32] pfnCallComplete -> %Rrc\n", pClient->State.uClientID, rc));
1061
1062 if (rc != VERR_CANCELLED)
1063 {
1064 RTListNodeRemove(&pFirstMsg->ListEntry);
1065 shClSvcMsgFree(pClient, pFirstMsg);
1066 }
1067
1068 return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
1069 }
1070
1071 LogFlowFunc(("[Client %RU32] Returning %Rrc\n", pClient->State.uClientID, rc));
1072 return rc;
1073 }
1074
1075 paParms[0].u.uint32 = 0;
1076 paParms[1].u.uint32 = 0;
1077 LogFlowFunc(("[Client %RU32] -> VERR_TRY_AGAIN\n", pClient->State.uClientID));
1078 return VERR_TRY_AGAIN;
1079}
1080
1081/**
1082 * Implements VBOX_SHCL_GUEST_FN_MSG_GET.
1083 *
1084 * @returns VBox status code.
1085 * @retval VINF_SUCCESS if message retrieved and removed from the pending queue.
1086 * @retval VERR_TRY_AGAIN if no message pending.
1087 * @retval VERR_MISMATCH if the incoming message ID does not match the pending.
1088 * @retval VINF_HGCM_ASYNC_EXECUTE if message was completed already.
1089 *
1090 * @param pClient The client state.
1091 * @param cParms Number of parameters.
1092 *
1093 * @note Called from within pClient->CritSect.
1094 */
1095static int shClSvcClientMsgCancel(PSHCLCLIENT pClient, uint32_t cParms)
1096{
1097 /*
1098 * Validate the request.
1099 */
1100 ASSERT_GUEST_MSG_RETURN(cParms == 0, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT);
1101
1102 /*
1103 * Execute.
1104 */
1105 if (pClient->Pending.uType != 0)
1106 {
1107 LogFlowFunc(("[Client %RU32] Cancelling waiting thread, isPending=%d, pendingNumParms=%RU32, m_idSession=%x\n",
1108 pClient->State.uClientID, pClient->Pending.uType, pClient->Pending.cParms, pClient->State.uSessionID));
1109
1110 /*
1111 * The PEEK call is simple: At least two parameters, all set to zero before sleeping.
1112 */
1113 int rcComplete;
1114 if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT)
1115 {
1116 Assert(pClient->Pending.cParms >= 2);
1117 if (pClient->Pending.paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT)
1118 HGCMSvcSetU64(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED);
1119 else
1120 HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED);
1121 rcComplete = VINF_TRY_AGAIN;
1122 }
1123 /*
1124 * The MSG_OLD call is complicated, though we're
1125 * generally here to wake up someone who is peeking and have two parameters.
1126 * If there aren't two parameters, fail the call.
1127 */
1128 else
1129 {
1130 Assert(pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT);
1131 if (pClient->Pending.cParms > 0)
1132 HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED);
1133 if (pClient->Pending.cParms > 1)
1134 HGCMSvcSetU32(&pClient->Pending.paParms[1], 0);
1135 rcComplete = pClient->Pending.cParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN;
1136 }
1137
1138 g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete);
1139
1140 pClient->Pending.hHandle = NULL;
1141 pClient->Pending.paParms = NULL;
1142 pClient->Pending.cParms = 0;
1143 pClient->Pending.uType = 0;
1144 return VINF_SUCCESS;
1145 }
1146 return VWRN_NOT_FOUND;
1147}
1148
1149
1150/**
1151 * Wakes up a pending client (i.e. waiting for new messages).
1152 *
1153 * @returns VBox status code.
1154 * @retval VINF_NO_CHANGE if the client is not in pending mode.
1155 *
1156 * @param pClient Client to wake up.
1157 * @note Caller must enter pClient->CritSect.
1158 */
1159int shClSvcClientWakeup(PSHCLCLIENT pClient)
1160{
1161 Assert(RTCritSectIsOwner(&pClient->CritSect));
1162 int rc = VINF_NO_CHANGE;
1163
1164 if (pClient->Pending.uType != 0)
1165 {
1166 LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID));
1167
1168 PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry);
1169 AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR);
1170
1171 LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n",
1172 pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms));
1173
1174 if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT)
1175 shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms);
1176 else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */
1177 shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms);
1178 else
1179 AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3);
1180
1181 rc = g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS);
1182
1183 if ( rc != VERR_CANCELLED
1184 && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT)
1185 {
1186 RTListNodeRemove(&pFirstMsg->ListEntry);
1187 shClSvcMsgFree(pClient, pFirstMsg);
1188 }
1189
1190 pClient->Pending.hHandle = NULL;
1191 pClient->Pending.paParms = NULL;
1192 pClient->Pending.cParms = 0;
1193 pClient->Pending.uType = 0;
1194 }
1195 else
1196 LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID));
1197
1198 return rc;
1199}
1200
1201/**
1202 * Requests to read clipboard data from the guest.
1203 *
1204 * @returns VBox status code.
1205 * @param pClient Client to request to read data form.
1206 * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX).
1207 * @param pidEvent Event ID for waiting for new data. Optional.
1208 * Must be released by the caller with ShClEventRelease() before unregistering then.
1209 */
1210int ShClSvcGuestDataRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENTID pidEvent)
1211{
1212 LogFlowFuncEnter();
1213 if (pidEvent)
1214 *pidEvent = NIL_SHCLEVENTID;
1215 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
1216
1217 LogFlowFunc(("fFormats=%#x\n", fFormats));
1218
1219 int rc = VERR_NOT_SUPPORTED;
1220
1221 SHCLEVENTID idEvent = NIL_SHCLEVENTID;
1222
1223 /* Generate a separate message for every (valid) format we support. */
1224 while (fFormats)
1225 {
1226 /* Pick the next format to get from the mask: */
1227 /** @todo Make format reporting precedence configurable? */
1228 SHCLFORMAT fFormat;
1229 if (fFormats & VBOX_SHCL_FMT_UNICODETEXT)
1230 fFormat = VBOX_SHCL_FMT_UNICODETEXT;
1231 else if (fFormats & VBOX_SHCL_FMT_BITMAP)
1232 fFormat = VBOX_SHCL_FMT_BITMAP;
1233 else if (fFormats & VBOX_SHCL_FMT_HTML)
1234 fFormat = VBOX_SHCL_FMT_HTML;
1235 else
1236 AssertMsgFailedBreak(("%#x\n", fFormats));
1237
1238 /* Remove it from the mask. */
1239 fFormats &= ~fFormat;
1240
1241 /*
1242 * Allocate messages, one for each format.
1243 */
1244 PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient,
1245 pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID
1246 ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA,
1247 2);
1248 if (pMsg)
1249 {
1250 /*
1251 * Enter the critical section and generate an event.
1252 */
1253 RTCritSectEnter(&pClient->CritSect);
1254
1255 idEvent = ShClEventIdGenerateAndRegister(&pClient->EventSrc);
1256 if (idEvent != NIL_SHCLEVENTID)
1257 {
1258 LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, idEvent));
1259
1260 const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, idEvent);
1261
1262 rc = VINF_SUCCESS;
1263
1264 /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */
1265 if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID))
1266 {
1267 AssertStmt(pClient->Legacy.cCID < 4096, rc = VERR_TOO_MUCH_DATA);
1268 if (RT_SUCCESS(rc))
1269 {
1270 PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID));
1271 if (pCID)
1272 {
1273 pCID->uCID = uCID;
1274 pCID->enmType = 0; /* Not used yet. */
1275 pCID->uFormat = fFormat;
1276 RTListAppend(&pClient->Legacy.lstCID, &pCID->Node);
1277 pClient->Legacy.cCID++;
1278 }
1279 else
1280 rc = VERR_NO_MEMORY;
1281 }
1282 }
1283
1284 if (RT_SUCCESS(rc))
1285 {
1286 /*
1287 * Format the message.
1288 */
1289 if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID)
1290 HGCMSvcSetU64(&pMsg->aParms[0], uCID);
1291 else
1292 HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA);
1293 HGCMSvcSetU32(&pMsg->aParms[1], fFormat);
1294
1295 shClSvcMsgAdd(pClient, pMsg, true /* fAppend */);
1296 }
1297 }
1298 else
1299 rc = VERR_SHCLPB_MAX_EVENTS_REACHED;
1300
1301 RTCritSectLeave(&pClient->CritSect);
1302
1303 if (RT_FAILURE(rc))
1304 shClSvcMsgFree(pClient, pMsg);
1305 }
1306 else
1307 rc = VERR_NO_MEMORY;
1308
1309 if (RT_FAILURE(rc))
1310 break;
1311 }
1312
1313 if (RT_SUCCESS(rc))
1314 {
1315 RTCritSectEnter(&pClient->CritSect);
1316
1317 /* Retain the last event generated (in case there were multiple clipboard formats)
1318 * if we need to return the event ID to the caller. */
1319 if (pidEvent)
1320 {
1321 ShClEventRetain(&pClient->EventSrc, idEvent);
1322 *pidEvent = idEvent;
1323 }
1324
1325 shClSvcClientWakeup(pClient);
1326
1327 RTCritSectLeave(&pClient->CritSect);
1328 }
1329
1330 LogFlowFuncLeaveRC(rc);
1331 return rc;
1332}
1333
1334/**
1335 * Signals that clipboard data from the guest has been received.
1336 *
1337 * @returns VBox status code. Returns VERR_NOT_FOUND when related event ID was not found.
1338 * @param pClient Client the guest clipboard data was received for.
1339 * @param pCmdCtx Client command context to use.
1340 * @param uFormat Clipboard format of data received.
1341 * @param pvData Pointer to clipboard data received.
1342 * @param cbData Size (in bytes) of clipboard data received.
1343 */
1344int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx,
1345 SHCLFORMAT uFormat, void *pvData, uint32_t cbData)
1346{
1347 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
1348 AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER);
1349 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
1350
1351 RT_NOREF(uFormat);
1352
1353 LogFlowFuncEnter();
1354
1355 const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID);
1356
1357 AssertMsgReturn(idEvent != NIL_SHCLEVENTID,
1358 ("Event %RU64 empty within supplied context ID\n", idEvent), VERR_WRONG_ORDER);
1359#ifdef VBOX_STRICT
1360 AssertMsgReturn(ShClEventGet(&pClient->EventSrc, idEvent) != NULL,
1361 ("Event %RU64 not found, even if context ID was around\n", idEvent), VERR_NOT_FOUND);
1362#endif
1363
1364 int rc = VINF_SUCCESS;
1365
1366 PSHCLEVENTPAYLOAD pPayload = NULL;
1367 if (cbData)
1368 rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload);
1369
1370 if (RT_SUCCESS(rc))
1371 {
1372 RTCritSectEnter(&pClient->CritSect);
1373 rc = ShClEventSignal(&pClient->EventSrc, idEvent, pPayload);
1374 RTCritSectLeave(&pClient->CritSect);
1375 if (RT_FAILURE(rc))
1376 ShClPayloadFree(pPayload);
1377
1378 /* No one holding a reference to the event anymore? Unregister it. */
1379 if (ShClEventGetRefs(&pClient->EventSrc, idEvent) == 0)
1380 {
1381 int rc2 = ShClEventUnregister(&pClient->EventSrc, idEvent);
1382 if (RT_SUCCESS(rc))
1383 rc = rc2;
1384 }
1385 }
1386
1387 LogFlowFuncLeaveRC(rc);
1388 return rc;
1389}
1390
1391/**
1392 * Reports available VBox clipboard formats to the guest.
1393 *
1394 * @returns VBox status code.
1395 * @param pClient Client to report clipboard formats to.
1396 * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero
1397 * is okay (empty the clipboard).
1398 */
1399int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats)
1400{
1401 LogFlowFunc(("fFormats=%#x\n", fFormats));
1402 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
1403
1404#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1405 /*
1406 * If transfer mode is set to disabled, don't report the URI list format to the guest.
1407 */
1408 if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED))
1409 {
1410 LogFlowFunc(("fFormats=%#x -> %#x\n", fFormats, fFormats & ~VBOX_SHCL_FMT_URI_LIST));
1411 fFormats &= ~VBOX_SHCL_FMT_URI_LIST;
1412 }
1413#endif
1414 LogRel2(("Shared Clipboard: Reporting formats %#x to guest\n", fFormats));
1415
1416 /*
1417 * Allocate a message, populate parameters and post it to the client.
1418 */
1419 int rc;
1420 PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2);
1421 if (pMsg)
1422 {
1423 HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT);
1424 HGCMSvcSetU32(&pMsg->aParms[1], fFormats);
1425
1426 RTCritSectEnter(&pClient->CritSect);
1427 shClSvcMsgAddAndWakeupClient(pClient, pMsg);
1428 RTCritSectLeave(&pClient->CritSect);
1429
1430#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1431 /* If we announce an URI list, create a transfer locally and also tell the guest to create
1432 * a transfer on the guest side. */
1433 if (fFormats & VBOX_SHCL_FMT_URI_LIST)
1434 {
1435 rc = shClSvcTransferStart(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL,
1436 NULL /* pTransfer */);
1437 if (RT_SUCCESS(rc))
1438 rc = shClSvcSetSource(pClient, SHCLSOURCE_LOCAL);
1439
1440 if (RT_FAILURE(rc))
1441 LogRel(("Shared Clipboard: Initializing host write transfer failed with %Rrc\n", rc));
1442 }
1443 else
1444#endif
1445 {
1446 rc = VINF_SUCCESS;
1447 }
1448 }
1449 else
1450 rc = VERR_NO_MEMORY;
1451
1452 LogFlowFuncLeaveRC(rc);
1453 return rc;
1454}
1455
1456
1457/**
1458 * Handles the VBOX_SHCL_GUEST_FN_REPORT_FORMATS message from the guest.
1459 */
1460static int shClSvcClientReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
1461{
1462 /*
1463 * Check if the service mode allows this operation and whether the guest is
1464 * supposed to be reading from the host.
1465 */
1466 uint32_t uMode = ShClSvcGetMode();
1467 if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL
1468 || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST)
1469 { /* likely */ }
1470 else
1471 return VERR_ACCESS_DENIED;
1472
1473 /*
1474 * Digest parameters.
1475 */
1476 ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS
1477 || ( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B
1478 && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)),
1479 VERR_WRONG_PARAMETER_COUNT);
1480
1481 uintptr_t iParm = 0;
1482 if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B)
1483 {
1484 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
1485 /* no defined value, so just ignore it */
1486 iParm++;
1487 }
1488 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
1489 uint32_t const fFormats = paParms[iParm].u.uint32;
1490 iParm++;
1491 if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B)
1492 {
1493 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
1494 ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS);
1495 iParm++;
1496 }
1497 Assert(iParm == cParms);
1498
1499 /*
1500 * Report the formats.
1501 *
1502 * We ignore empty reports if the guest isn't the clipboard owner, this
1503 * prevents a freshly booted guest with an empty clibpoard from clearing
1504 * the host clipboard on startup. Likewise, when a guest shutdown it will
1505 * typically issue an empty report in case it's the owner, we don't want
1506 * that to clear host content either.
1507 */
1508 int rc;
1509 if (!fFormats && pClient->State.enmSource != SHCLSOURCE_REMOTE)
1510 rc = VINF_SUCCESS;
1511 else
1512 {
1513 rc = shClSvcSetSource(pClient, SHCLSOURCE_REMOTE);
1514 if (RT_SUCCESS(rc))
1515 {
1516 if (g_ExtState.pfnExtension)
1517 {
1518 SHCLEXTPARMS parms;
1519 RT_ZERO(parms);
1520 parms.uFormat = fFormats;
1521
1522 g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof(parms));
1523 }
1524 else
1525 rc = ShClBackendFormatAnnounce(pClient, fFormats);
1526 }
1527 }
1528
1529 return rc;
1530}
1531
1532/**
1533 * Handles the VBOX_SHCL_GUEST_FN_DATA_READ message from the guest.
1534 */
1535static int shClSvcClientReadData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
1536{
1537 LogFlowFuncEnter();
1538
1539 /*
1540 * Check if the service mode allows this operation and whether the guest is
1541 * supposed to be reading from the host.
1542 */
1543 uint32_t uMode = ShClSvcGetMode();
1544 if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL
1545 || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST)
1546 { /* likely */ }
1547 else
1548 return VERR_ACCESS_DENIED;
1549
1550 /*
1551 * Digest parameters.
1552 *
1553 * We are dragging some legacy here from the 6.1 dev cycle, a 5 parameter
1554 * variant which prepends a 64-bit context ID (RAZ as meaning not defined),
1555 * a 32-bit flag (MBZ, no defined meaning) and switches the last two parameters.
1556 */
1557 ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_DATA_READ
1558 || ( cParms == VBOX_SHCL_CPARMS_DATA_READ_61B
1559 && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)),
1560 VERR_WRONG_PARAMETER_COUNT);
1561
1562 uintptr_t iParm = 0;
1563 SHCLCLIENTCMDCTX cmdCtx;
1564 RT_ZERO(cmdCtx);
1565 if (cParms == VBOX_SHCL_CPARMS_DATA_READ_61B)
1566 {
1567 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
1568 /* This has no defined meaning and was never used, however the guest passed stuff, so ignore it and leave idContext=0. */
1569 iParm++;
1570 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
1571 ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS);
1572 iParm++;
1573 }
1574
1575 SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE;
1576 uint32_t cbData = 0;
1577 void *pvData = NULL;
1578
1579 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
1580 uFormat = paParms[iParm].u.uint32;
1581 iParm++;
1582 if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B)
1583 {
1584 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */
1585 pvData = paParms[iParm].u.pointer.addr;
1586 cbData = paParms[iParm].u.pointer.size;
1587 iParm++;
1588 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/
1589 iParm++;
1590 }
1591 else
1592 {
1593 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/
1594 iParm++;
1595 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */
1596 pvData = paParms[iParm].u.pointer.addr;
1597 cbData = paParms[iParm].u.pointer.size;
1598 iParm++;
1599 }
1600 Assert(iParm == cParms);
1601
1602 /*
1603 * For some reason we need to do this (makes absolutely no sense to bird).
1604 */
1605 /** @todo r=bird: I really don't get why you need the State.POD.uFormat
1606 * member. I'm sure there is a reason. Incomplete code? */
1607 if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID))
1608 {
1609 if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE)
1610 pClient->State.POD.uFormat = uFormat;
1611 }
1612
1613 LogRel2(("Shared Clipboard: Guest wants to read %RU32 bytes host clipboard data in format %RU32\n", cbData, uFormat));
1614
1615 /*
1616 * Do the reading.
1617 */
1618 int rc;
1619 uint32_t cbActual = 0;
1620
1621 /* If there is a service extension active, try reading data from it first. */
1622 if (g_ExtState.pfnExtension)
1623 {
1624 SHCLEXTPARMS parms;
1625 RT_ZERO(parms);
1626
1627 parms.uFormat = uFormat;
1628 parms.u.pvData = pvData;
1629 parms.cbData = cbData;
1630
1631 g_ExtState.fReadingData = true;
1632
1633 /* Read clipboard data from the extension. */
1634 rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms));
1635 LogRelFlowFunc(("Shared Clipboard: DATA/Ext: fDelayedAnnouncement=%RTbool fDelayedFormats=%#x cbData=%RU32->%RU32 rc=%Rrc\n",
1636 g_ExtState.fDelayedAnnouncement, g_ExtState.fDelayedFormats, cbData, parms.cbData, rc));
1637
1638 /* Did the extension send the clipboard formats yet?
1639 * Otherwise, do this now. */
1640 if (g_ExtState.fDelayedAnnouncement)
1641 {
1642 int rc2 = ShClSvcHostReportFormats(pClient, g_ExtState.fDelayedFormats);
1643 AssertRC(rc2);
1644
1645 g_ExtState.fDelayedAnnouncement = false;
1646 g_ExtState.fDelayedFormats = 0;
1647 }
1648
1649 g_ExtState.fReadingData = false;
1650
1651 if (RT_SUCCESS(rc))
1652 cbActual = parms.cbData;
1653 }
1654 else
1655 {
1656 rc = ShClBackendReadData(pClient, &cmdCtx, uFormat, pvData, cbData, &cbActual);
1657 LogRelFlowFunc(("Shared Clipboard: DATA/Host: cbData=%RU32->%RU32 rc=%Rrc\n", cbData, cbActual, rc));
1658 }
1659
1660 if (RT_SUCCESS(rc))
1661 {
1662 /* Return the actual size required to fullfil the request. */
1663 if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B)
1664 HGCMSvcSetU32(&paParms[2], cbActual);
1665 else
1666 HGCMSvcSetU32(&paParms[3], cbActual);
1667
1668 /* If the data to return exceeds the buffer the guest supplies, tell it (and let it try again). */
1669 if (cbActual >= cbData)
1670 rc = VINF_BUFFER_OVERFLOW;
1671 }
1672
1673 LogFlowFuncLeaveRC(rc);
1674 return rc;
1675}
1676
1677int shClSvcClientWriteData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
1678{
1679 LogFlowFuncEnter();
1680
1681 /*
1682 * Check if the service mode allows this operation and whether the guest is
1683 * supposed to be reading from the host.
1684 */
1685 uint32_t uMode = ShClSvcGetMode();
1686 if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL
1687 || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST)
1688 { /* likely */ }
1689 else
1690 return VERR_ACCESS_DENIED;
1691
1692 const bool fReportsContextID = RT_BOOL(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID);
1693
1694 /*
1695 * Digest parameters.
1696 *
1697 * There are 3 different format here, formatunately no parameters have been
1698 * switch around so it's plain sailing compared to the DATA_READ message.
1699 */
1700 ASSERT_GUEST_RETURN(fReportsContextID
1701 ? cParms == VBOX_SHCL_CPARMS_DATA_WRITE || cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B
1702 : cParms == VBOX_SHCL_CPARMS_DATA_WRITE_OLD,
1703 VERR_WRONG_PARAMETER_COUNT);
1704
1705 uintptr_t iParm = 0;
1706 SHCLCLIENTCMDCTX cmdCtx;
1707 RT_ZERO(cmdCtx);
1708 if (cParms > VBOX_SHCL_CPARMS_DATA_WRITE_OLD)
1709 {
1710 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE);
1711 cmdCtx.uContextID = paParms[iParm].u.uint64;
1712 iParm++;
1713 }
1714 else
1715 {
1716 /* Older Guest Additions (< 6.1) did not supply a context ID.
1717 * We dig it out from our saved context ID list then a bit down below. */
1718 }
1719
1720 if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B)
1721 {
1722 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
1723 ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS);
1724 iParm++;
1725 }
1726
1727 SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE;
1728 uint32_t cbData = 0;
1729 void *pvData = NULL;
1730
1731 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Format bit. */
1732 uFormat = paParms[iParm].u.uint32;
1733 iParm++;
1734 if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B)
1735 {
1736 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* "cbData" - duplicates buffer size. */
1737 iParm++;
1738 }
1739 ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */
1740 pvData = paParms[iParm].u.pointer.addr;
1741 cbData = paParms[iParm].u.pointer.size;
1742 iParm++;
1743 Assert(iParm == cParms);
1744
1745 /*
1746 * Handle / check context ID.
1747 */
1748 if (!fReportsContextID) /* Do we have to deal with old(er) GAs (< 6.1) which don't support context IDs? Dig out the context ID then. */
1749 {
1750 PSHCLCLIENTLEGACYCID pCID = NULL;
1751 PSHCLCLIENTLEGACYCID pCIDIter;
1752 RTListForEach(&pClient->Legacy.lstCID, pCIDIter, SHCLCLIENTLEGACYCID, Node) /* Slow, but does the job for now. */
1753 {
1754 if (pCIDIter->uFormat == uFormat)
1755 {
1756 pCID = pCIDIter;
1757 break;
1758 }
1759 }
1760
1761 ASSERT_GUEST_MSG_RETURN(pCID != NULL, ("Context ID for format %#x not found\n", uFormat), VERR_INVALID_CONTEXT);
1762 cmdCtx.uContextID = pCID->uCID;
1763
1764 /* Not needed anymore; clean up. */
1765 Assert(pClient->Legacy.cCID);
1766 pClient->Legacy.cCID--;
1767 RTListNodeRemove(&pCID->Node);
1768 RTMemFree(pCID);
1769 }
1770
1771 uint64_t const idCtxExpected = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID,
1772 VBOX_SHCL_CONTEXTID_GET_EVENT(cmdCtx.uContextID));
1773 ASSERT_GUEST_MSG_RETURN(cmdCtx.uContextID == idCtxExpected,
1774 ("Wrong context ID: %#RX64, expected %#RX64\n", cmdCtx.uContextID, idCtxExpected),
1775 VERR_INVALID_CONTEXT);
1776
1777 /*
1778 * For some reason we need to do this (makes absolutely no sense to bird).
1779 */
1780 /** @todo r=bird: I really don't get why you need the State.POD.uFormat
1781 * member. I'm sure there is a reason. Incomplete code? */
1782 if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID))
1783 {
1784 if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE)
1785 pClient->State.POD.uFormat = uFormat;
1786 }
1787
1788 LogRel2(("Shared Clipboard: Guest writes %RU32 bytes clipboard data in format %RU32 to host\n", cbData, uFormat));
1789
1790 /*
1791 * Write the data to the active host side clipboard.
1792 */
1793 int rc;
1794 if (g_ExtState.pfnExtension)
1795 {
1796 SHCLEXTPARMS parms;
1797 RT_ZERO(parms);
1798 parms.uFormat = uFormat;
1799 parms.u.pvData = pvData;
1800 parms.cbData = cbData;
1801
1802 g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms));
1803 rc = VINF_SUCCESS;
1804 }
1805 else
1806 {
1807 /* Let the backend implementation know. */
1808 rc = ShClBackendWriteData(pClient, &cmdCtx, uFormat, pvData, cbData);
1809
1810 int rc2; /* Don't return internals back to the guest. */
1811 rc2 = ShClSvcGuestDataSignal(pClient, &cmdCtx, uFormat, pvData, cbData); /* To complete pending events, if any. */
1812 AssertRC(rc2);
1813 }
1814
1815 LogFlowFuncLeaveRC(rc);
1816 return rc;
1817}
1818
1819/**
1820 * Gets an error from HGCM service parameters.
1821 *
1822 * @returns VBox status code.
1823 * @param cParms Number of HGCM parameters supplied in \a paParms.
1824 * @param paParms Array of HGCM parameters.
1825 * @param pRc Where to store the received error code.
1826 */
1827static int shClSvcClientError(uint32_t cParms, VBOXHGCMSVCPARM paParms[], int *pRc)
1828{
1829 AssertPtrReturn(paParms, VERR_INVALID_PARAMETER);
1830 AssertPtrReturn(pRc, VERR_INVALID_PARAMETER);
1831
1832 int rc;
1833
1834 if (cParms == VBOX_SHCL_CPARMS_ERROR)
1835 {
1836 rc = HGCMSvcGetU32(&paParms[1], (uint32_t *)pRc); /** @todo int vs. uint32_t !!! */
1837 }
1838 else
1839 rc = VERR_INVALID_PARAMETER;
1840
1841 LogFlowFuncLeaveRC(rc);
1842 return rc;
1843}
1844
1845/**
1846 * Sets the transfer source type of a Shared Clipboard client.
1847 *
1848 * @returns VBox status code.
1849 * @param pClient Client to set transfer source type for.
1850 * @param enmSource Source type to set.
1851 */
1852int shClSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource)
1853{
1854 if (!pClient) /* If no client connected (anymore), bail out. */
1855 return VINF_SUCCESS;
1856
1857 int rc = VINF_SUCCESS;
1858
1859 if (ShClSvcLock())
1860 {
1861 pClient->State.enmSource = enmSource;
1862
1863 LogFlowFunc(("Source of client %RU32 is now %RU32\n", pClient->State.uClientID, pClient->State.enmSource));
1864
1865 ShClSvcUnlock();
1866 }
1867
1868 LogFlowFuncLeaveRC(rc);
1869 return rc;
1870}
1871
1872static int svcInit(void)
1873{
1874 int rc = RTCritSectInit(&g_CritSect);
1875
1876 if (RT_SUCCESS(rc))
1877 {
1878 shClSvcModeSet(VBOX_SHCL_MODE_OFF);
1879
1880 rc = ShClBackendInit();
1881
1882 /* Clean up on failure, because 'svnUnload' will not be called
1883 * if the 'svcInit' returns an error.
1884 */
1885 if (RT_FAILURE(rc))
1886 {
1887 RTCritSectDelete(&g_CritSect);
1888 }
1889 }
1890
1891 return rc;
1892}
1893
1894static DECLCALLBACK(int) svcUnload(void *)
1895{
1896 LogFlowFuncEnter();
1897
1898 ShClBackendDestroy();
1899
1900 RTCritSectDelete(&g_CritSect);
1901
1902 return VINF_SUCCESS;
1903}
1904
1905static DECLCALLBACK(int) svcDisconnect(void *, uint32_t u32ClientID, void *pvClient)
1906{
1907 RT_NOREF(u32ClientID);
1908
1909 LogFunc(("u32ClientID=%RU32\n", u32ClientID));
1910
1911 PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient;
1912 AssertPtr(pClient);
1913
1914#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1915 shClSvcClientTransfersReset(pClient);
1916#endif
1917
1918 ShClBackendDisconnect(pClient);
1919
1920 shClSvcClientDestroy(pClient);
1921
1922 return VINF_SUCCESS;
1923}
1924
1925static DECLCALLBACK(int) svcConnect(void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring)
1926{
1927 RT_NOREF(fRequestor, fRestoring);
1928
1929 PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient;
1930 AssertPtr(pvClient);
1931
1932 int rc = shClSvcClientInit(pClient, u32ClientID);
1933 if (RT_SUCCESS(rc))
1934 {
1935 rc = ShClBackendConnect(pClient, ShClSvcGetHeadless());
1936 if (RT_SUCCESS(rc))
1937 {
1938 /* Sync the host clipboard content with the client. */
1939 rc = ShClBackendSync(pClient);
1940 if (rc == VINF_NO_CHANGE)
1941 {
1942 /*
1943 * The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older
1944 * Guest Additions rely on the fact that only VINF_SUCCESS indicates a successful connect
1945 * to the host service (instead of using RT_SUCCESS()).
1946 *
1947 * So implicitly set VINF_SUCCESS here to not break older Guest Additions.
1948 */
1949 rc = VINF_SUCCESS;
1950 }
1951
1952 if (RT_SUCCESS(rc))
1953 {
1954 /* Assign weak pointer to client map .*/
1955 g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */
1956
1957 /* For now we ASSUME that the first client ever connected is in charge for
1958 * communicating withe the service extension.
1959 *
1960 ** @todo This needs to be fixed ASAP w/o breaking older guest / host combos. */
1961 if (g_ExtState.uClientID == 0)
1962 g_ExtState.uClientID = u32ClientID;
1963 }
1964 }
1965 }
1966
1967 LogFlowFuncLeaveRC(rc);
1968 return rc;
1969}
1970
1971static DECLCALLBACK(void) svcCall(void *,
1972 VBOXHGCMCALLHANDLE callHandle,
1973 uint32_t u32ClientID,
1974 void *pvClient,
1975 uint32_t u32Function,
1976 uint32_t cParms,
1977 VBOXHGCMSVCPARM paParms[],
1978 uint64_t tsArrival)
1979{
1980 RT_NOREF(u32ClientID, pvClient, tsArrival);
1981 PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient;
1982 AssertPtr(pClient);
1983
1984#ifdef LOG_ENABLED
1985 LogFunc(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n",
1986 u32ClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, paParms));
1987 for (uint32_t i = 0; i < cParms; i++)
1988 {
1989 switch (paParms[i].type)
1990 {
1991 case VBOX_HGCM_SVC_PARM_32BIT:
1992 LogFunc((" paParms[%RU32]: type uint32_t - value %RU32\n", i, paParms[i].u.uint32));
1993 break;
1994 case VBOX_HGCM_SVC_PARM_64BIT:
1995 LogFunc((" paParms[%RU32]: type uint64_t - value %RU64\n", i, paParms[i].u.uint64));
1996 break;
1997 case VBOX_HGCM_SVC_PARM_PTR:
1998 LogFunc((" paParms[%RU32]: type ptr - value 0x%p (%RU32 bytes)\n",
1999 i, paParms[i].u.pointer.addr, paParms[i].u.pointer.size));
2000 break;
2001 case VBOX_HGCM_SVC_PARM_PAGES:
2002 LogFunc((" paParms[%RU32]: type pages - cb=%RU32, cPages=%RU16\n",
2003 i, paParms[i].u.Pages.cb, paParms[i].u.Pages.cPages));
2004 break;
2005 default:
2006 AssertFailed();
2007 }
2008 }
2009 LogFunc(("Client state: fFlags=0x%x, fGuestFeatures0=0x%x, fGuestFeatures1=0x%x\n",
2010 pClient->State.fFlags, pClient->State.fGuestFeatures0, pClient->State.fGuestFeatures1));
2011#endif
2012
2013 int rc;
2014 switch (u32Function)
2015 {
2016 case VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT:
2017 RTCritSectEnter(&pClient->CritSect);
2018 rc = shClSvcClientMsgOldGet(pClient, callHandle, cParms, paParms);
2019 RTCritSectLeave(&pClient->CritSect);
2020 break;
2021
2022 case VBOX_SHCL_GUEST_FN_CONNECT:
2023 LogRel(("Shared Clipboard: 6.1.0 beta or rc Guest Additions detected. Please upgrade!\n"));
2024 rc = VERR_NOT_IMPLEMENTED;
2025 break;
2026
2027 case VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE:
2028 rc = shClSvcClientNegogiateChunkSize(pClient, callHandle, cParms, paParms);
2029 break;
2030
2031 case VBOX_SHCL_GUEST_FN_REPORT_FEATURES:
2032 rc = shClSvcClientReportFeatures(pClient, callHandle, cParms, paParms);
2033 break;
2034
2035 case VBOX_SHCL_GUEST_FN_QUERY_FEATURES:
2036 rc = shClSvcClientQueryFeatures(callHandle, cParms, paParms);
2037 break;
2038
2039 case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT:
2040 RTCritSectEnter(&pClient->CritSect);
2041 rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, false /*fWait*/);
2042 RTCritSectLeave(&pClient->CritSect);
2043 break;
2044
2045 case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT:
2046 RTCritSectEnter(&pClient->CritSect);
2047 rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, true /*fWait*/);
2048 RTCritSectLeave(&pClient->CritSect);
2049 break;
2050
2051 case VBOX_SHCL_GUEST_FN_MSG_GET:
2052 RTCritSectEnter(&pClient->CritSect);
2053 rc = shClSvcClientMsgGet(pClient, callHandle, cParms, paParms);
2054 RTCritSectLeave(&pClient->CritSect);
2055 break;
2056
2057 case VBOX_SHCL_GUEST_FN_MSG_CANCEL:
2058 RTCritSectEnter(&pClient->CritSect);
2059 rc = shClSvcClientMsgCancel(pClient, cParms);
2060 RTCritSectLeave(&pClient->CritSect);
2061 break;
2062
2063 case VBOX_SHCL_GUEST_FN_REPORT_FORMATS:
2064 rc = shClSvcClientReportFormats(pClient, cParms, paParms);
2065 break;
2066
2067 case VBOX_SHCL_GUEST_FN_DATA_READ:
2068 rc = shClSvcClientReadData(pClient, cParms, paParms);
2069 break;
2070
2071 case VBOX_SHCL_GUEST_FN_DATA_WRITE:
2072 rc = shClSvcClientWriteData(pClient, cParms, paParms);
2073 break;
2074
2075 case VBOX_SHCL_GUEST_FN_ERROR:
2076 {
2077 int rcGuest;
2078 rc = shClSvcClientError(cParms,paParms, &rcGuest);
2079 if (RT_SUCCESS(rc))
2080 {
2081 LogRel(("Shared Clipboard: Error from guest side: %Rrc\n", rcGuest));
2082
2083 /* Reset client state and start over. */
2084 shclSvcClientStateReset(&pClient->State);
2085#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2086 shClSvcClientTransfersReset(pClient);
2087#endif
2088 }
2089 break;
2090 }
2091
2092 default:
2093 {
2094#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2095 if ( u32Function <= VBOX_SHCL_GUEST_FN_LAST
2096 && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID) )
2097 {
2098 if (g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED)
2099 rc = shClSvcTransferHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival);
2100 else
2101 {
2102 LogRel2(("Shared Clipboard: File transfers are disabled for this VM\n"));
2103 rc = VERR_ACCESS_DENIED;
2104 }
2105 }
2106 else
2107#endif
2108 {
2109 LogRel2(("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function));
2110 rc = VERR_NOT_IMPLEMENTED;
2111 }
2112 break;
2113 }
2114 }
2115
2116 LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc));
2117
2118 if (rc != VINF_HGCM_ASYNC_EXECUTE)
2119 g_pHelpers->pfnCallComplete(callHandle, rc);
2120}
2121
2122/**
2123 * Initializes a Shared Clipboard service's client state.
2124 *
2125 * @returns VBox status code.
2126 * @param pClientState Client state to initialize.
2127 * @param uClientID Client ID (HGCM) to use for this client state.
2128 */
2129int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID)
2130{
2131 LogFlowFuncEnter();
2132
2133 shclSvcClientStateReset(pClientState);
2134
2135 /* Register the client. */
2136 pClientState->uClientID = uClientID;
2137
2138 return VINF_SUCCESS;
2139}
2140
2141/**
2142 * Destroys a Shared Clipboard service's client state.
2143 *
2144 * @returns VBox status code.
2145 * @param pClientState Client state to destroy.
2146 */
2147int shClSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState)
2148{
2149 RT_NOREF(pClientState);
2150
2151 LogFlowFuncEnter();
2152
2153 return VINF_SUCCESS;
2154}
2155
2156/**
2157 * Resets a Shared Clipboard service's client state.
2158 *
2159 * @param pClientState Client state to reset.
2160 */
2161void shclSvcClientStateReset(PSHCLCLIENTSTATE pClientState)
2162{
2163 LogFlowFuncEnter();
2164
2165 pClientState->fGuestFeatures0 = VBOX_SHCL_GF_NONE;
2166 pClientState->fGuestFeatures1 = VBOX_SHCL_GF_NONE;
2167
2168 pClientState->cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
2169 pClientState->enmSource = SHCLSOURCE_INVALID;
2170 pClientState->fFlags = SHCLCLIENTSTATE_FLAGS_NONE;
2171
2172 pClientState->POD.enmDir = SHCLTRANSFERDIR_UNKNOWN;
2173 pClientState->POD.uFormat = VBOX_SHCL_FMT_NONE;
2174 pClientState->POD.cbToReadWriteTotal = 0;
2175 pClientState->POD.cbReadWritten = 0;
2176
2177#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2178 pClientState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN;
2179#endif
2180}
2181
2182/*
2183 * We differentiate between a function handler for the guest and one for the host.
2184 */
2185static DECLCALLBACK(int) svcHostCall(void *,
2186 uint32_t u32Function,
2187 uint32_t cParms,
2188 VBOXHGCMSVCPARM paParms[])
2189{
2190 int rc = VINF_SUCCESS;
2191
2192 LogFlowFunc(("u32Function=%RU32 (%s), cParms=%RU32, paParms=%p\n",
2193 u32Function, ShClHostFunctionToStr(u32Function), cParms, paParms));
2194
2195 switch (u32Function)
2196 {
2197 case VBOX_SHCL_HOST_FN_SET_MODE:
2198 {
2199 if (cParms != 1)
2200 {
2201 rc = VERR_INVALID_PARAMETER;
2202 }
2203 else
2204 {
2205 uint32_t u32Mode = VBOX_SHCL_MODE_OFF;
2206
2207 rc = HGCMSvcGetU32(&paParms[0], &u32Mode);
2208 if (RT_SUCCESS(rc))
2209 rc = shClSvcModeSet(u32Mode);
2210 }
2211
2212 break;
2213 }
2214
2215#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2216 case VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE:
2217 {
2218 if (cParms != 1)
2219 {
2220 rc = VERR_INVALID_PARAMETER;
2221 }
2222 else
2223 {
2224 uint32_t fTransferMode;
2225 rc = HGCMSvcGetU32(&paParms[0], &fTransferMode);
2226 if (RT_SUCCESS(rc))
2227 rc = shClSvcTransferModeSet(fTransferMode);
2228 }
2229 break;
2230 }
2231#endif
2232 case VBOX_SHCL_HOST_FN_SET_HEADLESS:
2233 {
2234 if (cParms != 1)
2235 {
2236 rc = VERR_INVALID_PARAMETER;
2237 }
2238 else
2239 {
2240 uint32_t uHeadless;
2241 rc = HGCMSvcGetU32(&paParms[0], &uHeadless);
2242 if (RT_SUCCESS(rc))
2243 {
2244 g_fHeadless = RT_BOOL(uHeadless);
2245 LogRel(("Shared Clipboard: Service running in %s mode\n", g_fHeadless ? "headless" : "normal"));
2246 }
2247 }
2248 break;
2249 }
2250
2251 default:
2252 {
2253#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2254 rc = shClSvcTransferHostHandler(u32Function, cParms, paParms);
2255#else
2256 rc = VERR_NOT_IMPLEMENTED;
2257#endif
2258 break;
2259 }
2260 }
2261
2262 LogFlowFuncLeaveRC(rc);
2263 return rc;
2264}
2265
2266#ifndef UNIT_TEST
2267
2268/**
2269 * SSM descriptor table for the SHCLCLIENTLEGACYCID structure.
2270 *
2271 * @note Saving the ListEntry attribute is not necessary, as this gets used on runtime only.
2272 */
2273static SSMFIELD const s_aShClSSMClientLegacyCID[] =
2274{
2275 SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uCID),
2276 SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, enmType),
2277 SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uFormat),
2278 SSMFIELD_ENTRY_TERM()
2279};
2280
2281/**
2282 * SSM descriptor table for the SHCLCLIENTSTATE structure.
2283 *
2284 * @note Saving the session ID not necessary, as they're not persistent across
2285 * state save/restore.
2286 */
2287static SSMFIELD const s_aShClSSMClientState[] =
2288{
2289 SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0),
2290 SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1),
2291 SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize),
2292 SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource),
2293 SSMFIELD_ENTRY(SHCLCLIENTSTATE, fFlags),
2294 SSMFIELD_ENTRY_TERM()
2295};
2296
2297/**
2298 * VBox 6.1 Beta 1 version of s_aShClSSMClientState (no flags).
2299 */
2300static SSMFIELD const s_aShClSSMClientState61B1[] =
2301{
2302 SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0),
2303 SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1),
2304 SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize),
2305 SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource),
2306 SSMFIELD_ENTRY_TERM()
2307};
2308
2309/**
2310 * SSM descriptor table for the SHCLCLIENTPODSTATE structure.
2311 */
2312static SSMFIELD const s_aShClSSMClientPODState[] =
2313{
2314 SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, enmDir),
2315 SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, uFormat),
2316 SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbToReadWriteTotal),
2317 SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbReadWritten),
2318 SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, tsLastReadWrittenMs),
2319 SSMFIELD_ENTRY_TERM()
2320};
2321
2322/**
2323 * SSM descriptor table for the SHCLCLIENTURISTATE structure.
2324 */
2325static SSMFIELD const s_aShClSSMClientTransferState[] =
2326{
2327 SSMFIELD_ENTRY(SHCLCLIENTTRANSFERSTATE, enmTransferDir),
2328 SSMFIELD_ENTRY_TERM()
2329};
2330
2331/**
2332 * SSM descriptor table for the header of the SHCLCLIENTMSG structure.
2333 * The actual message parameters will be serialized separately.
2334 */
2335static SSMFIELD const s_aShClSSMClientMsgHdr[] =
2336{
2337 SSMFIELD_ENTRY(SHCLCLIENTMSG, idMsg),
2338 SSMFIELD_ENTRY(SHCLCLIENTMSG, cParms),
2339 SSMFIELD_ENTRY_TERM()
2340};
2341
2342/**
2343 * SSM descriptor table for what used to be the VBOXSHCLMSGCTX structure but is
2344 * now part of SHCLCLIENTMSG.
2345 */
2346static SSMFIELD const s_aShClSSMClientMsgCtx[] =
2347{
2348 SSMFIELD_ENTRY(SHCLCLIENTMSG, idCtx),
2349 SSMFIELD_ENTRY_TERM()
2350};
2351#endif /* !UNIT_TEST */
2352
2353static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM)
2354{
2355 LogFlowFuncEnter();
2356
2357#ifndef UNIT_TEST
2358 /*
2359 * When the state will be restored, pending requests will be reissued
2360 * by VMMDev. The service therefore must save state as if there were no
2361 * pending request.
2362 * Pending requests, if any, will be completed in svcDisconnect.
2363 */
2364 RT_NOREF(u32ClientID);
2365 LogFunc(("u32ClientID=%RU32\n", u32ClientID));
2366
2367 PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient;
2368 AssertPtr(pClient);
2369
2370 /* Write Shared Clipboard saved state version. */
2371 SSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT);
2372
2373 int rc = SSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL);
2374 AssertRCReturn(rc, rc);
2375
2376 rc = SSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL);
2377 AssertRCReturn(rc, rc);
2378
2379 rc = SSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL);
2380 AssertRCReturn(rc, rc);
2381
2382 /* Serialize the client's internal message queue. */
2383 rc = SSMR3PutU64(pSSM, pClient->cMsgAllocated);
2384 AssertRCReturn(rc, rc);
2385
2386 PSHCLCLIENTMSG pMsg;
2387 RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry)
2388 {
2389 SSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL);
2390 SSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL);
2391
2392 for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++)
2393 HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM);
2394 }
2395
2396 rc = SSMR3PutU64(pSSM, pClient->Legacy.cCID);
2397 AssertRCReturn(rc, rc);
2398
2399 PSHCLCLIENTLEGACYCID pCID;
2400 RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node)
2401 {
2402 rc = SSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, &s_aShClSSMClientLegacyCID[0], NULL);
2403 AssertRCReturn(rc, rc);
2404 }
2405#else /* UNIT_TEST */
2406 RT_NOREF3(u32ClientID, pvClient, pSSM);
2407#endif /* UNIT_TEST */
2408 return VINF_SUCCESS;
2409}
2410
2411#ifndef UNIT_TEST
2412static int svcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion)
2413{
2414 RT_NOREF(u32ClientID, pvClient, pSSM, uVersion);
2415
2416 uint32_t uMarker;
2417 int rc = SSMR3GetU32(pSSM, &uMarker); /* Begin marker. */
2418 AssertRC(rc);
2419 Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */);
2420
2421 rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */
2422 AssertRCReturn(rc, rc);
2423
2424 bool fValue;
2425 rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgQuit */
2426 AssertRCReturn(rc, rc);
2427
2428 rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgReadData */
2429 AssertRCReturn(rc, rc);
2430
2431 rc = SSMR3GetBool(pSSM, &fValue); /* fHostMsgFormats */
2432 AssertRCReturn(rc, rc);
2433
2434 uint32_t fFormats;
2435 rc = SSMR3GetU32(pSSM, &fFormats); /* u32RequestedFormat */
2436 AssertRCReturn(rc, rc);
2437
2438 rc = SSMR3GetU32(pSSM, &uMarker); /* End marker. */
2439 AssertRCReturn(rc, rc);
2440 Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */);
2441
2442 return VINF_SUCCESS;
2443}
2444#endif /* UNIT_TEST */
2445
2446static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion)
2447{
2448 LogFlowFuncEnter();
2449
2450#ifndef UNIT_TEST
2451
2452 RT_NOREF(u32ClientID, uVersion);
2453
2454 PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient;
2455 AssertPtr(pClient);
2456
2457 /* Restore the client data. */
2458 uint32_t lenOrVer;
2459 int rc = SSMR3GetU32(pSSM, &lenOrVer);
2460 AssertRCReturn(rc, rc);
2461
2462 LogFunc(("u32ClientID=%RU32, lenOrVer=%#RX64\n", u32ClientID, lenOrVer));
2463
2464 if (lenOrVer == VBOX_SHCL_SAVED_STATE_VER_3_1)
2465 return svcLoadStateV0(u32ClientID, pvClient, pSSM, uVersion);
2466
2467 if ( lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1B2
2468 && lenOrVer <= VBOX_SHCL_SAVED_STATE_VER_CURRENT)
2469 {
2470 if (lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1RC1)
2471 {
2472 SSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, &s_aShClSSMClientState[0], NULL);
2473 SSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */,
2474 &s_aShClSSMClientPODState[0], NULL);
2475 }
2476 else
2477 SSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, &s_aShClSSMClientState61B1[0], NULL);
2478 rc = SSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /* fFlags */,
2479 &s_aShClSSMClientTransferState[0], NULL);
2480 AssertRCReturn(rc, rc);
2481
2482 /* Load the client's internal message queue. */
2483 uint64_t cMsgs;
2484 rc = SSMR3GetU64(pSSM, &cMsgs);
2485 AssertRCReturn(rc, rc);
2486 AssertLogRelMsgReturn(cMsgs < _16K, ("Too many messages: %u (%x)\n", cMsgs, cMsgs), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
2487
2488 for (uint64_t i = 0; i < cMsgs; i++)
2489 {
2490 union
2491 {
2492 SHCLCLIENTMSG Msg;
2493 uint8_t abPadding[RT_UOFFSETOF(SHCLCLIENTMSG, aParms) + sizeof(VBOXHGCMSVCPARM) * 2];
2494 } u;
2495
2496 SSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL);
2497 rc = SSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL);
2498 AssertRCReturn(rc, rc);
2499
2500 AssertLogRelMsgReturn(u.Msg.cParms <= VMMDEV_MAX_HGCM_PARMS,
2501 ("Too many HGCM message parameters: %u (%#x)\n", u.Msg.cParms, u.Msg.cParms),
2502 VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
2503
2504 PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, u.Msg.idMsg, u.Msg.cParms);
2505 AssertReturn(pMsg, VERR_NO_MEMORY);
2506 pMsg->idCtx = u.Msg.idCtx;
2507
2508 for (uint32_t p = 0; p < pMsg->cParms; p++)
2509 {
2510 rc = HGCMSvcSSMR3Get(&pMsg->aParms[p], pSSM);
2511 AssertRCReturnStmt(rc, shClSvcMsgFree(pClient, pMsg), rc);
2512 }
2513
2514 shClSvcMsgAdd(pClient, pMsg, true /* fAppend */);
2515 }
2516
2517 if (lenOrVer >= VBOX_SHCL_SAVED_STATE_LEGACY_CID)
2518 {
2519 uint64_t cCID;
2520 rc = SSMR3GetU64(pSSM, &cCID);
2521 AssertRCReturn(rc, rc);
2522 AssertLogRelMsgReturn(cCID < _16K, ("Too many context IDs: %u (%x)\n", cCID, cCID), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
2523
2524 for (uint64_t i = 0; i < cCID; i++)
2525 {
2526 PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID));
2527 AssertPtrReturn(pCID, VERR_NO_MEMORY);
2528
2529 SSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, &s_aShClSSMClientLegacyCID[0], NULL);
2530 RTListAppend(&pClient->Legacy.lstCID, &pCID->Node);
2531 }
2532 }
2533 }
2534 else
2535 {
2536 LogRel(("Shared Clipboard: Unsupported saved state version (%#x)\n", lenOrVer));
2537 return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
2538 }
2539
2540 /* Actual host data are to be reported to guest (SYNC). */
2541 ShClBackendSync(pClient);
2542
2543#else /* UNIT_TEST */
2544 RT_NOREF(u32ClientID, pvClient, pSSM, uVersion);
2545#endif /* UNIT_TEST */
2546 return VINF_SUCCESS;
2547}
2548
2549static DECLCALLBACK(int) extCallback(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData)
2550{
2551 RT_NOREF(pvData, cbData);
2552
2553 LogFlowFunc(("u32Function=%RU32\n", u32Function));
2554
2555 int rc = VINF_SUCCESS;
2556
2557 /* Figure out if the client in charge for the service extension still is connected. */
2558 ClipboardClientMap::const_iterator itClient = g_mapClients.find(g_ExtState.uClientID);
2559 if (itClient != g_mapClients.end())
2560 {
2561 PSHCLCLIENT pClient = itClient->second;
2562 AssertPtr(pClient);
2563
2564 switch (u32Function)
2565 {
2566 /* The service extension announces formats to the guest. */
2567 case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE:
2568 {
2569 LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: g_ExtState.fReadingData=%RTbool\n", g_ExtState.fReadingData));
2570 if (!g_ExtState.fReadingData)
2571 rc = ShClSvcHostReportFormats(pClient, u32Format);
2572 else
2573 {
2574 g_ExtState.fDelayedAnnouncement = true;
2575 g_ExtState.fDelayedFormats = u32Format;
2576 rc = VINF_SUCCESS;
2577 }
2578 break;
2579 }
2580
2581 /* The service extension wants read data from the guest. */
2582 case VBOX_CLIPBOARD_EXT_FN_DATA_READ:
2583 rc = ShClSvcGuestDataRequest(pClient, u32Format, NULL /* pidEvent */);
2584 break;
2585
2586 default:
2587 /* Just skip other messages. */
2588 break;
2589 }
2590 }
2591 else
2592 rc = VERR_NOT_FOUND;
2593
2594 LogFlowFuncLeaveRC(rc);
2595 return rc;
2596}
2597
2598static DECLCALLBACK(int) svcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension)
2599{
2600 LogFlowFunc(("pfnExtension=%p\n", pfnExtension));
2601
2602 SHCLEXTPARMS parms;
2603 RT_ZERO(parms);
2604
2605 if (pfnExtension)
2606 {
2607 /* Install extension. */
2608 g_ExtState.pfnExtension = pfnExtension;
2609 g_ExtState.pvExtension = pvExtension;
2610
2611 parms.u.pfnCallback = extCallback;
2612
2613 g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms));
2614 }
2615 else
2616 {
2617 if (g_ExtState.pfnExtension)
2618 g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms));
2619
2620 /* Uninstall extension. */
2621 g_ExtState.pfnExtension = NULL;
2622 g_ExtState.pvExtension = NULL;
2623 }
2624
2625 return VINF_SUCCESS;
2626}
2627
2628extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable)
2629{
2630 int rc = VINF_SUCCESS;
2631
2632 LogFlowFunc(("pTable=%p\n", pTable));
2633
2634 if (!VALID_PTR(pTable))
2635 {
2636 rc = VERR_INVALID_PARAMETER;
2637 }
2638 else
2639 {
2640 LogFunc(("pTable->cbSize = %d, ptable->u32Version = 0x%08X\n", pTable->cbSize, pTable->u32Version));
2641
2642 if ( pTable->cbSize != sizeof (VBOXHGCMSVCFNTABLE)
2643 || pTable->u32Version != VBOX_HGCM_SVC_VERSION)
2644 {
2645 rc = VERR_VERSION_MISMATCH;
2646 }
2647 else
2648 {
2649 g_pHelpers = pTable->pHelpers;
2650
2651 pTable->cbClient = sizeof(SHCLCLIENT);
2652
2653 pTable->pfnUnload = svcUnload;
2654 pTable->pfnConnect = svcConnect;
2655 pTable->pfnDisconnect = svcDisconnect;
2656 pTable->pfnCall = svcCall;
2657 pTable->pfnHostCall = svcHostCall;
2658 pTable->pfnSaveState = svcSaveState;
2659 pTable->pfnLoadState = svcLoadState;
2660 pTable->pfnRegisterExtension = svcRegisterExtension;
2661 pTable->pfnNotify = NULL;
2662 pTable->pvService = NULL;
2663
2664 /* Service specific initialization. */
2665 rc = svcInit();
2666 }
2667 }
2668
2669 LogFlowFunc(("Returning %Rrc\n", rc));
2670 return rc;
2671}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette