VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp@ 85681

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

DnD: Lots of documentation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.6 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 85681 2020-08-11 09:36:37Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag and drop source.
4 */
5
6/*
7 * Copyright (C) 2014-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/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#include "GuestDnDSourceImpl.h"
27#include "GuestDnDPrivate.h"
28#include "ConsoleImpl.h"
29
30#include "Global.h"
31#include "AutoCaller.h"
32#include "ThreadTask.h"
33
34#include <iprt/asm.h>
35#include <iprt/dir.h>
36#include <iprt/file.h>
37#include <iprt/path.h>
38#include <iprt/uri.h>
39
40#include <iprt/cpp/utils.h> /* For unconst(). */
41
42#include <VBox/com/array.h>
43
44
45/**
46 * Base class for a source task.
47 */
48class GuestDnDSourceTask : public ThreadTask
49{
50public:
51
52 GuestDnDSourceTask(GuestDnDSource *pSource)
53 : ThreadTask("GenericGuestDnDSourceTask")
54 , mSource(pSource)
55 , mRC(VINF_SUCCESS) { }
56
57 virtual ~GuestDnDSourceTask(void) { }
58
59 /** Returns the overall result of the task. */
60 int getRC(void) const { return mRC; }
61 /** Returns if the overall result of the task is ok (succeeded) or not. */
62 bool isOk(void) const { return RT_SUCCESS(mRC); }
63
64protected:
65
66 /** COM object pointer to the parent (source). */
67 const ComObjPtr<GuestDnDSource> mSource;
68 /** Overall result of the task. */
69 int mRC;
70};
71
72/**
73 * Task structure for receiving data from a source using
74 * a worker thread.
75 */
76class GuestDnDRecvDataTask : public GuestDnDSourceTask
77{
78public:
79
80 GuestDnDRecvDataTask(GuestDnDSource *pSource, GuestDnDRecvCtx *pCtx)
81 : GuestDnDSourceTask(pSource)
82 , mpCtx(pCtx)
83 {
84 m_strTaskName = "dndSrcRcvData";
85 }
86
87 void handler()
88 {
89 LogFlowThisFunc(("\n"));
90
91 const ComObjPtr<GuestDnDSource> pThis(mSource);
92 Assert(!pThis.isNull());
93
94 AutoCaller autoCaller(pThis);
95 if (FAILED(autoCaller.rc()))
96 return;
97
98 int vrc = pThis->i_receiveData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */);
99 if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
100 {
101 if (vrc != VERR_CANCELLED)
102 LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
103
104 /* Make sure to fire a cancel request to the guest side in case something went wrong. */
105 pThis->sendCancel();
106 }
107 }
108
109 virtual ~GuestDnDRecvDataTask(void) { }
110
111protected:
112
113 /** Pointer to receive data context. */
114 GuestDnDRecvCtx *mpCtx;
115};
116
117// constructor / destructor
118/////////////////////////////////////////////////////////////////////////////
119
120DEFINE_EMPTY_CTOR_DTOR(GuestDnDSource)
121
122HRESULT GuestDnDSource::FinalConstruct(void)
123{
124 /*
125 * Set the maximum block size this source can handle to 64K. This always has
126 * been hardcoded until now.
127 *
128 * Note: Never ever rely on information from the guest; the host dictates what and
129 * how to do something, so try to negogiate a sensible value here later.
130 */
131 mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
132
133 LogFlowThisFunc(("\n"));
134 return BaseFinalConstruct();
135}
136
137void GuestDnDSource::FinalRelease(void)
138{
139 LogFlowThisFuncEnter();
140 uninit();
141 BaseFinalRelease();
142 LogFlowThisFuncLeave();
143}
144
145// public initializer/uninitializer for internal purposes only
146/////////////////////////////////////////////////////////////////////////////
147
148int GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
149{
150 LogFlowThisFuncEnter();
151
152 /* Enclose the state transition NotReady->InInit->Ready. */
153 AutoInitSpan autoInitSpan(this);
154 AssertReturn(autoInitSpan.isOk(), E_FAIL);
155
156 unconst(m_pGuest) = pGuest;
157
158 /* Confirm a successful initialization when it's the case. */
159 autoInitSpan.setSucceeded();
160
161 return VINF_SUCCESS;
162}
163
164/**
165 * Uninitializes the instance.
166 * Called from FinalRelease().
167 */
168void GuestDnDSource::uninit(void)
169{
170 LogFlowThisFunc(("\n"));
171
172 /* Enclose the state transition Ready->InUninit->NotReady. */
173 AutoUninitSpan autoUninitSpan(this);
174 if (autoUninitSpan.uninitDone())
175 return;
176}
177
178// implementation of wrapped IDnDBase methods.
179/////////////////////////////////////////////////////////////////////////////
180
181HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
182{
183#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
184 ReturnComNotImplemented();
185#else /* VBOX_WITH_DRAG_AND_DROP */
186
187 AutoCaller autoCaller(this);
188 if (FAILED(autoCaller.rc())) return autoCaller.rc();
189
190 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
191
192 *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
193
194 return S_OK;
195#endif /* VBOX_WITH_DRAG_AND_DROP */
196}
197
198HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats)
199{
200#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
201 ReturnComNotImplemented();
202#else /* VBOX_WITH_DRAG_AND_DROP */
203
204 AutoCaller autoCaller(this);
205 if (FAILED(autoCaller.rc())) return autoCaller.rc();
206
207 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
208
209 aFormats = GuestDnDBase::i_getFormats();
210
211 return S_OK;
212#endif /* VBOX_WITH_DRAG_AND_DROP */
213}
214
215HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats)
216{
217#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
218 ReturnComNotImplemented();
219#else /* VBOX_WITH_DRAG_AND_DROP */
220
221 AutoCaller autoCaller(this);
222 if (FAILED(autoCaller.rc())) return autoCaller.rc();
223
224 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
225
226 return GuestDnDBase::i_addFormats(aFormats);
227#endif /* VBOX_WITH_DRAG_AND_DROP */
228}
229
230HRESULT GuestDnDSource::removeFormats(const GuestDnDMIMEList &aFormats)
231{
232#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
233 ReturnComNotImplemented();
234#else /* VBOX_WITH_DRAG_AND_DROP */
235
236 AutoCaller autoCaller(this);
237 if (FAILED(autoCaller.rc())) return autoCaller.rc();
238
239 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
240
241 return GuestDnDBase::i_removeFormats(aFormats);
242#endif /* VBOX_WITH_DRAG_AND_DROP */
243}
244
245HRESULT GuestDnDSource::getProtocolVersion(ULONG *aProtocolVersion)
246{
247#if !defined(VBOX_WITH_DRAG_AND_DROP)
248 ReturnComNotImplemented();
249#else /* VBOX_WITH_DRAG_AND_DROP */
250
251 AutoCaller autoCaller(this);
252 if (FAILED(autoCaller.rc())) return autoCaller.rc();
253
254 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
255
256 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
257#endif /* VBOX_WITH_DRAG_AND_DROP */
258}
259
260// implementation of wrapped IDnDSource methods.
261/////////////////////////////////////////////////////////////////////////////
262
263HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats,
264 std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
265{
266#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
267 ReturnComNotImplemented();
268#else /* VBOX_WITH_DRAG_AND_DROP */
269
270 /* aDefaultAction is optional. */
271
272 AutoCaller autoCaller(this);
273 if (FAILED(autoCaller.rc())) return autoCaller.rc();
274
275 /* Determine guest DnD protocol to use. */
276 GuestDnDBase::getProtocolVersion(&m_DataBase.uProtocolVersion);
277
278 /* Default is ignoring the action. */
279 if (aDefaultAction)
280 *aDefaultAction = DnDAction_Ignore;
281
282 HRESULT hr = S_OK;
283
284 GuestDnDMsg Msg;
285 Msg.setType(HOST_DND_GH_REQ_PENDING);
286 if (m_DataBase.uProtocolVersion >= 3)
287 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
288 Msg.appendUInt32(uScreenId);
289
290 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
291 if (RT_SUCCESS(rc))
292 {
293 GuestDnDResponse *pResp = GuestDnDInst()->response();
294 AssertPtr(pResp);
295
296 bool fFetchResult = true;
297
298 rc = pResp->waitForGuestResponse(100 /* Timeout in ms */);
299 if (RT_FAILURE(rc))
300 fFetchResult = false;
301
302 if ( fFetchResult
303 && isDnDIgnoreAction(pResp->getActionDefault()))
304 fFetchResult = false;
305
306 /* Fetch the default action to use. */
307 if (fFetchResult)
308 {
309 /*
310 * In the GuestDnDSource case the source formats are from the guest,
311 * as GuestDnDSource acts as a target for the guest. The host always
312 * dictates what's supported and what's not, so filter out all formats
313 * which are not supported by the host.
314 */
315 GuestDnDMIMEList lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, pResp->formats());
316 if (lstFiltered.size())
317 {
318 LogRel3(("DnD: Host offered the following formats:\n"));
319 for (size_t i = 0; i < lstFiltered.size(); i++)
320 LogRel3(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str()));
321
322 aFormats = lstFiltered;
323 aAllowedActions = GuestDnD::toMainActions(pResp->getActionsAllowed());
324 if (aDefaultAction)
325 *aDefaultAction = GuestDnD::toMainAction(pResp->getActionDefault());
326
327 /* Apply the (filtered) formats list. */
328 m_lstFmtOffered = lstFiltered;
329 }
330 else
331 LogRel2(("DnD: Negotiation of formats between guest and host failed, drag and drop to host not possible\n"));
332 }
333
334 LogFlowFunc(("fFetchResult=%RTbool, lstActionsAllowed=0x%x\n", fFetchResult, pResp->getActionsAllowed()));
335 }
336
337 LogFlowFunc(("hr=%Rhrc\n", hr));
338 return hr;
339#endif /* VBOX_WITH_DRAG_AND_DROP */
340}
341
342HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
343{
344#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
345 ReturnComNotImplemented();
346#else /* VBOX_WITH_DRAG_AND_DROP */
347
348 AutoCaller autoCaller(this);
349 if (FAILED(autoCaller.rc())) return autoCaller.rc();
350
351 LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction));
352
353 /* Input validation. */
354 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
355 return setError(E_INVALIDARG, tr("No drop format specified"));
356
357 /* Is the specified format in our list of (left over) offered formats? */
358 if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered))
359 return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str());
360
361 /* Check that the given action is supported by us. */
362 VBOXDNDACTION dndAction = GuestDnD::toHGCMAction(aAction);
363 if (isDnDIgnoreAction(dndAction)) /* If there is no usable action, ignore this request. */
364 return S_OK;
365
366 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
367
368 /* Check if this object still is in a pending state and bail out if so. */
369 if (m_fIsPending)
370 return setError(E_FAIL, tr("Current drop operation to host still in progress"));
371
372 /* Reset our internal state. */
373 i_reset();
374
375 /* At the moment we only support one transfer at a time. */
376 if (GuestDnDInst()->getSourceCount())
377 return setError(E_INVALIDARG, tr("Another drag and drop operation to the host already is in progress"));
378
379 /* Reset progress object. */
380 GuestDnDResponse *pResp = GuestDnDInst()->response();
381 AssertPtr(pResp);
382 HRESULT hr = pResp->resetProgress(m_pGuest);
383 if (FAILED(hr))
384 return hr;
385
386 GuestDnDRecvDataTask *pTask = NULL;
387
388 try
389 {
390 mData.mRecvCtx.pSource = this;
391 mData.mRecvCtx.pResp = pResp;
392 mData.mRecvCtx.enmAction = dndAction;
393 mData.mRecvCtx.strFmtReq = aFormat;
394 mData.mRecvCtx.lstFmtOffered = m_lstFmtOffered;
395
396 LogRel2(("DnD: Requesting data from guest in format '%s'\n", aFormat.c_str()));
397
398 pTask = new GuestDnDRecvDataTask(this, &mData.mRecvCtx);
399 if (!pTask->isOk())
400 {
401 delete pTask;
402 LogRel2(("DnD: Receive data task failed to initialize\n"));
403 throw hr = E_FAIL;
404 }
405
406 /* Drop write lock before creating thread. */
407 alock.release();
408
409 /* This function delete pTask in case of exceptions,
410 * so there is no need in the call of delete operator. */
411 hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
412 pTask = NULL; /* Note: pTask is now owned by the worker thread. */
413 }
414 catch (std::bad_alloc &)
415 {
416 hr = setError(E_OUTOFMEMORY);
417 }
418 catch (...)
419 {
420 LogRel2(("DnD: Could not create thread for data receiving task\n"));
421 hr = E_FAIL;
422 }
423
424 if (SUCCEEDED(hr))
425 {
426 /* Register ourselves at the DnD manager. */
427 GuestDnDInst()->registerSource(this);
428
429 hr = pResp->queryProgressTo(aProgress.asOutParam());
430 ComAssertComRC(hr);
431
432 }
433 else
434 hr = setError(hr, tr("Starting thread for GuestDnDSource failed (%Rhrc)"), hr);
435
436 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
437 return hr;
438#endif /* VBOX_WITH_DRAG_AND_DROP */
439}
440
441HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
442{
443#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
444 ReturnComNotImplemented();
445#else /* VBOX_WITH_DRAG_AND_DROP */
446
447 AutoCaller autoCaller(this);
448 if (FAILED(autoCaller.rc())) return autoCaller.rc();
449
450 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
451
452 /* Don't allow receiving the actual data until our current transfer is complete. */
453 if (m_fIsPending)
454 return setError(E_FAIL, tr("Current drop operation to host still in progress"));
455
456 HRESULT hr = S_OK;
457
458 try
459 {
460 GuestDnDRecvCtx *pCtx = &mData.mRecvCtx;
461 if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
462 {
463 PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
464
465 const char *pcszDropDirAbs = DnDDroppedFilesGetDirAbs(pDF);
466 AssertPtr(pcszDropDirAbs);
467
468 LogRel2(("DnD: Using drop directory '%s', got %RU64 root entries\n",
469 pcszDropDirAbs, DnDTransferListGetRootCount(&pCtx->Transfer.List)));
470
471 /* We return the data as "text/uri-list" MIME data here. */
472 char *pszBuf = NULL;
473 size_t cbBuf = 0;
474 int rc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
475 pcszDropDirAbs, DND_PATH_SEPARATOR, &pszBuf, &cbBuf);
476 if (RT_SUCCESS(rc))
477 {
478 Assert(cbBuf);
479 AssertPtr(pszBuf);
480
481 aData.resize(cbBuf);
482 memcpy(&aData.front(), pszBuf, cbBuf);
483 RTStrFree(pszBuf);
484 }
485 else
486 LogRel(("DnD: Unable to build source root list, rc=%Rrc\n", rc));
487 }
488 else /* Raw data. */
489 {
490 if (pCtx->Meta.cbData)
491 {
492 /* Copy the data into a safe array of bytes. */
493 aData.resize(pCtx->Meta.cbData);
494 memcpy(&aData.front(), pCtx->Meta.pvData, pCtx->Meta.cbData);
495 }
496 else
497 aData.resize(0);
498 }
499 }
500 catch (std::bad_alloc &)
501 {
502 hr = E_OUTOFMEMORY;
503 }
504
505 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
506 return hr;
507#endif /* VBOX_WITH_DRAG_AND_DROP */
508}
509
510// implementation of internal methods.
511/////////////////////////////////////////////////////////////////////////////
512
513/**
514 * Returns an error string from a guest DnD error.
515 *
516 * @returns Error string.
517 * @param guestRc Guest error to return error string for.
518 */
519/* static */
520Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc)
521{
522 Utf8Str strError;
523
524 switch (guestRc)
525 {
526 case VERR_ACCESS_DENIED:
527 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
528 "user does not have the appropriate access rights for. Please make sure that all selected "
529 "elements can be accessed and that your guest user has the appropriate rights"));
530 break;
531
532 case VERR_NOT_FOUND:
533 /* Should not happen due to file locking on the guest, but anyway ... */
534 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
535 "found on the guest anymore. This can be the case if the guest files were moved and/or"
536 "altered while the drag and drop operation was in progress"));
537 break;
538
539 case VERR_SHARING_VIOLATION:
540 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
541 "Please make sure that all selected elements can be accessed and that your guest user has "
542 "the appropriate rights"));
543 break;
544
545 case VERR_TIMEOUT:
546 strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time"));
547 break;
548
549 default:
550 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
551 break;
552 }
553
554 return strError;
555}
556
557/**
558 * Returns an error string from a host DnD error.
559 *
560 * @returns Error string.
561 * @param hostRc Host error to return error string for.
562 */
563/* static */
564Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc)
565{
566 Utf8Str strError;
567
568 switch (hostRc)
569 {
570 case VERR_ACCESS_DENIED:
571 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
572 "user does not have the appropriate access rights for. Please make sure that all selected "
573 "elements can be accessed and that your host user has the appropriate rights."));
574 break;
575
576 case VERR_DISK_FULL:
577 strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full)."));
578 break;
579
580 case VERR_NOT_FOUND:
581 /* Should not happen due to file locking on the host, but anyway ... */
582 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
583 "found on the host anymore. This can be the case if the host files were moved and/or"
584 "altered while the drag and drop operation was in progress."));
585 break;
586
587 case VERR_SHARING_VIOLATION:
588 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
589 "Please make sure that all selected elements can be accessed and that your host user has "
590 "the appropriate rights."));
591 break;
592
593 default:
594 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
595 break;
596 }
597
598 return strError;
599}
600
601/**
602 * Resets all internal data and state.
603 */
604void GuestDnDSource::i_reset(void)
605{
606 LogFlowThisFunc(("\n"));
607
608 mData.mRecvCtx.reset();
609
610 m_fIsPending = false;
611
612 /* Unregister ourselves from the DnD manager. */
613 GuestDnDInst()->unregisterSource(this);
614}
615
616#ifdef VBOX_WITH_DRAG_AND_DROP_GH
617
618/**
619 * Handles receiving a send data header from the guest.
620 *
621 * @returns VBox status code.
622 * @param pCtx Receive context to use.
623 * @param pDataHdr Pointer to send data header from the guest.
624 */
625int GuestDnDSource::i_onReceiveDataHdr(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
626{
627 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
628 AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
629
630 LogRel2(("DnD: Receiving %RU64 bytes total data (%RU32 bytes meta data, %RU64 objects) from guest ...\n",
631 pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects));
632
633 AssertReturn(pDataHdr->cbTotal >= pDataHdr->cbMeta, VERR_INVALID_PARAMETER);
634
635 pCtx->Meta.cbAnnounced = pDataHdr->cbMeta;
636 pCtx->cbExtra = pDataHdr->cbTotal - pDataHdr->cbMeta;
637
638 Assert(pCtx->Transfer.cObjToProcess == 0); /* Sanity. */
639 Assert(pCtx->Transfer.cObjProcessed == 0);
640
641 pCtx->Transfer.reset();
642
643 pCtx->Transfer.cObjToProcess = pDataHdr->cObjects;
644
645 /** @todo Handle compression type. */
646 /** @todo Handle checksum type. */
647
648 LogFlowFuncLeave();
649 return VINF_SUCCESS;
650}
651
652/**
653 * Main function for receiving data from the guest.
654 *
655 * @returns VBox status code.
656 * @param pCtx Receive context to use.
657 * @param pSndData Pointer to send data block from the guest.
658 */
659int GuestDnDSource::i_onReceiveData(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATA pSndData)
660{
661 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
662 AssertPtrReturn(pSndData, VERR_INVALID_POINTER);
663
664 int rc = VINF_SUCCESS;
665
666 try
667 {
668 GuestDnDTransferRecvData *pTransfer = &pCtx->Transfer;
669
670 size_t cbData;
671 void *pvData;
672 size_t cbTotalAnnounced;
673 size_t cbMetaAnnounced;
674
675 if (m_DataBase.uProtocolVersion < 3)
676 {
677 cbData = pSndData->u.v1.cbData;
678 pvData = pSndData->u.v1.pvData;
679
680 /* Sends the total data size to receive for every data chunk. */
681 cbTotalAnnounced = pSndData->u.v1.cbTotalSize;
682
683 /* Meta data size always is cbData, meaning there cannot be an
684 * extended data chunk transfer by sending further data. */
685 cbMetaAnnounced = cbData;
686 }
687 else
688 {
689 cbData = pSndData->u.v3.cbData;
690 pvData = pSndData->u.v3.pvData;
691
692 /* Note: Data sizes get initialized in i_onReceiveDataHdr().
693 * So just use the set values here. */
694 cbTotalAnnounced = pCtx->getTotalAnnounced();
695 cbMetaAnnounced = pCtx->Meta.cbAnnounced;
696 }
697
698 if (cbData > cbTotalAnnounced)
699 {
700 AssertMsgFailed(("Incoming data size invalid: cbData=%zu, cbTotal=%zu\n", cbData, cbTotalAnnounced));
701 rc = VERR_INVALID_PARAMETER;
702 }
703 else if ( cbTotalAnnounced == 0
704 || cbTotalAnnounced < cbMetaAnnounced)
705 {
706 AssertMsgFailed(("cbTotal (%zu) is smaller than cbMeta (%zu)\n", cbTotalAnnounced, cbMetaAnnounced));
707 rc = VERR_INVALID_PARAMETER;
708 }
709
710 if (RT_FAILURE(rc))
711 return rc;
712
713 AssertReturn(cbData <= mData.mcbBlockSize, VERR_BUFFER_OVERFLOW);
714
715 const size_t cbMetaRecv = pCtx->Meta.add(pvData, cbData);
716 AssertReturn(cbMetaRecv <= pCtx->Meta.cbData, VERR_BUFFER_OVERFLOW);
717
718 LogFlowThisFunc(("cbData=%zu, cbMetaRecv=%zu, cbMetaAnnounced=%zu, cbTotalAnnounced=%zu\n",
719 cbData, cbMetaRecv, cbMetaAnnounced, cbTotalAnnounced));
720
721 LogRel2(("DnD: %RU8%% of meta data complete (%zu/%zu bytes)\n",
722 (uint8_t)(cbMetaRecv * 100 / RT_MAX(cbMetaAnnounced, 1)), cbMetaRecv, cbMetaAnnounced));
723
724 /*
725 * (Meta) Data transfer complete?
726 */
727 if (cbMetaAnnounced == cbMetaRecv)
728 {
729 LogRel2(("DnD: Receiving meta data complete\n"));
730
731 if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
732 {
733 rc = DnDTransferListInitEx(&pTransfer->List,
734 DnDDroppedFilesGetDirAbs(&pTransfer->DroppedFiles), DNDTRANSFERLISTFMT_NATIVE);
735 if (RT_SUCCESS(rc))
736 rc = DnDTransferListAppendRootsFromBuffer(&pTransfer->List, DNDTRANSFERLISTFMT_URI,
737 (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR,
738 DNDTRANSFERLIST_FLAGS_NONE);
739 /* Validation. */
740 if (RT_SUCCESS(rc))
741 {
742 uint64_t cRoots = DnDTransferListGetRootCount(&pTransfer->List);
743
744 LogRel2(("DnD: Received %RU64 root entries from guest\n", cRoots));
745
746 if ( cRoots == 0
747 || cRoots > pTransfer->cObjToProcess)
748 {
749 LogRel(("DnD: Number of root entries invalid / mismatch: Got %RU64, expected %RU64\n",
750 cRoots, pTransfer->cObjToProcess));
751 rc = VERR_INVALID_PARAMETER;
752 }
753 }
754
755 if (RT_SUCCESS(rc))
756 {
757 /* Update our process with the data we already received. */
758 rc = updateProgress(pCtx, pCtx->pResp, cbMetaAnnounced);
759 AssertRC(rc);
760 }
761
762 if (RT_FAILURE(rc))
763 LogRel(("DnD: Error building root entry list, rc=%Rrc\n", rc));
764 }
765 else /* Raw data. */
766 {
767 rc = updateProgress(pCtx, pCtx->pResp, cbData);
768 AssertRC(rc);
769 }
770
771 if (RT_FAILURE(rc))
772 LogRel(("DnD: Error receiving meta data, rc=%Rrc\n", rc));
773 }
774 }
775 catch (std::bad_alloc &)
776 {
777 rc = VERR_NO_MEMORY;
778 }
779
780 LogFlowFuncLeaveRC(rc);
781 return rc;
782}
783
784
785int GuestDnDSource::i_onReceiveDir(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
786{
787 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
788 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
789 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
790
791 LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
792
793 const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
794 const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
795
796 int rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_DIRECTORY,
797 DnDDroppedFilesGetDirAbs(pDF), pszPath);
798 if (RT_SUCCESS(rc))
799 {
800 const char *pcszPathAbs = DnDTransferObjectGetSourcePath(pObj);
801 AssertPtr(pcszPathAbs);
802
803 rc = RTDirCreateFullPath(pcszPathAbs, fMode);
804 if (RT_SUCCESS(rc))
805 {
806 pCtx->Transfer.cObjProcessed++;
807 if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
808 {
809 rc = DnDDroppedFilesAddDir(pDF, pcszPathAbs);
810 }
811 else
812 rc = VERR_TOO_MUCH_DATA;
813
814 DnDTransferObjectDestroy(pObj);
815
816 if (RT_FAILURE(rc))
817 LogRel2(("DnD: Created guest directory '%s' on host\n", pcszPathAbs));
818 }
819 else
820 LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pcszPathAbs, rc));
821 }
822
823 if (RT_FAILURE(rc))
824 LogRel(("DnD: Receiving guest directory '%s' failed with rc=%Rrc\n", pszPath, rc));
825
826 LogFlowFuncLeaveRC(rc);
827 return rc;
828}
829
830/**
831 * Receives a file header from the guest.
832 *
833 * @returns VBox status code.
834 * @param pCtx Receive context to use.
835 * @param pszPath File path of file to use.
836 * @param cbPath Size (in bytes, including terminator) of file path.
837 * @param cbSize File size (in bytes) to receive.
838 * @param fMode File mode to use.
839 * @param fFlags Additional receive flags; not used yet.
840 */
841int GuestDnDSource::i_onReceiveFileHdr(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath,
842 uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
843{
844 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
845 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
846 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
847 AssertReturn(fMode, VERR_INVALID_PARAMETER);
848 /* fFlags are optional. */
849
850 RT_NOREF(fFlags);
851
852 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
853
854 AssertMsgReturn(cbSize <= pCtx->cbExtra,
855 ("File size (%RU64) exceeds extra size to transfer (%RU64)\n", cbSize, pCtx->cbExtra), VERR_INVALID_PARAMETER);
856 AssertMsgReturn( pCtx->isComplete() == false
857 && pCtx->Transfer.cObjToProcess,
858 ("Data transfer already complete, bailing out\n"), VERR_INVALID_PARAMETER);
859
860 int rc = VINF_SUCCESS;
861
862 do
863 {
864 const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
865
866 if ( DnDTransferObjectIsOpen(pObj)
867 && !DnDTransferObjectIsComplete(pObj))
868 {
869 AssertMsgFailed(("Object '%s' not complete yet\n", DnDTransferObjectGetSourcePath(pObj)));
870 rc = VERR_WRONG_ORDER;
871 break;
872 }
873
874 const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
875
876 rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_FILE, DnDDroppedFilesGetDirAbs(pDF), pszPath);
877 AssertRCBreak(rc);
878
879 const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
880 AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
881
882 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
883 rc = DnDTransferObjectOpen(pObj, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
884 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR, DNDTRANSFEROBJECT_FLAGS_NONE);
885 if (RT_FAILURE(rc))
886 {
887 LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pcszSource, rc));
888 break;
889 }
890
891 /* Note: Protocol v1 does not send any file sizes, so always 0. */
892 if (m_DataBase.uProtocolVersion >= 2)
893 rc = DnDTransferObjectSetSize(pObj, cbSize);
894
895 /** @todo Unescape path before printing. */
896 LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode %#x)\n",
897 pcszSource, DnDTransferObjectGetSize(pObj), DnDTransferObjectGetMode(pObj)));
898
899 /** @todo Set progress object title to current file being transferred? */
900
901 if (DnDTransferObjectIsComplete(pObj)) /* 0-byte file? We're done already. */
902 {
903 LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pcszSource));
904
905 pCtx->Transfer.cObjProcessed++;
906 if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
907 {
908 /* Add for having a proper rollback. */
909 rc = DnDDroppedFilesAddFile(pDF, pcszSource);
910 }
911 else
912 rc = VERR_TOO_MUCH_DATA;
913
914 DnDTransferObjectDestroy(pObj);
915 }
916
917 } while (0);
918
919 if (RT_FAILURE(rc))
920 LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc));
921
922 LogFlowFuncLeaveRC(rc);
923 return rc;
924}
925
926/**
927 * Receives file data from the guest.
928 *
929 * @returns VBox status code.
930 * @param pCtx Receive context to use.
931 * @param pvData Pointer to file data received from the guest.
932 * @param pCtx Size (in bytes) of file data received from the guest.
933 */
934int GuestDnDSource::i_onReceiveFileData(GuestDnDRecvCtx *pCtx, const void *pvData, uint32_t cbData)
935{
936 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
937 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
938 AssertReturn(cbData, VERR_INVALID_PARAMETER);
939
940 int rc = VINF_SUCCESS;
941
942 LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
943
944 /*
945 * Sanity checking.
946 */
947 if (cbData > mData.mcbBlockSize)
948 return VERR_INVALID_PARAMETER;
949
950 do
951 {
952 const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
953
954 const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
955 AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
956
957 AssertMsgReturn(DnDTransferObjectIsOpen(pObj),
958 ("Object '%s' not open (anymore)\n", pcszSource), VERR_WRONG_ORDER);
959 AssertMsgReturn(DnDTransferObjectIsComplete(pObj) == false,
960 ("Object '%s' already marked as complete\n", pcszSource), VERR_WRONG_ORDER);
961
962 uint32_t cbWritten;
963 rc = DnDTransferObjectWrite(pObj, pvData, cbData, &cbWritten);
964 if (RT_FAILURE(rc))
965 LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pcszSource, rc));
966
967 Assert(cbWritten <= cbData);
968 if (cbWritten < cbData)
969 {
970 LogRel(("DnD: Only written %RU32 of %RU32 bytes of guest file '%s' -- disk full?\n",
971 cbWritten, cbData, pcszSource));
972 rc = VERR_IO_GEN_FAILURE; /** @todo Find a better rc. */
973 break;
974 }
975
976 rc = updateProgress(pCtx, pCtx->pResp, cbWritten);
977 AssertRCBreak(rc);
978
979 if (DnDTransferObjectIsComplete(pObj))
980 {
981 LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pcszSource));
982
983 pCtx->Transfer.cObjProcessed++;
984 if (pCtx->Transfer.cObjProcessed > pCtx->Transfer.cObjToProcess)
985 rc = VERR_TOO_MUCH_DATA;
986
987 DnDTransferObjectDestroy(pObj);
988 }
989
990 } while (0);
991
992 if (RT_FAILURE(rc))
993 LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc));
994
995 LogFlowFuncLeaveRC(rc);
996 return rc;
997}
998#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
999
1000/**
1001 * Main function to receive DnD data from the guest.
1002 *
1003 * @returns VBox status code.
1004 * @param pCtx Receive context to use.
1005 * @param msTimeout Timeout (in ms) to wait for receiving data.
1006 */
1007int GuestDnDSource::i_receiveData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
1008{
1009 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1010
1011 /* Sanity. */
1012 AssertMsgReturn(pCtx->enmAction,
1013 ("Action to perform is none when it shouldn't\n"), VERR_INVALID_PARAMETER);
1014 AssertMsgReturn(pCtx->strFmtReq.isNotEmpty(),
1015 ("Requested format from host is empty when it shouldn't\n"), VERR_INVALID_PARAMETER);
1016
1017 /*
1018 * Do we need to receive a different format than initially requested?
1019 *
1020 * For example, receiving a file link as "text/plain" requires still to receive
1021 * the file from the guest as "text/uri-list" first, then pointing to
1022 * the file path on the host in the "text/plain" data returned.
1023 */
1024
1025 bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
1026
1027 LogFlowFunc(("strFmtReq=%s, strFmtRecv=%s, enmAction=0x%x\n",
1028 pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str(), pCtx->enmAction));
1029
1030 /* Plain text wanted? */
1031 if ( pCtx->strFmtReq.equalsIgnoreCase("text/plain")
1032 || pCtx->strFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
1033 {
1034 /* Did the guest offer a file? Receive a file instead. */
1035 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
1036 pCtx->strFmtRecv = "text/uri-list";
1037 /* Guest only offers (plain) text. */
1038 else
1039 pCtx->strFmtRecv = "text/plain;charset=utf-8";
1040
1041 /** @todo Add more conversions here. */
1042 }
1043 /* File(s) wanted? */
1044 else if (pCtx->strFmtReq.equalsIgnoreCase("text/uri-list"))
1045 {
1046 /* Does the guest support sending files? */
1047 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
1048 pCtx->strFmtRecv = "text/uri-list";
1049 else /* Bail out. */
1050 fFoundFormat = false;
1051 }
1052
1053 int rc = VINF_SUCCESS;
1054
1055 if (fFoundFormat)
1056 {
1057 if (!pCtx->strFmtRecv.equals(pCtx->strFmtReq))
1058 LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
1059 pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str()));
1060
1061 /*
1062 * Call the appropriate receive handler based on the data format to handle.
1063 */
1064 bool fURIData = DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length());
1065 if (fURIData)
1066 {
1067 rc = i_receiveTransferData(pCtx, msTimeout);
1068 }
1069 else
1070 {
1071 rc = i_receiveRawData(pCtx, msTimeout);
1072 }
1073 }
1074 else /* Just inform the user (if verbose release logging is enabled). */
1075 {
1076 LogRel(("DnD: The guest does not support format '%s':\n", pCtx->strFmtReq.c_str()));
1077 LogRel(("DnD: Guest offered the following formats:\n"));
1078 for (size_t i = 0; i < pCtx->lstFmtOffered.size(); i++)
1079 LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->lstFmtOffered.at(i).c_str()));
1080
1081 rc = VERR_NOT_SUPPORTED;
1082 }
1083
1084 if (RT_FAILURE(rc))
1085 {
1086 LogRel(("DnD: Receiving data from guest failed with %Rrc\n", rc));
1087
1088 /* Let the guest side know first. */
1089 sendCancel();
1090
1091 /* Reset state. */
1092 i_reset();
1093 }
1094
1095 LogFlowFuncLeaveRC(rc);
1096 return rc;
1097}
1098
1099/**
1100 * Receives raw (meta) data from the guest.
1101 *
1102 * @returns VBox status code.
1103 * @param pCtx Receive context to use.
1104 * @param msTimeout Timeout (in ms) to wait for receiving data.
1105 */
1106int GuestDnDSource::i_receiveRawData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
1107{
1108 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1109
1110 int rc;
1111
1112 LogFlowFuncEnter();
1113
1114 GuestDnDResponse *pResp = pCtx->pResp;
1115 AssertPtr(pCtx->pResp);
1116
1117 GuestDnD *pInst = GuestDnDInst();
1118 if (!pInst)
1119 return VERR_INVALID_POINTER;
1120
1121#define REGISTER_CALLBACK(x) \
1122 do { \
1123 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
1124 if (RT_FAILURE(rc)) \
1125 return rc; \
1126 } while (0)
1127
1128#define UNREGISTER_CALLBACK(x) \
1129 do { \
1130 int rc2 = pResp->setCallback(x, NULL); \
1131 AssertRC(rc2); \
1132 } while (0)
1133
1134 /*
1135 * Register callbacks.
1136 */
1137 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1138 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1139 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1140 if (m_DataBase.uProtocolVersion >= 3)
1141 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1142 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1143
1144 do
1145 {
1146 /*
1147 * Receive the raw data.
1148 */
1149 GuestDnDMsg Msg;
1150 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1151 if (m_DataBase.uProtocolVersion >= 3)
1152 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
1153 Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
1154 Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
1155 Msg.appendUInt32(pCtx->enmAction);
1156
1157 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1158 * the host and therefore now waiting for the actual raw data. */
1159 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1160 if (RT_SUCCESS(rc))
1161 {
1162 rc = waitForEvent(&pCtx->EventCallback, pCtx->pResp, msTimeout);
1163 if (RT_SUCCESS(rc))
1164 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1165 }
1166
1167 } while (0);
1168
1169 /*
1170 * Unregister callbacks.
1171 */
1172 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1173 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1174 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1175 if (m_DataBase.uProtocolVersion >= 3)
1176 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1177 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1178
1179#undef REGISTER_CALLBACK
1180#undef UNREGISTER_CALLBACK
1181
1182 if (RT_FAILURE(rc))
1183 {
1184 if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1185 {
1186 /*
1187 * Now that we've cleaned up tell the guest side to cancel.
1188 * This does not imply we're waiting for the guest to react, as the
1189 * host side never must depend on anything from the guest.
1190 */
1191 int rc2 = sendCancel();
1192 AssertRC(rc2);
1193
1194 rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_CANCELLED);
1195 AssertRC(rc2);
1196 }
1197 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1198 {
1199 int rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR,
1200 rc, GuestDnDSource::i_hostErrorToString(rc));
1201 AssertRC(rc2);
1202 }
1203
1204 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1205 }
1206
1207 LogFlowFuncLeaveRC(rc);
1208 return rc;
1209}
1210
1211/**
1212 * Receives transfer data (files / directories / ...) from the guest.
1213 *
1214 * @returns VBox status code.
1215 * @param pCtx Receive context to use.
1216 * @param msTimeout Timeout (in ms) to wait for receiving data.
1217 */
1218int GuestDnDSource::i_receiveTransferData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
1219{
1220 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1221
1222 int rc;
1223
1224 LogFlowFuncEnter();
1225
1226 GuestDnDResponse *pResp = pCtx->pResp;
1227 AssertPtr(pCtx->pResp);
1228
1229 GuestDnD *pInst = GuestDnDInst();
1230 if (!pInst)
1231 return VERR_INVALID_POINTER;
1232
1233#define REGISTER_CALLBACK(x) \
1234 do { \
1235 rc = pResp->setCallback(x, i_receiveTransferDataCallback, pCtx); \
1236 if (RT_FAILURE(rc)) \
1237 return rc; \
1238 } while (0)
1239
1240#define UNREGISTER_CALLBACK(x) \
1241 do { \
1242 int rc2 = pResp->setCallback(x, NULL); \
1243 AssertRC(rc2); \
1244 } while (0)
1245
1246 /*
1247 * Register callbacks.
1248 */
1249 /* Guest callbacks. */
1250 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1251 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1252 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1253 if (m_DataBase.uProtocolVersion >= 3)
1254 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1255 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1256 REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1257 if (m_DataBase.uProtocolVersion >= 2)
1258 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1259 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1260
1261 const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
1262
1263 do
1264 {
1265 rc = DnDDroppedFilesOpenTemp(pDF, 0 /* fFlags */);
1266 if (RT_FAILURE(rc))
1267 {
1268 LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n",
1269 DnDDroppedFilesGetDirAbs(pDF), rc));
1270 break;
1271 }
1272
1273 /*
1274 * Receive the transfer list.
1275 */
1276 GuestDnDMsg Msg;
1277 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1278 if (m_DataBase.uProtocolVersion >= 3)
1279 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
1280 Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
1281 Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
1282 Msg.appendUInt32(pCtx->enmAction);
1283
1284 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1285 * the host and therefore now waiting for the actual URI data. */
1286 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1287 if (RT_SUCCESS(rc))
1288 {
1289 LogFlowFunc(("Waiting ...\n"));
1290
1291 rc = waitForEvent(&pCtx->EventCallback, pCtx->pResp, msTimeout);
1292 if (RT_SUCCESS(rc))
1293 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1294
1295 LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
1296 }
1297
1298 } while (0);
1299
1300 /*
1301 * Unregister callbacks.
1302 */
1303 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1304 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1305 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1306 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1307 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1308 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1309 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1310 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1311
1312#undef REGISTER_CALLBACK
1313#undef UNREGISTER_CALLBACK
1314
1315 if (RT_FAILURE(rc))
1316 {
1317 int rc2 = DnDDroppedFilesRollback(pDF);
1318 if (RT_FAILURE(rc2))
1319 LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
1320 rc2, DnDDroppedFilesGetDirAbs(pDF)));
1321
1322 if (rc == VERR_CANCELLED)
1323 {
1324 /*
1325 * Now that we've cleaned up tell the guest side to cancel.
1326 * This does not imply we're waiting for the guest to react, as the
1327 * host side never must depend on anything from the guest.
1328 */
1329 rc2 = sendCancel();
1330 AssertRC(rc2);
1331
1332 rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_CANCELLED);
1333 AssertRC(rc2);
1334
1335 /* Cancelling is not an error, just set success here. */
1336 rc = VINF_SUCCESS;
1337 }
1338 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1339 {
1340 rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR,
1341 rc, GuestDnDSource::i_hostErrorToString(rc));
1342 AssertRC(rc2);
1343 }
1344 }
1345
1346 DnDDroppedFilesClose(pDF);
1347
1348 LogFlowFuncLeaveRC(rc);
1349 return rc;
1350}
1351
1352/**
1353 * Static HGCM service callback which handles receiving raw data.
1354 *
1355 * @returns VBox status code. Will get sent back to the host service.
1356 * @param uMsg HGCM message ID (function number).
1357 * @param pvParms Pointer to additional message data. Optional and can be NULL.
1358 * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
1359 * @param pvUser User-supplied pointer on callback registration.
1360 */
1361/* static */
1362DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1363{
1364 GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
1365 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1366
1367 GuestDnDSource *pThis = pCtx->pSource;
1368 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1369
1370 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1371
1372 int rc = VINF_SUCCESS;
1373
1374 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1375 bool fNotify = false;
1376
1377 switch (uMsg)
1378 {
1379 case GUEST_DND_CONNECT:
1380 /* Nothing to do here (yet). */
1381 break;
1382
1383 case GUEST_DND_DISCONNECT:
1384 rc = VERR_CANCELLED;
1385 break;
1386
1387#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1388 case GUEST_DND_GH_SND_DATA_HDR:
1389 {
1390 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1391 AssertPtr(pCBData);
1392 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1393 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1394
1395 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1396 break;
1397 }
1398 case GUEST_DND_GH_SND_DATA:
1399 {
1400 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1401 AssertPtr(pCBData);
1402 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1403 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1404
1405 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1406 break;
1407 }
1408 case GUEST_DND_GH_EVT_ERROR:
1409 {
1410 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1411 AssertPtr(pCBData);
1412 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1413 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1414
1415 pCtx->pResp->reset();
1416
1417 if (RT_SUCCESS(pCBData->rc))
1418 {
1419 AssertMsgFailed(("Received guest error with no error code set\n"));
1420 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1421 }
1422 else if (pCBData->rc == VERR_WRONG_ORDER)
1423 {
1424 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_CANCELLED);
1425 }
1426 else
1427 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1428 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1429
1430 LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
1431
1432 if (RT_SUCCESS(rc))
1433 rcCallback = VERR_GSTDND_GUEST_ERROR;
1434 break;
1435 }
1436#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1437 default:
1438 rc = VERR_NOT_SUPPORTED;
1439 break;
1440 }
1441
1442 if ( RT_FAILURE(rc)
1443 || RT_FAILURE(rcCallback))
1444 {
1445 fNotify = true;
1446 if (RT_SUCCESS(rcCallback))
1447 rcCallback = rc;
1448 }
1449
1450 if (RT_FAILURE(rc))
1451 {
1452 switch (rc)
1453 {
1454 case VERR_NO_DATA:
1455 LogRel2(("DnD: Data transfer to host complete\n"));
1456 break;
1457
1458 case VERR_CANCELLED:
1459 LogRel2(("DnD: Data transfer to host canceled\n"));
1460 break;
1461
1462 default:
1463 LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
1464 break;
1465 }
1466
1467 /* Unregister this callback. */
1468 AssertPtr(pCtx->pResp);
1469 int rc2 = pCtx->pResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1470 AssertRC(rc2);
1471 }
1472
1473 /* All data processed? */
1474 if (pCtx->isComplete())
1475 fNotify = true;
1476
1477 LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1478 pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc));
1479
1480 if (fNotify)
1481 {
1482 int rc2 = pCtx->EventCallback.Notify(rcCallback);
1483 AssertRC(rc2);
1484 }
1485
1486 LogFlowFuncLeaveRC(rc);
1487 return rc; /* Tell the guest. */
1488}
1489
1490/**
1491 * Static HGCM service callback which handles receiving transfer data from the guest.
1492 *
1493 * @returns VBox status code. Will get sent back to the host service.
1494 * @param uMsg HGCM message ID (function number).
1495 * @param pvParms Pointer to additional message data. Optional and can be NULL.
1496 * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
1497 * @param pvUser User-supplied pointer on callback registration.
1498 */
1499/* static */
1500DECLCALLBACK(int) GuestDnDSource::i_receiveTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1501{
1502 GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
1503 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1504
1505 GuestDnDSource *pThis = pCtx->pSource;
1506 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1507
1508 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1509
1510 int rc = VINF_SUCCESS;
1511
1512 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1513 bool fNotify = false;
1514
1515 switch (uMsg)
1516 {
1517 case GUEST_DND_CONNECT:
1518 /* Nothing to do here (yet). */
1519 break;
1520
1521 case GUEST_DND_DISCONNECT:
1522 rc = VERR_CANCELLED;
1523 break;
1524
1525#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1526 case GUEST_DND_GH_SND_DATA_HDR:
1527 {
1528 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1529 AssertPtr(pCBData);
1530 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1531 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1532
1533 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1534 break;
1535 }
1536 case GUEST_DND_GH_SND_DATA:
1537 {
1538 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1539 AssertPtr(pCBData);
1540 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1541 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1542
1543 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1544 break;
1545 }
1546 case GUEST_DND_GH_SND_DIR:
1547 {
1548 PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
1549 AssertPtr(pCBData);
1550 AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1551 AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1552
1553 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1554 break;
1555 }
1556 case GUEST_DND_GH_SND_FILE_HDR:
1557 {
1558 PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1559 AssertPtr(pCBData);
1560 AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1561 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1562
1563 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1564 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1565 break;
1566 }
1567 case GUEST_DND_GH_SND_FILE_DATA:
1568 {
1569 PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1570 AssertPtr(pCBData);
1571 AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1572 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1573
1574 if (pThis->m_DataBase.uProtocolVersion <= 1)
1575 {
1576 /**
1577 * Notes for protocol v1 (< VBox 5.0):
1578 * - Every time this command is being sent it includes the file header,
1579 * so just process both calls here.
1580 * - There was no information whatsoever about the total file size; the old code only
1581 * appended data to the desired file. So just pass 0 as cbSize.
1582 */
1583 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1584 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1585 if (RT_SUCCESS(rc))
1586 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1587 }
1588 else /* Protocol v2 and up. */
1589 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1590 break;
1591 }
1592 case GUEST_DND_GH_EVT_ERROR:
1593 {
1594 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1595 AssertPtr(pCBData);
1596 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1597 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1598
1599 pCtx->pResp->reset();
1600
1601 if (RT_SUCCESS(pCBData->rc))
1602 {
1603 AssertMsgFailed(("Received guest error with no error code set\n"));
1604 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1605 }
1606 else if (pCBData->rc == VERR_WRONG_ORDER)
1607 {
1608 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_CANCELLED);
1609 }
1610 else
1611 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1612 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1613
1614 LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
1615
1616 if (RT_SUCCESS(rc))
1617 rcCallback = VERR_GSTDND_GUEST_ERROR;
1618 break;
1619 }
1620#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1621 default:
1622 rc = VERR_NOT_SUPPORTED;
1623 break;
1624 }
1625
1626 if ( RT_FAILURE(rc)
1627 || RT_FAILURE(rcCallback))
1628 {
1629 fNotify = true;
1630 if (RT_SUCCESS(rcCallback))
1631 rcCallback = rc;
1632 }
1633
1634 if (RT_FAILURE(rc))
1635 {
1636 switch (rc)
1637 {
1638 case VERR_NO_DATA:
1639 LogRel2(("DnD: File transfer to host complete\n"));
1640 break;
1641
1642 case VERR_CANCELLED:
1643 LogRel2(("DnD: File transfer to host canceled\n"));
1644 break;
1645
1646 default:
1647 LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
1648 break;
1649 }
1650
1651 /* Unregister this callback. */
1652 AssertPtr(pCtx->pResp);
1653 int rc2 = pCtx->pResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1654 AssertRC(rc2);
1655 }
1656
1657 /* All data processed? */
1658 if ( pCtx->Transfer.isComplete()
1659 && pCtx->isComplete())
1660 {
1661 fNotify = true;
1662 }
1663
1664 LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1665 pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc));
1666
1667 if (fNotify)
1668 {
1669 int rc2 = pCtx->EventCallback.Notify(rcCallback);
1670 AssertRC(rc2);
1671 }
1672
1673 LogFlowFuncLeaveRC(rc);
1674 return rc; /* Tell the guest. */
1675}
1676
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