VirtualBox

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

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

DnD/Main: Got rid of protocol-guessing via Guest Additions version and pass down the reported (now marked as deprecated) protocol version and guest feature bits to Main.

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