VirtualBox

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

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

DnD: Simplified / untangled reset handling:

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

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