VirtualBox

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

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

DnD/Main: Renamed GuestDnDMsg::setNextXXX() -> GuestDnDMsg::appendXXX().

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