VirtualBox

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

Last change on this file since 63471 was 63471, checked in by vboxsync, 8 years ago

DnD/Main: Resolved worker thread @todos.

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