VirtualBox

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

Last change on this file since 85402 was 85402, checked in by vboxsync, 5 years ago

DnD/Main: Renaming; use size_t where appropriate.

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