VirtualBox

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

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

DnD/Main: Cleaned up error path handling a bit.

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