VirtualBox

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

Last change on this file since 98262 was 98262, checked in by vboxsync, 17 months ago

Main: rc() -> hrc()/vrc(). bugref:10223

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

© 2023 Oracle
ContactPrivacy policyTerms of Use