VirtualBox

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

Last change on this file since 78126 was 78093, checked in by vboxsync, 6 years ago

DnD/Main: Renamed GuestDnDInst() macro -> GUESTDNDINST().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.8 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 78093 2019-04-10 15:11:29Z 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 Unescape 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 (pObj->IsComplete()) /* 0-byte file? We're done already. */
855 {
856 /** @todo Sanitize path. */
857 LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pObj->GetDestPathAbs().c_str()));
858
859 pCtx->mURI.processObject(*pObj);
860 pObj->Close();
861
862 objCtx.reset();
863 }
864 }
865
866 } while (0);
867
868 if (RT_FAILURE(rc))
869 LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc));
870
871 LogFlowFuncLeaveRC(rc);
872 return rc;
873}
874
875int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
876{
877 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
878 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
879 AssertReturn(cbData, VERR_INVALID_PARAMETER);
880
881 int rc = VINF_SUCCESS;
882
883 LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
884
885 /*
886 * Sanity checking.
887 */
888 if (cbData > mData.mcbBlockSize)
889 return VERR_INVALID_PARAMETER;
890
891 do
892 {
893 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
894 DnDURIObject *pObj = objCtx.getObj();
895
896 if (!pObj)
897 {
898 LogFlowFunc(("Warning: No current object set\n"));
899 rc = VERR_WRONG_ORDER;
900 break;
901 }
902
903 if (pObj->IsComplete())
904 {
905 LogFlowFunc(("Warning: Object '%s' already completed\n", pObj->GetDestPathAbs().c_str()));
906 rc = VERR_WRONG_ORDER;
907 break;
908 }
909
910 if (!pObj->IsOpen()) /* File opened on host? */
911 {
912 LogFlowFunc(("Warning: Object '%s' not opened\n", pObj->GetDestPathAbs().c_str()));
913 rc = VERR_WRONG_ORDER;
914 break;
915 }
916
917 uint32_t cbWritten;
918 rc = pObj->Write(pvData, cbData, &cbWritten);
919 if (RT_SUCCESS(rc))
920 {
921 Assert(cbWritten <= cbData);
922 if (cbWritten < cbData)
923 {
924 /** @todo What to do when the host's disk is full? */
925 rc = VERR_DISK_FULL;
926 }
927
928 if (RT_SUCCESS(rc))
929 rc = updateProgress(&pCtx->mData, pCtx->mpResp, cbWritten);
930 }
931 else
932 LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pObj->GetDestPathAbs().c_str(), rc));
933
934 if (RT_SUCCESS(rc))
935 {
936 if (pObj->IsComplete())
937 {
938 /** @todo Sanitize path. */
939 LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pObj->GetDestPathAbs().c_str()));
940 pCtx->mURI.processObject(*pObj);
941 objCtx.reset();
942 }
943 }
944
945 } while (0);
946
947 if (RT_FAILURE(rc))
948 LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc));
949
950 LogFlowFuncLeaveRC(rc);
951 return rc;
952}
953#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
954
955/**
956 * @returns VBox status code that the caller ignores. Not sure if that's
957 * intentional or not.
958 */
959int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
960{
961 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
962
963 GuestDnD *pInst = GUESTDNDINST();
964 if (!pInst)
965 return VERR_INVALID_POINTER;
966
967 GuestDnDResponse *pResp = pCtx->mpResp;
968 AssertPtr(pCtx->mpResp);
969
970 int rc = pCtx->mCBEvent.Reset();
971 if (RT_FAILURE(rc))
972 return rc;
973
974 /* Is this context already in receiving state? */
975 if (ASMAtomicReadBool(&pCtx->mIsActive))
976 return VERR_WRONG_ORDER;
977 ASMAtomicWriteBool(&pCtx->mIsActive, true);
978
979 /*
980 * Reset any old data.
981 */
982 pCtx->mData.reset();
983 pCtx->mURI.reset();
984 pResp->reset();
985
986 /*
987 * Do we need to receive a different format than initially requested?
988 *
989 * For example, receiving a file link as "text/plain" requires still to receive
990 * the file from the guest as "text/uri-list" first, then pointing to
991 * the file path on the host in the "text/plain" data returned.
992 */
993
994 bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
995
996 LogFlowFunc(("mFmtReq=%s, mFmtRecv=%s, mAction=0x%x\n",
997 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str(), pCtx->mAction));
998
999 /* Plain text wanted? */
1000 if ( pCtx->mFmtReq.equalsIgnoreCase("text/plain")
1001 || pCtx->mFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
1002 {
1003 /* Did the guest offer a file? Receive a file instead. */
1004 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1005 pCtx->mFmtRecv = "text/uri-list";
1006 /* Guest only offers (plain) text. */
1007 else
1008 pCtx->mFmtRecv = "text/plain;charset=utf-8";
1009
1010 /** @todo Add more conversions here. */
1011 }
1012 /* File(s) wanted? */
1013 else if (pCtx->mFmtReq.equalsIgnoreCase("text/uri-list"))
1014 {
1015 /* Does the guest support sending files? */
1016 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1017 pCtx->mFmtRecv = "text/uri-list";
1018 else /* Bail out. */
1019 fFoundFormat = false;
1020 }
1021
1022 if (fFoundFormat)
1023 {
1024 Assert(!pCtx->mFmtReq.isEmpty());
1025 Assert(!pCtx->mFmtRecv.isEmpty());
1026
1027 if (!pCtx->mFmtRecv.equals(pCtx->mFmtReq))
1028 LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
1029 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str()));
1030
1031 /*
1032 * Call the appropriate receive handler based on the data format to handle.
1033 */
1034 bool fURIData = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
1035 if (fURIData)
1036 {
1037 rc = i_receiveURIData(pCtx, msTimeout);
1038 }
1039 else
1040 {
1041 rc = i_receiveRawData(pCtx, msTimeout);
1042 }
1043 }
1044 else /* Just inform the user (if verbose release logging is enabled). */
1045 {
1046 LogRel(("DnD: The guest does not support format '%s':\n", pCtx->mFmtReq.c_str()));
1047 LogRel(("DnD: Guest offered the following formats:\n"));
1048 for (size_t i = 0; i < pCtx->mFmtOffered.size(); i++)
1049 LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->mFmtOffered.at(i).c_str()));
1050 }
1051
1052 ASMAtomicWriteBool(&pCtx->mIsActive, false);
1053
1054 LogFlowFuncLeaveRC(rc);
1055 return rc;
1056}
1057
1058/* static */
1059void GuestDnDSource::i_receiveDataThreadTask(RecvDataTask *pTask)
1060{
1061 LogFlowFunc(("pTask=%p\n", pTask));
1062 AssertPtrReturnVoid(pTask);
1063
1064 const ComObjPtr<GuestDnDSource> pThis(pTask->getSource());
1065 Assert(!pThis.isNull());
1066
1067 AutoCaller autoCaller(pThis);
1068 if (FAILED(autoCaller.rc()))
1069 return;
1070
1071 int vrc = pThis->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
1072 if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
1073 {
1074 AssertFailed();
1075 LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
1076 }
1077
1078 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
1079
1080 Assert(pThis->mDataBase.m_cTransfersPending);
1081 if (pThis->mDataBase.m_cTransfersPending)
1082 pThis->mDataBase.m_cTransfersPending--;
1083
1084 LogFlowFunc(("pSource=%p, vrc=%Rrc (ignored)\n", (GuestDnDSource *)pThis, vrc));
1085}
1086
1087int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1088{
1089 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1090
1091 int rc;
1092
1093 LogFlowFuncEnter();
1094
1095 GuestDnDResponse *pResp = pCtx->mpResp;
1096 AssertPtr(pCtx->mpResp);
1097
1098 GuestDnD *pInst = GUESTDNDINST();
1099 if (!pInst)
1100 return VERR_INVALID_POINTER;
1101
1102#define REGISTER_CALLBACK(x) \
1103 do { \
1104 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
1105 if (RT_FAILURE(rc)) \
1106 return rc; \
1107 } while (0)
1108
1109#define UNREGISTER_CALLBACK(x) \
1110 do { \
1111 int rc2 = pResp->setCallback(x, NULL); \
1112 AssertRC(rc2); \
1113 } while (0)
1114
1115 /*
1116 * Register callbacks.
1117 */
1118 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1119 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1120 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1121 if (mDataBase.m_uProtocolVersion >= 3)
1122 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1123 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1124
1125 do
1126 {
1127 /*
1128 * Receive the raw data.
1129 */
1130 GuestDnDMsg Msg;
1131 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1132 if (mDataBase.m_uProtocolVersion >= 3)
1133 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1134 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1135 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1136 Msg.setNextUInt32(pCtx->mAction);
1137
1138 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1139 * the host and therefore now waiting for the actual raw data. */
1140 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1141 if (RT_SUCCESS(rc))
1142 {
1143 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1144 if (RT_SUCCESS(rc))
1145 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1146 }
1147
1148 } while (0);
1149
1150 /*
1151 * Unregister callbacks.
1152 */
1153 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1154 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1155 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1156 if (mDataBase.m_uProtocolVersion >= 3)
1157 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1158 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1159
1160#undef REGISTER_CALLBACK
1161#undef UNREGISTER_CALLBACK
1162
1163 if (RT_FAILURE(rc))
1164 {
1165 if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1166 {
1167 /*
1168 * Now that we've cleaned up tell the guest side to cancel.
1169 * This does not imply we're waiting for the guest to react, as the
1170 * host side never must depend on anything from the guest.
1171 */
1172 int rc2 = sendCancel();
1173 AssertRC(rc2);
1174
1175 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1176 AssertRC(rc2);
1177 }
1178 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1179 {
1180 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1181 rc, GuestDnDSource::i_hostErrorToString(rc));
1182 AssertRC(rc2);
1183 }
1184
1185 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1186 }
1187
1188 LogFlowFuncLeaveRC(rc);
1189 return rc;
1190}
1191
1192int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1193{
1194 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1195
1196 int rc;
1197
1198 LogFlowFuncEnter();
1199
1200 GuestDnDResponse *pResp = pCtx->mpResp;
1201 AssertPtr(pCtx->mpResp);
1202
1203 GuestDnD *pInst = GUESTDNDINST();
1204 if (!pInst)
1205 return VERR_INVALID_POINTER;
1206
1207#define REGISTER_CALLBACK(x) \
1208 do { \
1209 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
1210 if (RT_FAILURE(rc)) \
1211 return rc; \
1212 } while (0)
1213
1214#define UNREGISTER_CALLBACK(x) \
1215 do { \
1216 int rc2 = pResp->setCallback(x, NULL); \
1217 AssertRC(rc2); \
1218 } while (0)
1219
1220 /*
1221 * Register callbacks.
1222 */
1223 /* Guest callbacks. */
1224 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1225 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1226 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1227 if (mDataBase.m_uProtocolVersion >= 3)
1228 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1229 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1230 REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1231 if (mDataBase.m_uProtocolVersion >= 2)
1232 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1233 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1234
1235 DnDDroppedFiles &droppedFiles = pCtx->mURI.getDroppedFiles();
1236
1237 do
1238 {
1239 rc = droppedFiles.OpenTemp(0 /* fFlags */);
1240 if (RT_FAILURE(rc))
1241 {
1242 LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n", droppedFiles.GetDirAbs(), rc));
1243 break;
1244 }
1245
1246 /*
1247 * Receive the URI list.
1248 */
1249 GuestDnDMsg Msg;
1250 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1251 if (mDataBase.m_uProtocolVersion >= 3)
1252 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1253 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1254 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1255 Msg.setNextUInt32(pCtx->mAction);
1256
1257 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1258 * the host and therefore now waiting for the actual URI data. */
1259 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1260 if (RT_SUCCESS(rc))
1261 {
1262 LogFlowFunc(("Waiting ...\n"));
1263
1264 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1265 if (RT_SUCCESS(rc))
1266 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1267
1268 LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
1269 }
1270
1271 } while (0);
1272
1273 /*
1274 * Unregister callbacks.
1275 */
1276 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1277 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1278 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1279 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1280 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1281 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1282 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1283 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1284
1285#undef REGISTER_CALLBACK
1286#undef UNREGISTER_CALLBACK
1287
1288 if (RT_FAILURE(rc))
1289 {
1290 int rc2 = droppedFiles.Rollback();
1291 if (RT_FAILURE(rc2))
1292 LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
1293 rc2, droppedFiles.GetDirAbs()));
1294
1295 if (rc == VERR_CANCELLED)
1296 {
1297 /*
1298 * Now that we've cleaned up tell the guest side to cancel.
1299 * This does not imply we're waiting for the guest to react, as the
1300 * host side never must depend on anything from the guest.
1301 */
1302 rc2 = sendCancel();
1303 AssertRC(rc2);
1304
1305 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1306 AssertRC(rc2);
1307 }
1308 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1309 {
1310 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1311 rc, GuestDnDSource::i_hostErrorToString(rc));
1312 AssertRC(rc2);
1313 }
1314
1315 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1316 }
1317
1318 droppedFiles.Close();
1319
1320 LogFlowFuncLeaveRC(rc);
1321 return rc;
1322}
1323
1324/* static */
1325DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1326{
1327 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1328 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1329
1330 GuestDnDSource *pThis = pCtx->mpSource;
1331 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1332
1333 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1334
1335 int rc = VINF_SUCCESS;
1336
1337 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1338 bool fNotify = false;
1339
1340 switch (uMsg)
1341 {
1342 case GUEST_DND_CONNECT:
1343 /* Nothing to do here (yet). */
1344 break;
1345
1346 case GUEST_DND_DISCONNECT:
1347 rc = VERR_CANCELLED;
1348 break;
1349
1350#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1351 case GUEST_DND_GH_SND_DATA_HDR:
1352 {
1353 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1354 AssertPtr(pCBData);
1355 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1356 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1357
1358 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1359 break;
1360 }
1361 case GUEST_DND_GH_SND_DATA:
1362 {
1363 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1364 AssertPtr(pCBData);
1365 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1366 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1367
1368 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1369 break;
1370 }
1371 case GUEST_DND_GH_EVT_ERROR:
1372 {
1373 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1374 AssertPtr(pCBData);
1375 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1376 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1377
1378 pCtx->mpResp->reset();
1379
1380 if (RT_SUCCESS(pCBData->rc))
1381 {
1382 AssertMsgFailed(("Received guest error with no error code set\n"));
1383 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1384 }
1385 else if (pCBData->rc == VERR_WRONG_ORDER)
1386 {
1387 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1388 }
1389 else
1390 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1391 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1392
1393 LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
1394
1395 if (RT_SUCCESS(rc))
1396 rcCallback = VERR_GSTDND_GUEST_ERROR;
1397 break;
1398 }
1399#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1400 default:
1401 rc = VERR_NOT_SUPPORTED;
1402 break;
1403 }
1404
1405 if ( RT_FAILURE(rc)
1406 || RT_FAILURE(rcCallback))
1407 {
1408 fNotify = true;
1409 if (RT_SUCCESS(rcCallback))
1410 rcCallback = rc;
1411 }
1412
1413 if (RT_FAILURE(rc))
1414 {
1415 switch (rc)
1416 {
1417 case VERR_NO_DATA:
1418 LogRel2(("DnD: Data transfer to host complete\n"));
1419 break;
1420
1421 case VERR_CANCELLED:
1422 LogRel2(("DnD: Data transfer to host canceled\n"));
1423 break;
1424
1425 default:
1426 LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
1427 break;
1428 }
1429
1430 /* Unregister this callback. */
1431 AssertPtr(pCtx->mpResp);
1432 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1433 AssertRC(rc2);
1434 }
1435
1436 /* All data processed? */
1437 if (pCtx->mData.isComplete())
1438 fNotify = true;
1439
1440 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1441 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1442
1443 if (fNotify)
1444 {
1445 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1446 AssertRC(rc2);
1447 }
1448
1449 LogFlowFuncLeaveRC(rc);
1450 return rc; /* Tell the guest. */
1451}
1452
1453/* static */
1454DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1455{
1456 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1457 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1458
1459 GuestDnDSource *pThis = pCtx->mpSource;
1460 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1461
1462 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1463
1464 int rc = VINF_SUCCESS;
1465
1466 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1467 bool fNotify = false;
1468
1469 switch (uMsg)
1470 {
1471 case GUEST_DND_CONNECT:
1472 /* Nothing to do here (yet). */
1473 break;
1474
1475 case GUEST_DND_DISCONNECT:
1476 rc = VERR_CANCELLED;
1477 break;
1478
1479#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1480 case GUEST_DND_GH_SND_DATA_HDR:
1481 {
1482 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1483 AssertPtr(pCBData);
1484 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1485 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1486
1487 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1488 break;
1489 }
1490 case GUEST_DND_GH_SND_DATA:
1491 {
1492 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1493 AssertPtr(pCBData);
1494 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1495 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1496
1497 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1498 break;
1499 }
1500 case GUEST_DND_GH_SND_DIR:
1501 {
1502 PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
1503 AssertPtr(pCBData);
1504 AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1505 AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1506
1507 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1508 break;
1509 }
1510 case GUEST_DND_GH_SND_FILE_HDR:
1511 {
1512 PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1513 AssertPtr(pCBData);
1514 AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1515 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1516
1517 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1518 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1519 break;
1520 }
1521 case GUEST_DND_GH_SND_FILE_DATA:
1522 {
1523 PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1524 AssertPtr(pCBData);
1525 AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1526 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1527
1528 if (pThis->mDataBase.m_uProtocolVersion <= 1)
1529 {
1530 /**
1531 * Notes for protocol v1 (< VBox 5.0):
1532 * - Every time this command is being sent it includes the file header,
1533 * so just process both calls here.
1534 * - There was no information whatsoever about the total file size; the old code only
1535 * appended data to the desired file. So just pass 0 as cbSize.
1536 */
1537 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1538 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1539 if (RT_SUCCESS(rc))
1540 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1541 }
1542 else /* Protocol v2 and up. */
1543 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1544 break;
1545 }
1546 case GUEST_DND_GH_EVT_ERROR:
1547 {
1548 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1549 AssertPtr(pCBData);
1550 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1551 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1552
1553 pCtx->mpResp->reset();
1554
1555 if (RT_SUCCESS(pCBData->rc))
1556 {
1557 AssertMsgFailed(("Received guest error with no error code set\n"));
1558 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1559 }
1560 else if (pCBData->rc == VERR_WRONG_ORDER)
1561 {
1562 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1563 }
1564 else
1565 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1566 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1567
1568 LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
1569
1570 if (RT_SUCCESS(rc))
1571 rcCallback = VERR_GSTDND_GUEST_ERROR;
1572 break;
1573 }
1574#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1575 default:
1576 rc = VERR_NOT_SUPPORTED;
1577 break;
1578 }
1579
1580 if ( RT_FAILURE(rc)
1581 || RT_FAILURE(rcCallback))
1582 {
1583 fNotify = true;
1584 if (RT_SUCCESS(rcCallback))
1585 rcCallback = rc;
1586 }
1587
1588 if (RT_FAILURE(rc))
1589 {
1590 switch (rc)
1591 {
1592 case VERR_NO_DATA:
1593 LogRel2(("DnD: File transfer to host complete\n"));
1594 break;
1595
1596 case VERR_CANCELLED:
1597 LogRel2(("DnD: File transfer to host canceled\n"));
1598 break;
1599
1600 default:
1601 LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
1602 break;
1603 }
1604
1605 /* Unregister this callback. */
1606 AssertPtr(pCtx->mpResp);
1607 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1608 AssertRC(rc2);
1609 }
1610
1611 /* All data processed? */
1612 if ( pCtx->mURI.isComplete()
1613 && pCtx->mData.isComplete())
1614 {
1615 fNotify = true;
1616 }
1617
1618 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1619 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1620
1621 if (fNotify)
1622 {
1623 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1624 AssertRC(rc2);
1625 }
1626
1627 LogFlowFuncLeaveRC(rc);
1628 return rc; /* Tell the guest. */
1629}
1630
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