VirtualBox

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

Last change on this file since 63165 was 63159, checked in by vboxsync, 9 years ago

Main: More uninitialized variable warnings.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette