VirtualBox

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

Last change on this file since 85558 was 85558, checked in by vboxsync, 4 years ago

DnD/Main: Return list as return value for GuestDnDBase::i_getFormats().

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

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