VirtualBox

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

Last change on this file since 70772 was 69500, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

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

© 2023 Oracle
ContactPrivacy policyTerms of Use