VirtualBox

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

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

backed out r108916

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