summaryrefslogtreecommitdiff
path: root/src/VBox/HostServices/DragAndDrop/service.cpp
blob: 54c5025618f594ba714ad23fcd9c6a8f7c601c4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/* $Id: service.cpp $ */
/** @file
 * Drag and Drop Service.
 */

/*
 * Copyright (C) 2011-2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/** @page pg_svc_guest_control   Guest Control HGCM Service
 *
 * This service acts as a proxy for handling and buffering host command requests
 * and clients on the guest. It tries to be as transparent as possible to let
 * the guest (client) and host side do their protocol handling as desired.
 *
 * The following terms are used:
 * - Host:   A host process (e.g. VBoxManage or another tool utilizing the Main API)
 *           which wants to control something on the guest.
 * - Client: A client (e.g. VBoxService) running inside the guest OS waiting for
 *           new host commands to perform. There can be multiple clients connected
 *           to a service. A client is represented by its HGCM client ID.
 * - Context ID: An (almost) unique ID automatically generated on the host (Main API)
 *               to not only distinguish clients but individual requests. Because
 *               the host does not know anything about connected clients it needs
 *               an indicator which it can refer to later. This context ID gets
 *               internally bound by the service to a client which actually processes
 *               the command in order to have a relationship between client<->context ID(s).
 *
 * The host can trigger commands which get buffered by the service (with full HGCM
 * parameter info). As soon as a client connects (or is ready to do some new work)
 * it gets a buffered host command to process it. This command then will be immediately
 * removed from the command list. If there are ready clients but no new commands to be
 * processed, these clients will be set into a deferred state (that is being blocked
 * to return until a new command is available).
 *
 * If a client needs to inform the host that something happened, it can send a
 * message to a low level HGCM callback registered in Main. This callback contains
 * the actual data as well as the context ID to let the host do the next necessary
 * steps for this context. This context ID makes it possible to wait for an event
 * inside the host's Main API function (like starting a process on the guest and
 * wait for getting its PID returned by the client) as well as cancelling blocking
 * host calls in order the client terminated/crashed (HGCM detects disconnected
 * clients and reports it to this service's callback).
 */

/******************************************************************************
 *   Header Files                                                             *
 ******************************************************************************/
#define LOG_GROUP LOG_GROUP_HGCM

#include "dndmanager.h"

//# define DO(s) RTPrintf s
#define DO(s) do {} while(0)
//#define DO(s) Log s

/******************************************************************************
 *   Service class declaration                                                *
 ******************************************************************************/

/**
 * Specialized drag & drop service class.
 */
class DragAndDropService: public HGCM::AbstractService<DragAndDropService>
{
public:
    explicit DragAndDropService(PVBOXHGCMSVCHELPERS pHelpers)
      : HGCM::AbstractService<DragAndDropService>(pHelpers)
      , m_pManager(0)
      , m_cClients(0)
    {}

protected:
    /* HGCM service implementation */
    int  init(VBOXHGCMSVCFNTABLE *pTable);
    int  uninit();
    int  clientConnect(uint32_t u32ClientID, void *pvClient);
    int  clientDisconnect(uint32_t u32ClientID, void *pvClient);
    void guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int  hostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);

    static DECLCALLBACK(int) progressCallback(unsigned uPercentage, uint32_t uState, void *pvUser);
    int      hostMessage(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    void     modeSet(uint32_t u32Mode);
    inline uint32_t modeGet() { return m_u32Mode; };

    DnDManager             *m_pManager;

    uint32_t                m_cClients;
    RTCList<HGCM::Client*>  m_clientQueue;
    uint32_t                m_u32Mode;
};

/******************************************************************************
 *   Service class implementation                                             *
 ******************************************************************************/

int DragAndDropService::init(VBOXHGCMSVCFNTABLE *pTable)
{
    /* Register functions. */
    pTable->pfnHostCall          = svcHostCall;
    pTable->pfnSaveState         = NULL;  /* The service is stateless, so the normal */
    pTable->pfnLoadState         = NULL;  /* construction done before restoring suffices */
    pTable->pfnRegisterExtension = svcRegisterExtension;
    modeSet(VBOX_DRAG_AND_DROP_MODE_OFF);

    m_pManager = new DnDManager(&DragAndDropService::progressCallback, this);

    return VINF_SUCCESS;
}

