VirtualBox

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

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

Build fix.

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