VirtualBox

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

Last change on this file since 59842 was 59842, checked in by vboxsync, 9 years ago

DnD/Main: Fixes for raw data transfers and cancellation logic.

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