int DragAndDropService::uninit()
{
    delete m_pManager;

    return VINF_SUCCESS;
}

int DragAndDropService::clientConnect(uint32_t u32ClientID, void *pvClient)
{
    LogFlowFunc(("New client (%ld) connected\n", u32ClientID));
    DO(("New client (%ld) connected\n", u32ClientID));
    if (m_cClients < UINT32_MAX)
        m_cClients++;
    else
        AssertMsgFailed(("Maximum number of clients reached\n"));
    return VINF_SUCCESS;
}

int DragAndDropService::clientDisconnect(uint32_t u32ClientID, void *pvClient)
{
    /* Remove all waiters with this clientId. */
    for (size_t i = 0; i < m_clientQueue.size(); )
    {
        HGCM::Client *pClient = m_clientQueue.at(i);
        if (pClient->clientId() == u32ClientID)
        {
            m_pHelpers->pfnCallComplete(pClient->handle(), VERR_INTERRUPTED);
            m_clientQueue.removeAt(i);
            delete pClient;
        }
        else
            i++;
    }

    return VINF_SUCCESS;
}

void DragAndDropService::modeSet(uint32_t u32Mode)
{
    switch (u32Mode)
    {
        case VBOX_DRAG_AND_DROP_MODE_OFF:
        case VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST:
        case VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST:
        case VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL:
            m_u32Mode = u32Mode;
            break;

        default:
            m_u32Mode = VBOX_DRAG_AND_DROP_MODE_OFF;
    }
}

void DragAndDropService::guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    int rc = VINF_SUCCESS;
    LogFlowFunc(("u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n",
                 u32ClientID, u32Function, cParms, paParms));
