VirtualBox

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

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

DnD/Main: Don't set (and return) an error for GuestDnDSource::dragIsPending() when old(er) X11-based Guest Additions return with no supported host formats, but only specific X11-formats (TARGETS, MULTIPLE). Needs to be fixed for newer Guest Additions.

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

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