VirtualBox

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

Last change on this file since 103415 was 103415, checked in by vboxsync, 10 months ago

Additions,Main,VMM,Runtime: Fix some unused expression warnings, bugref:3409

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