VirtualBox

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

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

ThreadTask: split createThread up into three methods to avoid having to pass NULL all the time when the type needs specifying. Also, explictly marked the racy variant.

  • 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 63182 2016-08-08 16:16:42Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag and drop source.
4 */
5
6/*
7 * Copyright (C) 2014-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include "GuestImpl.h"
23#include "GuestDnDSourceImpl.h"
24#include "GuestDnDPrivate.h"
25#include "ConsoleImpl.h"
26
27#include "Global.h"
28#include "AutoCaller.h"
29#include "ThreadTask.h"
30
31#include <iprt/asm.h>
32#include <iprt/dir.h>
33#include <iprt/file.h>
34#include <iprt/path.h>
35#include <iprt/uri.h>
36
37#include <iprt/cpp/utils.h> /* For unconst(). */
38
39#include <VBox/com/array.h>
40
41#ifdef LOG_GROUP
42 #undef LOG_GROUP
43#endif
44#define LOG_GROUP LOG_GROUP_GUEST_DND
45#include <VBox/log.h>
46
47/**
48 * Base class for a source task.
49 */
50class GuestDnDSourceTask : public ThreadTask
51{
52public:
53
54 GuestDnDSourceTask(GuestDnDSource *pSource)
55 : ThreadTask("GenericGuestDnDSourceTask")
56 , mSource(pSource)
57 , mRC(VINF_SUCCESS) { }
58
59 virtual ~GuestDnDSourceTask(void) { }
60
61 int getRC(void) const { return mRC; }
62 bool isOk(void) const { return RT_SUCCESS(mRC); }
63 const ComObjPtr<GuestDnDSource> &getSource(void) const { return mSource; }
64
65protected:
66
67 const ComObjPtr<GuestDnDSource> mSource;
68 int mRC;
69};
70
71/**
72 * Task structure for receiving data from a source using
73 * a worker thread.
74 */
75class RecvDataTask : public GuestDnDSourceTask
76{
77public:
78
79 RecvDataTask(GuestDnDSource *pSource, PRECVDATACTX pCtx)
80 : GuestDnDSourceTask(pSource)
81 , mpCtx(pCtx)
82 {
83 m_strTaskName = "dndSrcRcvData";
84 }
85
86 void handler()
87 {
88 int vrc = GuestDnDSource::i_receiveDataThread(*m_pThread, this);
89 }
90
91 virtual ~RecvDataTask(void) { }
92
93 PRECVDATACTX getCtx(void) { return mpCtx; }
94
95protected:
96
97 /** Pointer to receive data context. */
98 PRECVDATACTX mpCtx;
99};
100
101// constructor / destructor
102/////////////////////////////////////////////////////////////////////////////
103
104DEFINE_EMPTY_CTOR_DTOR(GuestDnDSource)
105
106HRESULT GuestDnDSource::FinalConstruct(void)
107{
108 /*
109 * Set the maximum block size this source can handle to 64K. This always has
110 * been hardcoded until now.
111 *
112 * Note: Never ever rely on information from the guest; the host dictates what and
113 * how to do something, so try to negogiate a sensible value here later.
114 */
115 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
116
117 LogFlowThisFunc(("\n"));
118 return BaseFinalConstruct();
119}
120
121void GuestDnDSource::FinalRelease(void)
122{
123 LogFlowThisFuncEnter();
124 uninit();
125 BaseFinalRelease();
126 LogFlowThisFuncLeave();
127}
128
129// public initializer/uninitializer for internal purposes only
130/////////////////////////////////////////////////////////////////////////////
131
132int GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
133{
134 LogFlowThisFuncEnter();
135
136 /* Enclose the state transition NotReady->InInit->Ready. */
137 AutoInitSpan autoInitSpan(this);
138 AssertReturn(autoInitSpan.isOk(), E_FAIL);
139
140 unconst(m_pGuest) = pGuest;
141
142 /* Confirm a successful initialization when it's the case. */
143 autoInitSpan.setSucceeded();
144
145 return VINF_SUCCESS;
146}
147
148/**
149 * Uninitializes the instance.
150 * Called from FinalRelease().
151 */
152void GuestDnDSource::uninit(void)
153{
154 LogFlowThisFunc(("\n"));
155
156 /* Enclose the state transition Ready->InUninit->NotReady. */
157 AutoUninitSpan autoUninitSpan(this);
158 if (autoUninitSpan.uninitDone())
159 return;
160}
161
162// implementation of wrapped IDnDBase methods.
163/////////////////////////////////////////////////////////////////////////////
164
165HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
166{
167#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
168 ReturnComNotImplemented();
169#else /* VBOX_WITH_DRAG_AND_DROP */
170
171 AutoCaller autoCaller(this);
172 if (FAILED(autoCaller.rc())) return autoCaller.rc();
173
174 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
175
176 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
177#endif /* VBOX_WITH_DRAG_AND_DROP */
178}
179
180HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats)
181{
182#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
183 ReturnComNotImplemented();
184#else /* VBOX_WITH_DRAG_AND_DROP */
185
186 AutoCaller autoCaller(this);
187 if (FAILED(autoCaller.rc())) return autoCaller.rc();
188
189 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
190
191 return GuestDnDBase::i_getFormats(aFormats);
192#endif /* VBOX_WITH_DRAG_AND_DROP */
193}
194
195HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats)
196{
197#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
198 ReturnComNotImplemented();
199#else /* VBOX_WITH_DRAG_AND_DROP */
200
201 AutoCaller autoCaller(this);
202 if (FAILED(autoCaller.rc())) return autoCaller.rc();
203
204 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
205
206 return GuestDnDBase::i_addFormats(aFormats);
207#endif /* VBOX_WITH_DRAG_AND_DROP */
208}
209
210HRESULT GuestDnDSource::removeFormats(const GuestDnDMIMEList &aFormats)
211{
212#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
213 ReturnComNotImplemented();
214#else /* VBOX_WITH_DRAG_AND_DROP */
215
216 AutoCaller autoCaller(this);
217 if (FAILED(autoCaller.rc())) return autoCaller.rc();
218
219 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
220
221 return GuestDnDBase::i_removeFormats(aFormats);
222#endif /* VBOX_WITH_DRAG_AND_DROP */
223}
224
225HRESULT GuestDnDSource::getProtocolVersion(ULONG *aProtocolVersion)
226{
227#if !defined(VBOX_WITH_DRAG_AND_DROP)
228 ReturnComNotImplemented();
229#else /* VBOX_WITH_DRAG_AND_DROP */
230
231 AutoCaller autoCaller(this);
232 if (FAILED(autoCaller.rc())) return autoCaller.rc();
233
234 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
235
236 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
237#endif /* VBOX_WITH_DRAG_AND_DROP */
238}
239
240// implementation of wrapped IDnDSource methods.
241/////////////////////////////////////////////////////////////////////////////
242
243HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats,
244 std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
245{
246#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
247 ReturnComNotImplemented();
248#else /* VBOX_WITH_DRAG_AND_DROP */
249
250 /* aDefaultAction is optional. */
251
252 AutoCaller autoCaller(this);
253 if (FAILED(autoCaller.rc())) return autoCaller.rc();
254
255 /* Determine guest DnD protocol to use. */
256 GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion);
257
258 /* Default is ignoring the action. */
259 if (aDefaultAction)
260 *aDefaultAction = DnDAction_Ignore;
261
262 HRESULT hr = S_OK;
263
264 GuestDnDMsg Msg;
265 Msg.setType(HOST_DND_GH_REQ_PENDING);
266 if (mDataBase.m_uProtocolVersion >= 3)
267 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
268 Msg.setNextUInt32(uScreenId);
269
270 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
271 if (RT_SUCCESS(rc))
272 {
273 GuestDnDResponse *pResp = GuestDnDInst()->response();
274 AssertPtr(pResp);
275
276 bool fFetchResult = true;
277
278 rc = pResp->waitForGuestResponse(100 /* Timeout in ms */);
279 if (RT_FAILURE(rc))
280 fFetchResult = false;
281
282 if ( fFetchResult
283 && isDnDIgnoreAction(pResp->defAction()))
284 fFetchResult = false;
285
286 /* Fetch the default action to use. */
287 if (fFetchResult)
288 {
289 /*
290 * In the GuestDnDSource case the source formats are from the guest,
291 * as GuestDnDSource acts as a target for the guest. The host always
292 * dictates what's supported and what's not, so filter out all formats
293 * which are not supported by the host.
294 */
295 GuestDnDMIMEList lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, pResp->formats());
296 if (lstFiltered.size())
297 {
298 LogRel3(("DnD: Host offered the following formats:\n"));
299 for (size_t i = 0; i < lstFiltered.size(); i++)
300 LogRel3(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str()));
301
302 aFormats = lstFiltered;
303 aAllowedActions = GuestDnD::toMainActions(pResp->allActions());
304 if (aDefaultAction)
305 *aDefaultAction = GuestDnD::toMainAction(pResp->defAction());
306
307 /* Apply the (filtered) formats list. */
308 m_lstFmtOffered = lstFiltered;
309 }
310 else
311 LogRel2(("DnD: Negotiation of formats between guest and host failed, drag and drop to host not possible\n"));
312 }
313
314 LogFlowFunc(("fFetchResult=%RTbool, allActions=0x%x\n", fFetchResult, pResp->allActions()));
315 }
316
317 LogFlowFunc(("hr=%Rhrc\n", hr));
318 return hr;
319#endif /* VBOX_WITH_DRAG_AND_DROP */
320}
321
322HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
323{
324#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
325 ReturnComNotImplemented();
326#else /* VBOX_WITH_DRAG_AND_DROP */
327
328 AutoCaller autoCaller(this);
329 if (FAILED(autoCaller.rc())) return autoCaller.rc();
330
331 LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction));
332
333 /* Input validation. */
334 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
335 return setError(E_INVALIDARG, tr("No drop format specified"));
336
337 /* Is the specified format in our list of (left over) offered formats? */
338 if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered))
339 return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str());
340
341 uint32_t uAction = GuestDnD::toHGCMAction(aAction);
342 if (isDnDIgnoreAction(uAction)) /* If there is no usable action, ignore this request. */
343 return S_OK;
344
345 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
346
347 /* At the moment we only support one transfer at a time. */
348 if (mDataBase.m_cTransfersPending)
349 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
350
351 /* Dito. */
352 GuestDnDResponse *pResp = GuestDnDInst()->response();
353 AssertPtr(pResp);
354
355 HRESULT hr = pResp->resetProgress(m_pGuest);
356 if (FAILED(hr))
357 return hr;
358
359 RecvDataTask *pTask = NULL;
360 RTTHREAD hThreadRcv;
361 int rc = S_OK;
362
363 try
364 {
365 mData.mRecvCtx.mIsActive = false;
366 mData.mRecvCtx.mpSource = this;
367 mData.mRecvCtx.mpResp = pResp;
368 mData.mRecvCtx.mFmtReq = aFormat;
369 mData.mRecvCtx.mFmtOffered = m_lstFmtOffered;
370
371 LogRel2(("DnD: Requesting data from guest in format: %s\n", aFormat.c_str()));
372
373 pTask = new RecvDataTask(this, &mData.mRecvCtx);
374 if (!pTask->isOk())
375 {
376 delete pTask;
377 LogRel2(("DnD: Could not create RecvDataTask object \n"));
378 throw hr = E_FAIL;
379 }
380
381 /* This function delete pTask in case of exceptions,
382 * so there is no need in the call of delete operator. */
383/** @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 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
753 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
754 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
755 AssertReturn(fMode, VERR_INVALID_PARAMETER);
756 /* fFlags are optional. */
757
758 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
759
760 /*
761 * Sanity checking.
762 */
763 if ( !cbPath
764 || cbPath > RTPATH_MAX)
765 {
766 return VERR_INVALID_PARAMETER;
767 }
768
769 if (!RTStrIsValidEncoding(pszPath))
770 return VERR_INVALID_PARAMETER;
771
772 if (cbSize > pCtx->mData.getTotal())
773 {
774 AssertMsgFailed(("File size (%RU64) exceeds total size to transfer (%RU64)\n", cbSize, pCtx->mData.getTotal()));
775 return VERR_INVALID_PARAMETER;
776 }
777
778 if (pCtx->mURI.getObjToProcess() && pCtx->mURI.isComplete())
779 return VERR_INVALID_PARAMETER;
780
781 int rc = VINF_SUCCESS;
782
783 do
784 {
785 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
786 DnDURIObject *pObj = objCtx.getObj();
787
788 /*
789 * Sanity checking.
790 */
791 if (pObj)
792 {
793 if ( pObj->IsOpen()
794 && !pObj->IsComplete())
795 {
796 AssertMsgFailed(("Object '%s' not complete yet\n", pObj->GetDestPath().c_str()));
797 rc = VERR_WRONG_ORDER;
798 break;
799 }
800
801 if (pObj->IsOpen()) /* File already opened? */
802 {
803 AssertMsgFailed(("Current opened object is '%s', close this first\n", pObj->GetDestPath().c_str()));
804 rc = VERR_WRONG_ORDER;
805 break;
806 }
807 }
808 else
809 {
810 /*
811 * Create new intermediate object to work with.
812 */
813 rc = objCtx.createIntermediate();
814 }
815
816 if (RT_SUCCESS(rc))
817 {
818 pObj = objCtx.getObj();
819 AssertPtr(pObj);
820
821 const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs();
822 AssertPtr(pszDroppedFilesDir);
823
824 char pszPathAbs[RTPATH_MAX];
825 rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), pszDroppedFilesDir, pszPath);
826 if (RT_FAILURE(rc))
827 {
828 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
829 break;
830 }
831
832 rc = DnDPathSanitize(pszPathAbs, sizeof(pszPathAbs));
833 if (RT_FAILURE(rc))
834 {
835 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
836 break;
837 }
838
839 LogFunc(("Rebased to: %s\n", pszPathAbs));
840
841 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
842 rc = pObj->OpenEx(pszPathAbs, DnDURIObject::File, DnDURIObject::Target,
843 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
844 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
845 if (RT_SUCCESS(rc))
846 {
847 /* Add for having a proper rollback. */
848 int rc2 = pCtx->mURI.getDroppedFiles().AddFile(pszPathAbs);
849 AssertRC(rc2);
850 }
851 }
852
853 if (RT_SUCCESS(rc))
854 {
855 /* Note: Protocol v1 does not send any file sizes, so always 0. */
856 if (mDataBase.m_uProtocolVersion >= 2)
857 rc = pObj->SetSize(cbSize);
858
859 /** @todo Unescpae path before printing. */
860 LogRel2(("DnD: Transferring guest file to host: %s (%RU64 bytes, mode 0x%x)\n",
861 pObj->GetDestPath().c_str(), pObj->GetSize(), pObj->GetMode()));
862
863 /** @todo Set progress object title to current file being transferred? */
864
865 if (!cbSize) /* 0-byte file? Close again. */
866 pObj->Close();
867 }
868
869 if (RT_FAILURE(rc))
870 {
871 LogRel2(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n",
872 pObj->GetDestPath().c_str(), rc));
873 break;
874 }
875
876 } while (0);
877
878 LogFlowFuncLeaveRC(rc);
879 return rc;
880}
881
882int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
883{
884 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
885 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
886 AssertReturn(cbData, VERR_INVALID_PARAMETER);
887
888 int rc = VINF_SUCCESS;
889
890 LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
891
892 /*
893 * Sanity checking.
894 */
895 if (cbData > mData.mcbBlockSize)
896 return VERR_INVALID_PARAMETER;
897
898 do
899 {
900 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
901 DnDURIObject *pObj = objCtx.getObj();
902
903 if (!pObj)
904 {
905 LogFlowFunc(("Warning: No current object set\n"));
906 rc = VERR_WRONG_ORDER;
907 break;
908 }
909
910 if (pObj->IsComplete())
911 {
912 LogFlowFunc(("Warning: Object '%s' already completed\n", pObj->GetDestPath().c_str()));
913 rc = VERR_WRONG_ORDER;
914 break;
915 }
916
917 if (!pObj->IsOpen()) /* File opened on host? */
918 {
919 LogFlowFunc(("Warning: Object '%s' not opened\n", pObj->GetDestPath().c_str()));
920 rc = VERR_WRONG_ORDER;
921 break;
922 }
923
924 uint32_t cbWritten;
925 rc = pObj->Write(pvData, cbData, &cbWritten);
926 if (RT_SUCCESS(rc))
927 {
928 Assert(cbWritten <= cbData);
929 if (cbWritten < cbData)
930 {
931 /** @todo What to do when the host's disk is full? */
932 rc = VERR_DISK_FULL;
933 }
934
935 if (RT_SUCCESS(rc))
936 rc = updateProgress(&pCtx->mData, pCtx->mpResp, cbWritten);
937 }
938 else /* Something went wrong; close the object. */
939 pObj->Close();
940
941 if (RT_SUCCESS(rc))
942 {
943 if (pObj->IsComplete())
944 {
945 /** @todo Sanitize path. */
946 LogRel2(("DnD: File transfer to host complete: %s\n", pObj->GetDestPath().c_str()));
947 pCtx->mURI.processObject(*pObj);
948 objCtx.reset();
949 }
950 }
951 else
952 {
953 /** @todo What to do when the host's disk is full? */
954 LogRel(("DnD: Error writing guest file to host to '%s': %Rrc\n", pObj->GetDestPath().c_str(), rc));
955 }
956
957 } while (0);
958
959 LogFlowFuncLeaveRC(rc);
960 return rc;
961}
962#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
963
964int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
965{
966 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
967
968 /* Is this context already in receiving state? */
969 if (ASMAtomicReadBool(&pCtx->mIsActive))
970 return VERR_WRONG_ORDER;
971 ASMAtomicWriteBool(&pCtx->mIsActive, true);
972
973 GuestDnD *pInst = GuestDnDInst();
974 if (!pInst)
975 return VERR_INVALID_POINTER;
976
977 GuestDnDResponse *pResp = pCtx->mpResp;
978 AssertPtr(pCtx->mpResp);
979
980 int rc = pCtx->mCBEvent.Reset();
981 if (RT_FAILURE(rc))
982 return rc;
983
984 /*
985 * Reset any old data.
986 */
987 pCtx->mData.reset();
988 pCtx->mURI.reset();
989 pResp->reset();
990
991 /*
992 * Do we need to receive a different format than initially requested?
993 *
994 * For example, receiving a file link as "text/plain" requires still to receive
995 * the file from the guest as "text/uri-list" first, then pointing to
996 * the file path on the host in the "text/plain" data returned.
997 */
998
999 bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
1000
1001 LogFlowFunc(("mFmtReq=%s, mFmtRecv=%s, mAction=0x%x\n",
1002 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str(), pCtx->mAction));
1003
1004 /* Plain text wanted? */
1005 if ( pCtx->mFmtReq.equalsIgnoreCase("text/plain")
1006 || pCtx->mFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
1007 {
1008 /* Did the guest offer a file? Receive a file instead. */
1009 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1010 pCtx->mFmtRecv = "text/uri-list";
1011 /* Guest only offers (plain) text. */
1012 else
1013 pCtx->mFmtRecv = "text/plain;charset=utf-8";
1014
1015 /** @todo Add more conversions here. */
1016 }
1017 /* File(s) wanted? */
1018 else if (pCtx->mFmtReq.equalsIgnoreCase("text/uri-list"))
1019 {
1020 /* Does the guest support sending files? */
1021 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1022 pCtx->mFmtRecv = "text/uri-list";
1023 else /* Bail out. */
1024 fFoundFormat = false;
1025 }
1026
1027 if (fFoundFormat)
1028 {
1029 Assert(!pCtx->mFmtReq.isEmpty());
1030 Assert(!pCtx->mFmtRecv.isEmpty());
1031
1032 if (!pCtx->mFmtRecv.equals(pCtx->mFmtReq))
1033 LogRel3(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
1034 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str()));
1035
1036 /*
1037 * Call the appropriate receive handler based on the data format to handle.
1038 */
1039 bool fURIData = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
1040 if (fURIData)
1041 {
1042 rc = i_receiveURIData(pCtx, msTimeout);
1043 }
1044 else
1045 {
1046 rc = i_receiveRawData(pCtx, msTimeout);
1047 }
1048 }
1049 else /* Just inform the user (if verbose release logging is enabled). */
1050 {
1051 LogRel2(("DnD: The guest does not support format '%s':\n", pCtx->mFmtReq.c_str()));
1052 LogRel2(("DnD: Guest offered the following formats:\n"));
1053 for (size_t i = 0; i < pCtx->mFmtOffered.size(); i++)
1054 LogRel2(("DnD:\tFormat #%zu: %s\n", i, pCtx->mFmtOffered.at(i).c_str()));
1055 }
1056
1057 ASMAtomicWriteBool(&pCtx->mIsActive, false);
1058
1059 LogFlowFuncLeaveRC(rc);
1060 return rc;
1061}
1062
1063/* static */
1064DECLCALLBACK(int) GuestDnDSource::i_receiveDataThread(RTTHREAD Thread, void *pvUser)
1065{
1066 LogFlowFunc(("pvUser=%p\n", pvUser));
1067
1068 RecvDataTask *pTask = (RecvDataTask *)pvUser;
1069 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
1070
1071 const ComObjPtr<GuestDnDSource> pThis(pTask->getSource());
1072 Assert(!pThis.isNull());
1073
1074 AutoCaller autoCaller(pThis);
1075 if (FAILED(autoCaller.rc())) return VERR_COM_INVALID_OBJECT_STATE;
1076
1077 int rc = RTThreadUserSignal(Thread);
1078 AssertRC(rc);
1079
1080 rc = pThis->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
1081
1082 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
1083
1084 Assert(pThis->mDataBase.m_cTransfersPending);
1085 pThis->mDataBase.m_cTransfersPending--;
1086
1087 LogFlowFunc(("pSource=%p returning rc=%Rrc\n", (GuestDnDSource *)pThis, rc));
1088 return rc;
1089}
1090
1091int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1092{
1093 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1094
1095 int rc;
1096
1097 LogFlowFuncEnter();
1098
1099 GuestDnDResponse *pResp = pCtx->mpResp;
1100 AssertPtr(pCtx->mpResp);
1101
1102 GuestDnD *pInst = GuestDnDInst();
1103 if (!pInst)
1104 return VERR_INVALID_POINTER;
1105
1106#define REGISTER_CALLBACK(x) \
1107 do { \
1108 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
1109 if (RT_FAILURE(rc)) \
1110 return rc; \
1111 } while (0)
1112
1113#define UNREGISTER_CALLBACK(x) \
1114 do { \
1115 int rc2 = pResp->setCallback(x, NULL); \
1116 AssertRC(rc2); \
1117 } while (0)
1118
1119 /*
1120 * Register callbacks.
1121 */
1122 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1123 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1124 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1125 if (mDataBase.m_uProtocolVersion >= 3)
1126 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1127 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1128
1129 do
1130 {
1131 /*
1132 * Receive the raw data.
1133 */
1134 GuestDnDMsg Msg;
1135 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1136 if (mDataBase.m_uProtocolVersion >= 3)
1137 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1138 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1139 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1140 Msg.setNextUInt32(pCtx->mAction);
1141
1142 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1143 * the host and therefore now waiting for the actual raw data. */
1144 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1145 if (RT_SUCCESS(rc))
1146 {
1147 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1148 if (RT_SUCCESS(rc))
1149 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1150 }
1151
1152 } while (0);
1153
1154 /*
1155 * Unregister callbacks.
1156 */
1157 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1158 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1159 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1160 if (mDataBase.m_uProtocolVersion >= 3)
1161 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1162 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1163
1164#undef REGISTER_CALLBACK
1165#undef UNREGISTER_CALLBACK
1166
1167 if (RT_FAILURE(rc))
1168 {
1169 if (rc == VERR_CANCELLED)
1170 {
1171 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1172 AssertRC(rc2);
1173
1174 rc2 = sendCancel();
1175 AssertRC(rc2);
1176 }
1177 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1178 {
1179 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1180 rc, GuestDnDSource::i_hostErrorToString(rc));
1181 AssertRC(rc2);
1182 }
1183 }
1184
1185 LogFlowFuncLeaveRC(rc);
1186 return rc;
1187}
1188
1189int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1190{
1191 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1192
1193 int rc;
1194
1195 LogFlowFuncEnter();
1196
1197 GuestDnDResponse *pResp = pCtx->mpResp;
1198 AssertPtr(pCtx->mpResp);
1199
1200 GuestDnD *pInst = GuestDnDInst();
1201 if (!pInst)
1202 return VERR_INVALID_POINTER;
1203
1204#define REGISTER_CALLBACK(x) \
1205 do { \
1206 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
1207 if (RT_FAILURE(rc)) \
1208 return rc; \
1209 } while (0)
1210
1211#define UNREGISTER_CALLBACK(x) \
1212 do { \
1213 int rc2 = pResp->setCallback(x, NULL); \
1214 AssertRC(rc2); \
1215 } while (0)
1216
1217 /*
1218 * Register callbacks.
1219 */
1220 /* Guest callbacks. */
1221 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1222 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1223 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1224 if (mDataBase.m_uProtocolVersion >= 3)
1225 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1226 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1227 REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1228 if (mDataBase.m_uProtocolVersion >= 2)
1229 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1230 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1231
1232 DnDDroppedFiles &droppedFiles = pCtx->mURI.getDroppedFiles();
1233
1234 do
1235 {
1236 rc = droppedFiles.OpenTemp(0 /* fFlags */);
1237 if (RT_FAILURE(rc))
1238 break;
1239 LogFlowFunc(("rc=%Rrc, strDropDir=%s\n", rc, droppedFiles.GetDirAbs()));
1240 if (RT_FAILURE(rc))
1241 break;
1242
1243 /*
1244 * Receive the URI list.
1245 */
1246 GuestDnDMsg Msg;
1247 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1248 if (mDataBase.m_uProtocolVersion >= 3)
1249 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1250 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1251 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1252 Msg.setNextUInt32(pCtx->mAction);
1253
1254 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1255 * the host and therefore now waiting for the actual URI data. */
1256 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1257 if (RT_SUCCESS(rc))
1258 {
1259 LogFlowFunc(("Waiting ...\n"));
1260
1261 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1262 if (RT_SUCCESS(rc))
1263 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1264
1265 LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
1266 }
1267
1268 } while (0);
1269
1270 /*
1271 * Unregister callbacks.
1272 */
1273 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1274 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1275 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1276 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1277 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1278 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1279 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1280 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1281
1282#undef REGISTER_CALLBACK
1283#undef UNREGISTER_CALLBACK
1284
1285 if (RT_FAILURE(rc))
1286 {
1287 if (rc == VERR_CANCELLED)
1288 {
1289 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1290 AssertRC(rc2);
1291
1292 rc2 = sendCancel();
1293 AssertRC(rc2);
1294 }
1295 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1296 {
1297 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1298 rc, GuestDnDSource::i_hostErrorToString(rc));
1299 AssertRC(rc2);
1300 }
1301 }
1302
1303 if (RT_FAILURE(rc))
1304 {
1305 int rc2 = droppedFiles.Rollback();
1306 if (RT_FAILURE(rc2))
1307 LogRel(("DnD: Deleting left over temporary files failed (%Rrc). Please remove directory manually: %s\n",
1308 rc2, droppedFiles.GetDirAbs()));
1309 }
1310
1311 droppedFiles.Close();
1312
1313 LogFlowFuncLeaveRC(rc);
1314 return rc;
1315}
1316
1317/* static */
1318DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1319{
1320 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1321 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1322
1323 GuestDnDSource *pThis = pCtx->mpSource;
1324 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1325
1326 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1327
1328 int rc = VINF_SUCCESS;
1329
1330 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1331 bool fNotify = false;
1332
1333 switch (uMsg)
1334 {
1335 case GUEST_DND_CONNECT:
1336 /* Nothing to do here (yet). */
1337 break;
1338
1339 case GUEST_DND_DISCONNECT:
1340 rc = VERR_CANCELLED;
1341 break;
1342
1343#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1344 case GUEST_DND_GH_SND_DATA_HDR:
1345 {
1346 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1347 AssertPtr(pCBData);
1348 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1349 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1350
1351 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1352 break;
1353 }
1354 case GUEST_DND_GH_SND_DATA:
1355 {
1356 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1357 AssertPtr(pCBData);
1358 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1359 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1360
1361 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1362 break;
1363 }
1364 case GUEST_DND_GH_EVT_ERROR:
1365 {
1366 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1367 AssertPtr(pCBData);
1368 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1369 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1370
1371 pCtx->mpResp->reset();
1372
1373 if (RT_SUCCESS(pCBData->rc))
1374 {
1375 AssertMsgFailed(("Received guest error with no error code set\n"));
1376 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1377 }
1378 else if (pCBData->rc == VERR_WRONG_ORDER)
1379 {
1380 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1381 }
1382 else
1383 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1384 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1385
1386 LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
1387
1388 if (RT_SUCCESS(rc))
1389 rcCallback = VERR_GSTDND_GUEST_ERROR;
1390 break;
1391 }
1392#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1393 default:
1394 rc = VERR_NOT_SUPPORTED;
1395 break;
1396 }
1397
1398 if ( RT_FAILURE(rc)
1399 || RT_FAILURE(rcCallback))
1400 {
1401 fNotify = true;
1402 if (RT_SUCCESS(rcCallback))
1403 rcCallback = rc;
1404 }
1405
1406 if (RT_FAILURE(rc))
1407 {
1408 switch (rc)
1409 {
1410 case VERR_NO_DATA:
1411 LogRel2(("DnD: Data transfer to host complete\n"));
1412 break;
1413
1414 case VERR_CANCELLED:
1415 LogRel2(("DnD: Data transfer to host canceled\n"));
1416 break;
1417
1418 default:
1419 LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
1420 break;
1421 }
1422
1423 /* Unregister this callback. */
1424 AssertPtr(pCtx->mpResp);
1425 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1426 AssertRC(rc2);
1427 }
1428
1429 /* All data processed? */
1430 if (pCtx->mData.isComplete())
1431 fNotify = true;
1432
1433 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1434 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1435
1436 if (fNotify)
1437 {
1438 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1439 AssertRC(rc2);
1440 }
1441
1442 LogFlowFuncLeaveRC(rc);
1443 return rc; /* Tell the guest. */
1444}
1445
1446/* static */
1447DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1448{
1449 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1450 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1451
1452 GuestDnDSource *pThis = pCtx->mpSource;
1453 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1454
1455 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1456
1457 int rc = VINF_SUCCESS;
1458
1459 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1460 bool fNotify = false;
1461
1462 switch (uMsg)
1463 {
1464 case GUEST_DND_CONNECT:
1465 /* Nothing to do here (yet). */
1466 break;
1467
1468 case GUEST_DND_DISCONNECT:
1469 rc = VERR_CANCELLED;
1470 break;
1471
1472#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1473 case GUEST_DND_GH_SND_DATA_HDR:
1474 {
1475 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1476 AssertPtr(pCBData);
1477 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1478 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1479
1480 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1481 break;
1482 }
1483 case GUEST_DND_GH_SND_DATA:
1484 {
1485 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1486 AssertPtr(pCBData);
1487 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1488 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1489
1490 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1491 break;
1492 }
1493 case GUEST_DND_GH_SND_DIR:
1494 {
1495 PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
1496 AssertPtr(pCBData);
1497 AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1498 AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1499
1500 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1501 break;
1502 }
1503 case GUEST_DND_GH_SND_FILE_HDR:
1504 {
1505 PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1506 AssertPtr(pCBData);
1507 AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1508 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1509
1510 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1511 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1512 break;
1513 }
1514 case GUEST_DND_GH_SND_FILE_DATA:
1515 {
1516 PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1517 AssertPtr(pCBData);
1518 AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1519 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1520
1521 if (pThis->mDataBase.m_uProtocolVersion <= 1)
1522 {
1523 /**
1524 * Notes for protocol v1 (< VBox 5.0):
1525 * - Every time this command is being sent it includes the file header,
1526 * so just process both calls here.
1527 * - There was no information whatsoever about the total file size; the old code only
1528 * appended data to the desired file. So just pass 0 as cbSize.
1529 */
1530 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1531 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1532 if (RT_SUCCESS(rc))
1533 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1534 }
1535 else /* Protocol v2 and up. */
1536 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1537 break;
1538 }
1539 case GUEST_DND_GH_EVT_ERROR:
1540 {
1541 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1542 AssertPtr(pCBData);
1543 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1544 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1545
1546 pCtx->mpResp->reset();
1547
1548 if (RT_SUCCESS(pCBData->rc))
1549 {
1550 AssertMsgFailed(("Received guest error with no error code set\n"));
1551 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1552 }
1553 else if (pCBData->rc == VERR_WRONG_ORDER)
1554 {
1555 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1556 }
1557 else
1558 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1559 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1560
1561 LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
1562
1563 if (RT_SUCCESS(rc))
1564 rcCallback = VERR_GSTDND_GUEST_ERROR;
1565 break;
1566 }
1567#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1568 default:
1569 rc = VERR_NOT_SUPPORTED;
1570 break;
1571 }
1572
1573 if ( RT_FAILURE(rc)
1574 || RT_FAILURE(rcCallback))
1575 {
1576 fNotify = true;
1577 if (RT_SUCCESS(rcCallback))
1578 rcCallback = rc;
1579 }
1580
1581 if (RT_FAILURE(rc))
1582 {
1583 switch (rc)
1584 {
1585 case VERR_NO_DATA:
1586 LogRel2(("DnD: File transfer to host complete\n"));
1587 break;
1588
1589 case VERR_CANCELLED:
1590 LogRel2(("DnD: File transfer to host canceled\n"));
1591 break;
1592
1593 default:
1594 LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
1595 break;
1596 }
1597
1598 /* Unregister this callback. */
1599 AssertPtr(pCtx->mpResp);
1600 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1601 AssertRC(rc2);
1602 }
1603
1604 /* All data processed? */
1605 if ( pCtx->mURI.isComplete()
1606 && pCtx->mData.isComplete())
1607 {
1608 fNotify = true;
1609 }
1610
1611 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1612 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1613
1614 if (fNotify)
1615 {
1616 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1617 AssertRC(rc2);
1618 }
1619
1620 LogFlowFuncLeaveRC(rc);
1621 return rc; /* Tell the guest. */
1622}
1623
Note: See TracBrowser for help on using the repository browser.

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