VirtualBox

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

Last change on this file since 97698 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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

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