VirtualBox

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

Last change on this file since 77863 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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