VirtualBox

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

Last change on this file since 84564 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.9 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag and drop source.
4 */
5
6/*
7 * Copyright (C) 2014-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#include "GuestDnDSourceImpl.h"
27#include "GuestDnDPrivate.h"
28#include "ConsoleImpl.h"
29
30#include "Global.h"
31#include "AutoCaller.h"
32#include "ThreadTask.h"
33
34#include <iprt/asm.h>
35#include <iprt/dir.h>
36#include <iprt/file.h>
37#include <iprt/path.h>
38#include <iprt/uri.h>
39
40#include <iprt/cpp/utils.h> /* For unconst(). */
41
42#include <VBox/com/array.h>
43
44
45/**
46 * Base class for a source task.
47 */
48class GuestDnDSourceTask : public ThreadTask
49{
50public:
51
52 GuestDnDSourceTask(GuestDnDSource *pSource)
53 : ThreadTask("GenericGuestDnDSourceTask")
54 , mSource(pSource)
55 , mRC(VINF_SUCCESS) { }
56
57 virtual ~GuestDnDSourceTask(void) { }
58
59 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 pTask = NULL; /* Note: pTask is now owned by the worker thread. */
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 }
400 else
401 hr = setError(hr, tr("Starting thread for GuestDnDSource::i_receiveDataThread failed (%Rhrc)"), hr);
402 /* Note: mDataBase.mfTransferIsPending will be set to false again by i_receiveDataThread. */
403
404 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
405 return hr;
406#endif /* VBOX_WITH_DRAG_AND_DROP */
407}
408
409HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
410{
411#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
412 ReturnComNotImplemented();
413#else /* VBOX_WITH_DRAG_AND_DROP */
414
415 AutoCaller autoCaller(this);
416 if (FAILED(autoCaller.rc())) return autoCaller.rc();
417
418 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
419
420 LogFlowThisFunc(("cTransfersPending=%RU32\n", mDataBase.m_cTransfersPending));
421
422 /* Don't allow receiving the actual data until our transfer actually is complete. */
423 if (mDataBase.m_cTransfersPending)
424 return setError(E_FAIL, tr("Current drop operation still in progress"));
425
426 PRECVDATACTX pCtx = &mData.mRecvCtx;
427 HRESULT hr = S_OK;
428
429 try
430 {
431 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
432 if (fHasURIList)
433 {
434 LogRel2(("DnD: Drop directory is: %s\n", pCtx->mURI.getDroppedFiles().GetDirAbs()));
435 int rc2 = pCtx->mURI.toMetaData(aData);
436 if (RT_FAILURE(rc2))
437 hr = E_OUTOFMEMORY;
438 }
439 else
440 {
441 const size_t cbData = pCtx->mData.getMeta().getSize();
442 LogFlowFunc(("cbData=%zu\n", cbData));
443 if (cbData)
444 {
445 /* Copy the data into a safe array of bytes. */
446 aData.resize(cbData);
447 memcpy(&aData.front(), pCtx->mData.getMeta().getData(), cbData);
448 }
449 else
450 aData.resize(0);
451 }
452 }
453 catch (std::bad_alloc &)
454 {
455 hr = E_OUTOFMEMORY;
456 }
457
458 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
459 return hr;
460#endif /* VBOX_WITH_DRAG_AND_DROP */
461}
462
463// implementation of internal methods.
464/////////////////////////////////////////////////////////////////////////////
465
466/* static */
467Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc)
468{
469 Utf8Str strError;
470
471 switch (guestRc)
472 {
473 case VERR_ACCESS_DENIED:
474 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
475 "user does not have the appropriate access rights for. Please make sure that all selected "
476 "elements can be accessed and that your guest user has the appropriate rights"));
477 break;
478
479 case VERR_NOT_FOUND:
480 /* Should not happen due to file locking on the guest, but anyway ... */
481 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
482 "found on the guest anymore. This can be the case if the guest files were moved and/or"
483 "altered while the drag and drop operation was in progress"));
484 break;
485
486 case VERR_SHARING_VIOLATION:
487 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
488 "Please make sure that all selected elements can be accessed and that your guest user has "
489 "the appropriate rights"));
490 break;
491
492 case VERR_TIMEOUT:
493 strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time"));
494 break;
495
496 default:
497 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
498 break;
499 }
500
501 return strError;
502}
503
504/* static */
505Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc)
506{
507 Utf8Str strError;
508
509 switch (hostRc)
510 {
511 case VERR_ACCESS_DENIED:
512 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
513 "user does not have the appropriate access rights for. Please make sure that all selected "
514 "elements can be accessed and that your host user has the appropriate rights."));
515 break;
516
517 case VERR_DISK_FULL:
518 strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full)."));
519 break;
520
521 case VERR_NOT_FOUND:
522 /* Should not happen due to file locking on the host, but anyway ... */
523 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
524 "found on the host anymore. This can be the case if the host files were moved and/or"
525 "altered while the drag and drop operation was in progress."));
526 break;
527
528 case VERR_SHARING_VIOLATION:
529 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
530 "Please make sure that all selected elements can be accessed and that your host user has "
531 "the appropriate rights."));
532 break;
533
534 default:
535 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
536 break;
537 }
538
539 return strError;
540}
541
542#ifdef VBOX_WITH_DRAG_AND_DROP_GH
543int GuestDnDSource::i_onReceiveDataHdr(PRECVDATACTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
544{
545 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
546 AssertReturn(pDataHdr, VERR_INVALID_POINTER);
547
548 pCtx->mData.setEstimatedSize(pDataHdr->cbTotal, pDataHdr->cbMeta);
549
550 Assert(pCtx->mURI.getObjToProcess() == 0);
551 pCtx->mURI.reset();
552 pCtx->mURI.setEstimatedObjects(pDataHdr->cObjects);
553
554 /** @todo Handle compression type. */
555 /** @todo Handle checksum type. */
556
557 LogFlowFuncLeave();
558 return VINF_SUCCESS;
559}
560
561int GuestDnDSource::i_onReceiveData(PRECVDATACTX pCtx, PVBOXDNDSNDDATA pSndData)
562{
563 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
564 AssertPtrReturn(pSndData, VERR_INVALID_POINTER);
565
566 int rc = VINF_SUCCESS;
567
568 try
569 {
570 GuestDnDData *pData = &pCtx->mData;
571 GuestDnDURIData *pURI = &pCtx->mURI;
572
573 uint32_t cbData;
574 void *pvData;
575 uint64_t cbTotal;
576 uint32_t cbMeta;
577
578 if (mDataBase.m_uProtocolVersion < 3)
579 {
580 cbData = pSndData->u.v1.cbData;
581 pvData = pSndData->u.v1.pvData;
582
583 /* Sends the total data size to receive for every data chunk. */
584 cbTotal = pSndData->u.v1.cbTotalSize;
585
586 /* Meta data size always is cbData, meaning there cannot be an
587 * extended data chunk transfer by sending further data. */
588 cbMeta = cbData;
589 }
590 else
591 {
592 cbData = pSndData->u.v3.cbData;
593 pvData = pSndData->u.v3.pvData;
594
595 /* Note: Data sizes get updated in i_onReceiveDataHdr(). */
596 cbTotal = pData->getTotal();
597 cbMeta = pData->getMeta().getSize();
598 }
599 Assert(cbTotal);
600
601 if ( cbData == 0
602 || cbData > cbTotal /* Paranoia */)
603 {
604 LogFlowFunc(("Incoming data size invalid: cbData=%RU32, cbToProcess=%RU64\n", cbData, pData->getTotal()));
605 rc = VERR_INVALID_PARAMETER;
606 }
607 else if (cbTotal < cbMeta)
608 {
609 AssertMsgFailed(("cbTotal (%RU64) is smaller than cbMeta (%RU32)\n", cbTotal, cbMeta));
610 rc = VERR_INVALID_PARAMETER;
611 }
612
613 if (RT_SUCCESS(rc))
614 {
615 cbMeta = pData->getMeta().add(pvData, cbData);
616 LogFlowThisFunc(("cbMetaSize=%zu, cbData=%RU32, cbMeta=%RU32, cbTotal=%RU64\n",
617 pData->getMeta().getSize(), cbData, cbMeta, cbTotal));
618 }
619
620 if (RT_SUCCESS(rc))
621 {
622 /*
623 * (Meta) Data transfer complete?
624 */
625 Assert(cbMeta <= pData->getMeta().getSize());
626 if (cbMeta == pData->getMeta().getSize())
627 {
628 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
629 LogFlowThisFunc(("fHasURIList=%RTbool\n", fHasURIList));
630 if (fHasURIList)
631 {
632 /* Try parsing the data as URI list. */
633 rc = pURI->fromRemoteMetaData(pData->getMeta());
634 if (RT_SUCCESS(rc))
635 {
636 if (mDataBase.m_uProtocolVersion < 3)
637 pData->setEstimatedSize(cbTotal, cbMeta);
638
639 /*
640 * Update our process with the data we already received.
641 * Note: The total size will consist of the meta data (in pVecData) and
642 * the actual accumulated file/directory data from the guest.
643 */
644 rc = updateProgress(pData, pCtx->mpResp, (uint32_t)pData->getMeta().getSize());
645 }
646 }
647 else /* Raw data. */
648 rc = updateProgress(pData, pCtx->mpResp, cbData);
649 }
650 }
651 }
652 catch (std::bad_alloc &)
653 {
654 rc = VERR_NO_MEMORY;
655 }
656
657 LogFlowFuncLeaveRC(rc);
658 return rc;
659}
660
661int GuestDnDSource::i_onReceiveDir(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
662{
663 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
664 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
665 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
666
667 LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
668
669 /*
670 * Sanity checking.
671 */
672 if ( !cbPath
673 || cbPath > RTPATH_MAX)
674 {
675 LogFlowFunc(("Path length invalid, bailing out\n"));
676 return VERR_INVALID_PARAMETER;
677 }
678
679 int rc = RTStrValidateEncodingEx(pszPath, RTSTR_MAX, 0);
680 if (RT_FAILURE(rc))
681 {
682 LogFlowFunc(("Path validation failed with %Rrc, bailing out\n", rc));
683 return VERR_INVALID_PARAMETER;
684 }
685
686 if (pCtx->mURI.isComplete())
687 {
688 LogFlowFunc(("Data transfer already complete, bailing out\n"));
689 return VERR_INVALID_PARAMETER;
690 }
691
692 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
693
694 rc = objCtx.createIntermediate(DnDURIObject::Type_Directory);
695 if (RT_FAILURE(rc))
696 return rc;
697
698 DnDURIObject *pObj = objCtx.getObj();
699 AssertPtr(pObj);
700
701 const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs();
702 char *pszDir = RTPathJoinA(pszDroppedFilesDir, pszPath);
703 if (pszDir)
704 {
705#ifdef RT_OS_WINDOWS
706 RTPathChangeToDosSlashes(pszDir, true /* fForce */);
707#else
708 RTPathChangeToDosSlashes(pszDir, true /* fForce */);
709#endif
710 rc = RTDirCreateFullPath(pszDir, fMode);
711 if (RT_SUCCESS(rc))
712 {
713 pCtx->mURI.processObject(*pObj);
714
715 /* Add for having a proper rollback. */
716 int rc2 = pCtx->mURI.getDroppedFiles().AddDir(pszDir);
717 AssertRC(rc2);
718
719 objCtx.reset();
720 LogRel2(("DnD: Created guest directory '%s' on host\n", pszDir));
721 }
722 else
723 LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pszDir, rc));
724
725 RTStrFree(pszDir);
726 }
727 else
728 rc = VERR_NO_MEMORY;
729
730 LogFlowFuncLeaveRC(rc);
731 return rc;
732}
733
734int GuestDnDSource::i_onReceiveFileHdr(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath,
735 uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
736{
737 RT_NOREF(fFlags);
738 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
739 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
740 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
741 AssertReturn(fMode, VERR_INVALID_PARAMETER);
742 /* fFlags are optional. */
743
744 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
745
746 /*
747 * Sanity checking.
748 */
749 if ( !cbPath
750 || cbPath > RTPATH_MAX)
751 {
752 return VERR_INVALID_PARAMETER;
753 }
754
755 if (!RTStrIsValidEncoding(pszPath))
756 return VERR_INVALID_PARAMETER;
757
758 if (cbSize > pCtx->mData.getTotal())
759 {
760 AssertMsgFailed(("File size (%RU64) exceeds total size to transfer (%RU64)\n", cbSize, pCtx->mData.getTotal()));
761 return VERR_INVALID_PARAMETER;
762 }
763
764 if (pCtx->mURI.getObjToProcess() && pCtx->mURI.isComplete())
765 return VERR_INVALID_PARAMETER;
766
767 int rc = VINF_SUCCESS;
768
769 do
770 {
771 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
772 DnDURIObject *pObj = objCtx.getObj();
773
774 /*
775 * Sanity checking.
776 */
777 if (pObj)
778 {
779 if ( pObj->IsOpen()
780 && !pObj->IsComplete())
781 {
782 AssertMsgFailed(("Object '%s' not complete yet\n", pObj->GetDestPathAbs().c_str()));
783 rc = VERR_WRONG_ORDER;
784 break;
785 }
786
787 if (pObj->IsOpen()) /* File already opened? */
788 {
789 AssertMsgFailed(("Current opened object is '%s', close this first\n", pObj->GetDestPathAbs().c_str()));
790 rc = VERR_WRONG_ORDER;
791 break;
792 }
793 }
794 else
795 {
796 /*
797 * Create new intermediate object to work with.
798 */
799 rc = objCtx.createIntermediate(DnDURIObject::Type_File);
800 }
801
802 if (RT_SUCCESS(rc))
803 {
804 pObj = objCtx.getObj();
805 AssertPtr(pObj);
806
807 const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs();
808 AssertPtr(pszDroppedFilesDir);
809
810 char pszPathAbs[RTPATH_MAX];
811 rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), pszDroppedFilesDir, pszPath);
812 if (RT_FAILURE(rc))
813 {
814 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
815 break;
816 }
817
818 rc = DnDPathSanitize(pszPathAbs, sizeof(pszPathAbs));
819 if (RT_FAILURE(rc))
820 {
821 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
822 break;
823 }
824
825 LogRel2(("DnD: Absolute file path for guest file on the host is now '%s'\n", pszPathAbs));
826
827 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
828 rc = pObj->OpenEx(pszPathAbs, DnDURIObject::View_Target,
829 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
830 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
831 if (RT_SUCCESS(rc))
832 {
833 /* Add for having a proper rollback. */
834 int rc2 = pCtx->mURI.getDroppedFiles().AddFile(pszPathAbs);
835 AssertRC(rc2);
836 }
837 else
838 LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pszPathAbs, rc));
839 }
840
841 if (RT_SUCCESS(rc))
842 {
843 /* Note: Protocol v1 does not send any file sizes, so always 0. */
844 if (mDataBase.m_uProtocolVersion >= 2)
845 rc = pObj->SetSize(cbSize);
846
847 /** @todo Unescape path before printing. */
848 LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode 0x%x)\n",
849 pObj->GetDestPathAbs().c_str(), pObj->GetSize(), pObj->GetMode()));
850
851 /** @todo Set progress object title to current file being transferred? */
852
853 if (pObj->IsComplete()) /* 0-byte file? We're done already. */
854 {
855 /** @todo Sanitize path. */
856 LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pObj->GetDestPathAbs().c_str()));
857
858 pCtx->mURI.processObject(*pObj);
859 pObj->Close();
860
861 objCtx.reset();
862 }
863 }
864
865 } while (0);
866
867 if (RT_FAILURE(rc))
868 LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc));
869
870 LogFlowFuncLeaveRC(rc);
871 return rc;
872}
873
874int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
875{
876 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
877 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
878 AssertReturn(cbData, VERR_INVALID_PARAMETER);
879
880 int rc = VINF_SUCCESS;
881
882 LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
883
884 /*
885 * Sanity checking.
886 */
887 if (cbData > mData.mcbBlockSize)
888 return VERR_INVALID_PARAMETER;
889
890 do
891 {
892 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
893 DnDURIObject *pObj = objCtx.getObj();
894
895 if (!pObj)
896 {
897 LogFlowFunc(("Warning: No current object set\n"));
898 rc = VERR_WRONG_ORDER;
899 break;
900 }
901
902 if (pObj->IsComplete())
903 {
904 LogFlowFunc(("Warning: Object '%s' already completed\n", pObj->GetDestPathAbs().c_str()));
905 rc = VERR_WRONG_ORDER;
906 break;
907 }
908
909 if (!pObj->IsOpen()) /* File opened on host? */
910 {
911 LogFlowFunc(("Warning: Object '%s' not opened\n", pObj->GetDestPathAbs().c_str()));
912 rc = VERR_WRONG_ORDER;
913 break;
914 }
915
916 uint32_t cbWritten;
917 rc = pObj->Write(pvData, cbData, &cbWritten);
918 if (RT_SUCCESS(rc))
919 {
920 Assert(cbWritten <= cbData);
921 if (cbWritten < cbData)
922 {
923 /** @todo What to do when the host's disk is full? */
924 rc = VERR_DISK_FULL;
925 }
926
927 if (RT_SUCCESS(rc))
928 rc = updateProgress(&pCtx->mData, pCtx->mpResp, cbWritten);
929 }
930 else
931 LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pObj->GetDestPathAbs().c_str(), rc));
932
933 if (RT_SUCCESS(rc))
934 {
935 if (pObj->IsComplete())
936 {
937 /** @todo Sanitize path. */
938 LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pObj->GetDestPathAbs().c_str()));
939 pCtx->mURI.processObject(*pObj);
940 objCtx.reset();
941 }
942 }
943
944 } while (0);
945
946 if (RT_FAILURE(rc))
947 LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc));
948
949 LogFlowFuncLeaveRC(rc);
950 return rc;
951}
952#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
953
954/**
955 * @returns VBox status code that the caller ignores. Not sure if that's
956 * intentional or not.
957 */
958int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
959{
960 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
961
962 GuestDnD *pInst = GUESTDNDINST();
963 if (!pInst)
964 return VERR_INVALID_POINTER;
965
966 GuestDnDResponse *pResp = pCtx->mpResp;
967 AssertPtr(pCtx->mpResp);
968
969 int rc = pCtx->mCBEvent.Reset();
970 if (RT_FAILURE(rc))
971 return rc;
972
973 /* Is this context already in receiving state? */
974 if (ASMAtomicReadBool(&pCtx->mIsActive))
975 return VERR_WRONG_ORDER;
976 ASMAtomicWriteBool(&pCtx->mIsActive, true);
977
978 /*
979 * Reset any old data.
980 */
981 pCtx->mData.reset();
982 pCtx->mURI.reset();
983 pResp->reset();
984
985 /*
986 * Do we need to receive a different format than initially requested?
987 *
988 * For example, receiving a file link as "text/plain" requires still to receive
989 * the file from the guest as "text/uri-list" first, then pointing to
990 * the file path on the host in the "text/plain" data returned.
991 */
992
993 bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
994
995 LogFlowFunc(("mFmtReq=%s, mFmtRecv=%s, mAction=0x%x\n",
996 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str(), pCtx->mAction));
997
998 /* Plain text wanted? */
999 if ( pCtx->mFmtReq.equalsIgnoreCase("text/plain")
1000 || pCtx->mFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
1001 {
1002 /* Did the guest offer a file? Receive a file instead. */
1003 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1004 pCtx->mFmtRecv = "text/uri-list";
1005 /* Guest only offers (plain) text. */
1006 else
1007 pCtx->mFmtRecv = "text/plain;charset=utf-8";
1008
1009 /** @todo Add more conversions here. */
1010 }
1011 /* File(s) wanted? */
1012 else if (pCtx->mFmtReq.equalsIgnoreCase("text/uri-list"))
1013 {
1014 /* Does the guest support sending files? */
1015 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1016 pCtx->mFmtRecv = "text/uri-list";
1017 else /* Bail out. */
1018 fFoundFormat = false;
1019 }
1020
1021 if (fFoundFormat)
1022 {
1023 Assert(!pCtx->mFmtReq.isEmpty());
1024 Assert(!pCtx->mFmtRecv.isEmpty());
1025
1026 if (!pCtx->mFmtRecv.equals(pCtx->mFmtReq))
1027 LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
1028 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str()));
1029
1030 /*
1031 * Call the appropriate receive handler based on the data format to handle.
1032 */
1033 bool fURIData = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
1034 if (fURIData)
1035 {
1036 rc = i_receiveURIData(pCtx, msTimeout);
1037 }
1038 else
1039 {
1040 rc = i_receiveRawData(pCtx, msTimeout);
1041 }
1042 }
1043 else /* Just inform the user (if verbose release logging is enabled). */
1044 {
1045 LogRel(("DnD: The guest does not support format '%s':\n", pCtx->mFmtReq.c_str()));
1046 LogRel(("DnD: Guest offered the following formats:\n"));
1047 for (size_t i = 0; i < pCtx->mFmtOffered.size(); i++)
1048 LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->mFmtOffered.at(i).c_str()));
1049 }
1050
1051 ASMAtomicWriteBool(&pCtx->mIsActive, false);
1052
1053 LogFlowFuncLeaveRC(rc);
1054 return rc;
1055}
1056
1057/* static */
1058void GuestDnDSource::i_receiveDataThreadTask(RecvDataTask *pTask)
1059{
1060 LogFlowFunc(("pTask=%p\n", pTask));
1061 AssertPtrReturnVoid(pTask);
1062
1063 const ComObjPtr<GuestDnDSource> pThis(pTask->getSource());
1064 Assert(!pThis.isNull());
1065
1066 AutoCaller autoCaller(pThis);
1067 if (FAILED(autoCaller.rc()))
1068 return;
1069
1070 int vrc = pThis->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
1071 if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
1072 {
1073 AssertFailed();
1074 LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
1075 }
1076
1077 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
1078
1079 Assert(pThis->mDataBase.m_cTransfersPending);
1080 if (pThis->mDataBase.m_cTransfersPending)
1081 pThis->mDataBase.m_cTransfersPending--;
1082
1083 LogFlowFunc(("pSource=%p, vrc=%Rrc (ignored)\n", (GuestDnDSource *)pThis, vrc));
1084}
1085
1086int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1087{
1088 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1089
1090 int rc;
1091
1092 LogFlowFuncEnter();
1093
1094 GuestDnDResponse *pResp = pCtx->mpResp;
1095 AssertPtr(pCtx->mpResp);
1096
1097 GuestDnD *pInst = GUESTDNDINST();
1098 if (!pInst)
1099 return VERR_INVALID_POINTER;
1100
1101#define REGISTER_CALLBACK(x) \
1102 do { \
1103 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
1104 if (RT_FAILURE(rc)) \
1105 return rc; \
1106 } while (0)
1107
1108#define UNREGISTER_CALLBACK(x) \
1109 do { \
1110 int rc2 = pResp->setCallback(x, NULL); \
1111 AssertRC(rc2); \
1112 } while (0)
1113
1114 /*
1115 * Register callbacks.
1116 */
1117 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1118 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1119 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1120 if (mDataBase.m_uProtocolVersion >= 3)
1121 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1122 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1123
1124 do
1125 {
1126 /*
1127 * Receive the raw data.
1128 */
1129 GuestDnDMsg Msg;
1130 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1131 if (mDataBase.m_uProtocolVersion >= 3)
1132 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1133 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1134 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1135 Msg.setNextUInt32(pCtx->mAction);
1136
1137 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1138 * the host and therefore now waiting for the actual raw data. */
1139 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1140 if (RT_SUCCESS(rc))
1141 {
1142 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1143 if (RT_SUCCESS(rc))
1144 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1145 }
1146
1147 } while (0);
1148
1149 /*
1150 * Unregister callbacks.
1151 */
1152 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1153 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1154 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1155 if (mDataBase.m_uProtocolVersion >= 3)
1156 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1157 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1158
1159#undef REGISTER_CALLBACK
1160#undef UNREGISTER_CALLBACK
1161
1162 if (RT_FAILURE(rc))
1163 {
1164 if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1165 {
1166 /*
1167 * Now that we've cleaned up tell the guest side to cancel.
1168 * This does not imply we're waiting for the guest to react, as the
1169 * host side never must depend on anything from the guest.
1170 */
1171 int rc2 = sendCancel();
1172 AssertRC(rc2);
1173
1174 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1175 AssertRC(rc2);
1176 }
1177 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1178 {
1179 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1180 rc, GuestDnDSource::i_hostErrorToString(rc));
1181 AssertRC(rc2);
1182 }
1183
1184 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1185 }
1186
1187 LogFlowFuncLeaveRC(rc);
1188 return rc;
1189}
1190
1191int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1192{
1193 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1194
1195 int rc;
1196
1197 LogFlowFuncEnter();
1198
1199 GuestDnDResponse *pResp = pCtx->mpResp;
1200 AssertPtr(pCtx->mpResp);
1201
1202 GuestDnD *pInst = GUESTDNDINST();
1203 if (!pInst)
1204 return VERR_INVALID_POINTER;
1205
1206#define REGISTER_CALLBACK(x) \
1207 do { \
1208 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
1209 if (RT_FAILURE(rc)) \
1210 return rc; \
1211 } while (0)
1212
1213#define UNREGISTER_CALLBACK(x) \
1214 do { \
1215 int rc2 = pResp->setCallback(x, NULL); \
1216 AssertRC(rc2); \
1217 } while (0)
1218
1219 /*
1220 * Register callbacks.
1221 */
1222 /* Guest callbacks. */
1223 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1224 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1225 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1226 if (mDataBase.m_uProtocolVersion >= 3)
1227 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1228 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1229 REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1230 if (mDataBase.m_uProtocolVersion >= 2)
1231 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1232 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1233
1234 DnDDroppedFiles &droppedFiles = pCtx->mURI.getDroppedFiles();
1235
1236 do
1237 {
1238 rc = droppedFiles.OpenTemp(0 /* fFlags */);
1239 if (RT_FAILURE(rc))
1240 {
1241 LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n", droppedFiles.GetDirAbs(), rc));
1242 break;
1243 }
1244
1245 /*
1246 * Receive the URI list.
1247 */
1248 GuestDnDMsg Msg;
1249 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1250 if (mDataBase.m_uProtocolVersion >= 3)
1251 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1252 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1253 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1254 Msg.setNextUInt32(pCtx->mAction);
1255
1256 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1257 * the host and therefore now waiting for the actual URI data. */
1258 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1259 if (RT_SUCCESS(rc))
1260 {
1261 LogFlowFunc(("Waiting ...\n"));
1262
1263 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1264 if (RT_SUCCESS(rc))
1265 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1266
1267 LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
1268 }
1269
1270 } while (0);
1271
1272 /*
1273 * Unregister callbacks.
1274 */
1275 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1276 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1277 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1278 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1279 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1280 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1281 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1282 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1283
1284#undef REGISTER_CALLBACK
1285#undef UNREGISTER_CALLBACK
1286
1287 if (RT_FAILURE(rc))
1288 {
1289 int rc2 = droppedFiles.Rollback();
1290 if (RT_FAILURE(rc2))
1291 LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
1292 rc2, droppedFiles.GetDirAbs()));
1293
1294 if (rc == VERR_CANCELLED)
1295 {
1296 /*
1297 * Now that we've cleaned up tell the guest side to cancel.
1298 * This does not imply we're waiting for the guest to react, as the
1299 * host side never must depend on anything from the guest.
1300 */
1301 rc2 = sendCancel();
1302 AssertRC(rc2);
1303
1304 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1305 AssertRC(rc2);
1306 }
1307 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1308 {
1309 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1310 rc, GuestDnDSource::i_hostErrorToString(rc));
1311 AssertRC(rc2);
1312 }
1313
1314 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1315 }
1316
1317 droppedFiles.Close();
1318
1319 LogFlowFuncLeaveRC(rc);
1320 return rc;
1321}
1322
1323/* static */
1324DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1325{
1326 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1327 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1328
1329 GuestDnDSource *pThis = pCtx->mpSource;
1330 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1331
1332 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1333
1334 int rc = VINF_SUCCESS;
1335
1336 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1337 bool fNotify = false;
1338
1339 switch (uMsg)
1340 {
1341 case GUEST_DND_CONNECT:
1342 /* Nothing to do here (yet). */
1343 break;
1344
1345 case GUEST_DND_DISCONNECT:
1346 rc = VERR_CANCELLED;
1347 break;
1348
1349#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1350 case GUEST_DND_GH_SND_DATA_HDR:
1351 {
1352 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1353 AssertPtr(pCBData);
1354 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1355 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1356
1357 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1358 break;
1359 }
1360 case GUEST_DND_GH_SND_DATA:
1361 {
1362 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1363 AssertPtr(pCBData);
1364 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1365 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1366
1367 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1368 break;
1369 }
1370 case GUEST_DND_GH_EVT_ERROR:
1371 {
1372 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1373 AssertPtr(pCBData);
1374 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1375 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1376
1377 pCtx->mpResp->reset();
1378
1379 if (RT_SUCCESS(pCBData->rc))
1380 {
1381 AssertMsgFailed(("Received guest error with no error code set\n"));
1382 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1383 }
1384 else if (pCBData->rc == VERR_WRONG_ORDER)
1385 {
1386 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1387 }
1388 else
1389 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1390 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1391
1392 LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
1393
1394 if (RT_SUCCESS(rc))
1395 rcCallback = VERR_GSTDND_GUEST_ERROR;
1396 break;
1397 }
1398#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1399 default:
1400 rc = VERR_NOT_SUPPORTED;
1401 break;
1402 }
1403
1404 if ( RT_FAILURE(rc)
1405 || RT_FAILURE(rcCallback))
1406 {
1407 fNotify = true;
1408 if (RT_SUCCESS(rcCallback))
1409 rcCallback = rc;
1410 }
1411
1412 if (RT_FAILURE(rc))
1413 {
1414 switch (rc)
1415 {
1416 case VERR_NO_DATA:
1417 LogRel2(("DnD: Data transfer to host complete\n"));
1418 break;
1419
1420 case VERR_CANCELLED:
1421 LogRel2(("DnD: Data transfer to host canceled\n"));
1422 break;
1423
1424 default:
1425 LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
1426 break;
1427 }
1428
1429 /* Unregister this callback. */
1430 AssertPtr(pCtx->mpResp);
1431 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1432 AssertRC(rc2);
1433 }
1434
1435 /* All data processed? */
1436 if (pCtx->mData.isComplete())
1437 fNotify = true;
1438
1439 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1440 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1441
1442 if (fNotify)
1443 {
1444 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1445 AssertRC(rc2);
1446 }
1447
1448 LogFlowFuncLeaveRC(rc);
1449 return rc; /* Tell the guest. */
1450}
1451
1452/* static */
1453DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1454{
1455 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1456 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1457
1458 GuestDnDSource *pThis = pCtx->mpSource;
1459 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1460
1461 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1462
1463 int rc = VINF_SUCCESS;
1464
1465 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1466 bool fNotify = false;
1467
1468 switch (uMsg)
1469 {
1470 case GUEST_DND_CONNECT:
1471 /* Nothing to do here (yet). */
1472 break;
1473
1474 case GUEST_DND_DISCONNECT:
1475 rc = VERR_CANCELLED;
1476 break;
1477
1478#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1479 case GUEST_DND_GH_SND_DATA_HDR:
1480 {
1481 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1482 AssertPtr(pCBData);
1483 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1484 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1485
1486 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1487 break;
1488 }
1489 case GUEST_DND_GH_SND_DATA:
1490 {
1491 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1492 AssertPtr(pCBData);
1493 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1494 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1495
1496 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1497 break;
1498 }
1499 case GUEST_DND_GH_SND_DIR:
1500 {
1501 PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
1502 AssertPtr(pCBData);
1503 AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1504 AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1505
1506 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1507 break;
1508 }
1509 case GUEST_DND_GH_SND_FILE_HDR:
1510 {
1511 PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1512 AssertPtr(pCBData);
1513 AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1514 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1515
1516 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1517 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1518 break;
1519 }
1520 case GUEST_DND_GH_SND_FILE_DATA:
1521 {
1522 PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1523 AssertPtr(pCBData);
1524 AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1525 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1526
1527 if (pThis->mDataBase.m_uProtocolVersion <= 1)
1528 {
1529 /**
1530 * Notes for protocol v1 (< VBox 5.0):
1531 * - Every time this command is being sent it includes the file header,
1532 * so just process both calls here.
1533 * - There was no information whatsoever about the total file size; the old code only
1534 * appended data to the desired file. So just pass 0 as cbSize.
1535 */
1536 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1537 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1538 if (RT_SUCCESS(rc))
1539 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1540 }
1541 else /* Protocol v2 and up. */
1542 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1543 break;
1544 }
1545 case GUEST_DND_GH_EVT_ERROR:
1546 {
1547 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1548 AssertPtr(pCBData);
1549 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1550 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1551
1552 pCtx->mpResp->reset();
1553
1554 if (RT_SUCCESS(pCBData->rc))
1555 {
1556 AssertMsgFailed(("Received guest error with no error code set\n"));
1557 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1558 }
1559 else if (pCBData->rc == VERR_WRONG_ORDER)
1560 {
1561 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1562 }
1563 else
1564 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1565 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1566
1567 LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
1568
1569 if (RT_SUCCESS(rc))
1570 rcCallback = VERR_GSTDND_GUEST_ERROR;
1571 break;
1572 }
1573#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1574 default:
1575 rc = VERR_NOT_SUPPORTED;
1576 break;
1577 }
1578
1579 if ( RT_FAILURE(rc)
1580 || RT_FAILURE(rcCallback))
1581 {
1582 fNotify = true;
1583 if (RT_SUCCESS(rcCallback))
1584 rcCallback = rc;
1585 }
1586
1587 if (RT_FAILURE(rc))
1588 {
1589 switch (rc)
1590 {
1591 case VERR_NO_DATA:
1592 LogRel2(("DnD: File transfer to host complete\n"));
1593 break;
1594
1595 case VERR_CANCELLED:
1596 LogRel2(("DnD: File transfer to host canceled\n"));
1597 break;
1598
1599 default:
1600 LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
1601 break;
1602 }
1603
1604 /* Unregister this callback. */
1605 AssertPtr(pCtx->mpResp);
1606 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1607 AssertRC(rc2);
1608 }
1609
1610 /* All data processed? */
1611 if ( pCtx->mURI.isComplete()
1612 && pCtx->mData.isComplete())
1613 {
1614 fNotify = true;
1615 }
1616
1617 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1618 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1619
1620 if (fNotify)
1621 {
1622 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1623 AssertRC(rc2);
1624 }
1625
1626 LogFlowFuncLeaveRC(rc);
1627 return rc; /* Tell the guest. */
1628}
1629
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