//    RTPrintf("u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n",
//                 u32ClientID, u32Function, cParms, paParms);

    switch (u32Function)
    {
        case DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG:
        {
            DO(("GUEST_DND_GET_NEXT_HOST_MSG\n"));
            if (   cParms != 3
                || paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* message */
                || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* parameter count */
                || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* blocking */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                rc = m_pManager->nextMessageInfo(&paParms[0].u.uint32, &paParms[1].u.uint32);
                if (   RT_FAILURE(rc)
                    && paParms[2].u.uint32) /* Blocking? */
                {
                    m_clientQueue.append(new HGCM::Client(u32ClientID, callHandle, u32Function, cParms, paParms));
                    rc = VINF_HGCM_ASYNC_EXECUTE;
                }
            }
            break;
        }
        case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
        {
            DO(("GUEST_DND_HG_ACK_OP\n"));
            if (   modeGet() != VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL
                && modeGet() != VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST)
            {
                DO(("=> ignoring!\n"));
                break;
            }

            if (   cParms != 1
                || paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* action */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                DragAndDropSvc::VBOXDNDCBHGACKOPDATA data;
                data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP;
                paParms[0].getUInt32(&data.uAction);
                if (m_pfnHostCallback)
                    rc = m_pfnHostCallback(m_pvHostData, u32Function, &data, sizeof(data));
//                m_pHelpers->pfnCallComplete(callHandle, rc);
            }
            break;
        }
        case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
        {
            DO(("GUEST_DND_HG_REQ_DATA\n"));
            if (   modeGet() != VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL
                && modeGet() != VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST)
            {
                DO(("=> ignoring!\n"));
                break;
            }

            if (   cParms != 1
                || paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* format */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                DragAndDropSvc::VBOXDNDCBHGREQDATADATA data;
                data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA;
                uint32_t cTmp;
                paParms[0].getPointer((void**)&data.pszFormat, &cTmp);
                if (m_pfnHostCallback)
                    rc = m_pfnHostCallback(m_pvHostData, u32Function, &data, sizeof(data));
//                m_pHelpers->pfnCallComplete(callHandle, rc);
//                if (data.pszFormat)
//                    RTMemFree(data.pszFormat);
//                if (data.pszTmpPath)
//                    RTMemFree(data.pszTmpPath);
            }
            break;
        }
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
        case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
        {
            DO(("GUEST_DND_GH_ACK_PENDING\n"));
            if (   modeGet() != VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL
                && modeGet() != VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST)
            {
                DO(("=> ignoring!\n"));
                break;
            }

            if (   cParms != 3
                || paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* defaction */
                || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* allactions */
                || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR   /* format */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA data;
                data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING;
                paParms[0].getUInt32(&data.uDefAction);
                paParms[1].getUInt32(&data.uAllActions);
                uint32_t cTmp;
                paParms[2].getPointer((void**)&data.pszFormat, &cTmp);
                if (m_pfnHostCallback)
                    rc = m_pfnHostCallback(m_pvHostData, u32Function, &data, sizeof(data));
            }
            break;
        }
        case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
        {
            DO(("GUEST_DND_GH_SND_DATA\n"));
            if (   modeGet() != VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL
                && modeGet() != VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST)
            {
                DO(("=> ignoring\n"));
                break;
            }

            if (   cParms != 2
                || paParms[0].type != VBOX_HGCM_SVC_PARM_PTR   /* data */
                || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* size */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                DragAndDropSvc::VBOXDNDCBSNDDATADATA data;
                data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA;
                paParms[0].getPointer((void**)&data.pvData, &data.cbData);
                paParms[1].getUInt32(&data.cbAllSize);
                if (m_pfnHostCallback)
                    rc = m_pfnHostCallback(m_pvHostData, u32Function, &data, sizeof(data));
            }
            break;
        }
        case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
        {
            DO(("GUEST_DND_GH_EVT_ERROR\n"));
            if (   modeGet() != VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL
                && modeGet() != VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST)
            {
                DO(("=> ignoring!\n"));
                break;
            }

            if (   cParms != 1
                || paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* rc */)
                rc = VERR_INVALID_PARAMETER;
            else
            {
                DragAndDropSvc::VBOXDNDCBEVTERRORDATA data;
                data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR;
                uint32_t rcOp;
                paParms[0].getUInt32(&rcOp);
                data.rc = rcOp;
                if (m_pfnHostCallback)
                    rc = m_pfnHostCallback(m_pvHostData, u32Function, &data, sizeof(data));
            }
            break;
        }
#endif
        default:
        {
            /* All other messages are handled by the DnD manager. */
            rc = m_pManager->nextMessage(u32Function, cParms, paParms);
            /* Check for error. Buffer overflow is allowed. It signals the
             * guest to ask for more data in the next event. */
            if (   RT_FAILURE(rc)
                && rc != VERR_CANCELLED
                && rc != VERR_BUFFER_OVERFLOW) /* Buffer overflow is allowed. */
            {
                m_clientQueue.append(new HGCM::Client(u32ClientID, callHandle, u32Function, cParms, paParms));
                rc = VINF_HGCM_ASYNC_EXECUTE;
            }
            break;
        }
    }
    /* If async execute is requested, we didn't notify the guest about
     * completion. The client is queued into the waiters list and will be
     * notified as soon as a new event is available. */
    if (rc != VINF_HGCM_ASYNC_EXECUTE)
        m_pHelpers->pfnCallComplete(callHandle, rc);
    DO(("guest call: %Rrc\n", rc));
}

