VirtualBox

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

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

DnD/Main: Added various i_setErrorXXX() methods for GuestDnDBase, which GuestDnDSource + GuestDnDTarget get derived from. To set COM objects errors usable for API clients. Not used yet.

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