VirtualBox

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

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

Main: warnings

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