int DragAndDropService::hostMessage(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    int rc = VINF_SUCCESS;
#if 0
    HGCM::Message *pMessage = new HGCM::Message(u32Function, cParms, paParms);
    m_hostQueue.push(pMessage);
//    bool fPush = true;
    RTPrintf("client queue %u\n", m_clientQueue.size());
    RTPrintf("host   queue %u\n", m_hostQueue.size());
    if (!m_clientQueue.empty())
    {
        pMessage = m_hostQueue.front();
        HGCM::Client *pClient = m_clientQueue.front();
        /* Check if this was a request for getting the next host
         * message. If so, return the message id and the parameter
         * count. The message itself has to be queued. */
        if (pClient->message() == DragAndDropSvc::GUEST_GET_NEXT_HOST_MSG)
        {
            RTPrintf("client is waiting for next host msg\n");
//            rc = VERR_TOO_MUCH_DATA;
            pClient->addMessageInfo(pMessage);
            /* temp */
//        m_pHelpers->pfnCallComplete(pClient->handle(), rc);
//        m_clientQueue.pop();
//        delete pClient;
        }
        else
        {
            RTPrintf("client is waiting for host msg (%d)\n", u32Function);
            /* There is a request for a host message pending. Check
             * if this is the correct message and if so deliver. If
             * not the message will be queued. */
            rc = pClient->addMessage(pMessage);
            m_hostQueue.pop();
            delete pMessage;
//            if (RT_SUCCESS(rc))
//                fPush = false;
        }
        /* In any case mark this client request as done. */
        m_pHelpers->pfnCallComplete(pClient->handle(), rc);
        m_clientQueue.pop_front();
        delete pClient;
    }
//    if (fPush)
//    {
//        RTPrintf("push message\n");
//        m_hostQueue.push(pMessage);
//    }
//    else
//        delete pMessage;
#endif

    return rc;
}

int DragAndDropService::hostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    int rc = VINF_SUCCESS;
    if (u32Function == DragAndDropSvc::HOST_DND_SET_MODE)
    {
        if (cParms != 1)
            rc = VERR_INVALID_PARAMETER;
        else if (paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT)
            rc = VERR_INVALID_PARAMETER;
        else
            modeSet(paParms[0].u.uint32);
    }
    else if (modeGet() != VBOX_DRAG_AND_DROP_MODE_OFF)
    {
        rc = m_pManager->addMessage(u32Function, cParms, paParms);
        if (    RT_SUCCESS(rc)
            && !m_clientQueue.isEmpty())
        {
            HGCM::Client *pClient = m_clientQueue.first();
            /* Check if this was a request for getting the next host
             * message. If so, return the message id and the parameter
             * count. The message itself has to be queued. */
            if (pClient->message() == DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG)
            {
                DO(("client is waiting for next host msg\n"));
//              rc = m_pManager->nextMessageInfo(&paParms[0].u.uint32, &paParms[1].u.uint32);
                uint32_t uMsg1;
                uint32_t cParms1;
                rc = m_pManager->nextMessageInfo(&uMsg1, &cParms1);
                if (RT_SUCCESS(rc))
                {
                    pClient->addMessageInfo(uMsg1, cParms1);
                    m_pHelpers->pfnCallComplete(pClient->handle(), rc);
                    m_clientQueue.removeFirst();
                    delete pClient;
                }
                else
                    AssertMsgFailed(("Should not happen!"));
            }
            else
                AssertMsgFailed(("Should not happen!"));
        }
//      else
//          AssertMsgFailed(("Should not happen %Rrc!", rc));
    }

    LogFlowFunc(("rc=%Rrc\n", rc));
    return rc;
}

DECLCALLBACK(int) DragAndDropService::progressCallback(unsigned uPercentage, uint32_t uState, void *pvUser)
{
    AssertPtrReturn(pvUser, VERR_INVALID_POINTER);

    DragAndDropService *pSelf = static_cast<DragAndDropService *>(pvUser);

    if (pSelf->m_pfnHostCallback)
    {
        DO(("GUEST_DND_HG_EVT_PROGRESS %u\n", uPercentage));
        DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA data;
        data.hdr.u32Magic = DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS;
        data.uPercentage  = uPercentage;
        data.uState       = uState;

        return pSelf->m_pfnHostCallback(pSelf->m_pvHostData, DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS, &data, sizeof(data));
    }

    return VINF_SUCCESS;
}

/**
 * @copydoc VBOXHGCMSVCLOAD
 */
extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable)
{
    return DragAndDropService::svcLoad(pTable);
}