VirtualBox

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

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

build fix

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