VirtualBox

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

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

scm --update-copyright-year

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

© 2023 Oracle
ContactPrivacy policyTerms of Use