VirtualBox

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

Last change on this file since 85451 was 85451, checked in by vboxsync, 4 years ago

DnD/Main: Release and re-acquire write locks before/after creating worker threads.

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