VirtualBox

source: vbox/trunk/src/VBox/Main/hgcm/HGCM.cpp@ 794

Last change on this file since 794 was 222, checked in by vboxsync, 18 years ago

Fixed parameters order in HGCM host call. That produced warning, reported by Frank.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.4 KB
Line 
1/** @file
2 *
3 * HGCM (Host-Guest Communication Manager)
4 */
5
6/*
7 * Copyright (C) 2006 InnoTek Systemberatung GmbH
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 as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22
23/*
24 * NOT FOR REVIEWING YET. A LOT OF TODO/MISSED/INCOMPLETE/SKETCH CODE INSIDE!
25 */
26
27#define LOG_GROUP_MAIN_OVERRIDE LOG_GROUP_HGCM
28#include "Logging.h"
29
30#include <string.h>
31
32#include "hgcm/HGCM.h"
33#include "hgcm/HGCMThread.h"
34
35#include <VBox/err.h>
36#include <VBox/hgcmsvc.h>
37
38#include <iprt/alloc.h>
39#include <iprt/avl.h>
40#include <iprt/critsect.h>
41#include <iprt/asm.h>
42#include <iprt/ldr.h>
43#include <iprt/string.h>
44#include <iprt/semaphore.h>
45#include <iprt/thread.h>
46
47#include <VBox/VBoxGuest.h>
48
49/**
50 *
51 * Service location types:
52 *
53 * LOCAL SERVICE
54 * service DLL is loaded by the VM process,
55 * and directly called by the VM HGCM instance.
56 */
57
58/**
59 * A service gets one thread, which synchronously delivers messages to
60 * the service. This is good for serialization.
61 *
62 * Some services may want to process messages asynchronously, and will want
63 * a next message to be delivered, while a previous message is still being
64 * processed.
65 *
66 * The dedicated service thread delivers a next message when service
67 * returns after fetching a previous one. The service will call a message
68 * completion callback when message is actually processed. So returning
69 * from the service call means only that the service is processing message.
70 *
71 * 'Message processed' condition is indicated by service, which call the
72 * callback, even if the callback is called synchronously in the dedicated
73 * thread.
74 *
75 * This message completion callback is only valid for Call requests.
76 * Connect and Disconnect are processed sznchronously by service.
77 *
78 */
79
80/** @todo services registration, only registered service dll can be loaded */
81
82/** @todo a registered LOCAL service must also get a thread, for now
83 * a thread per service (later may be there will be an option to
84 * have a thread per client for a service, however I do not think it's
85 * really necessary).
86 * The requests will be queued and executed by the service thread,
87 * an IRQ notification will be ussued when a request is completed.
88 *
89 * May be other services (like VRDP acceleration) should still use
90 * the EMT thread, because they have their own threads for long
91 * operations.
92 * So we have to distinguish those services during
93 * registration process (external/internal registration).
94 * External dlls will always have its own thread,
95 * internal (trusted) services will choose between having executed
96 * on EMT or on a separate thread.
97 *
98 */
99
100
101/** Internal helper service object. HGCM code would use it to
102 * hold information about services and communicate with services.
103 * The HGCMService is an (in future) abstract class that implements
104 * common functionality. There will be derived classes for specific
105 * service types (Local, etc).
106 */
107
108/** @todo should be HGCMObject */
109class HGCMService
110{
111 private:
112 VBOXHGCMSVCHELPERS m_svcHelpers;
113
114 static HGCMService *sm_pSvcListHead;
115 static HGCMService *sm_pSvcListTail;
116
117
118 HGCMTHREADHANDLE m_thread;
119 friend DECLCALLBACK(void) hgcmServiceThread (HGCMTHREADHANDLE ThreadHandle, void *pvUser);
120
121 uint32_t volatile m_u32RefCnt;
122
123 HGCMService *m_pSvcNext;
124 HGCMService *m_pSvcPrev;
125
126 char *m_pszSvcName;
127 char *m_pszSvcLibrary;
128
129 PPDMIHGCMPORT m_pHGCMPort;
130
131 RTLDRMOD m_hLdrMod;
132 PFNVBOXHGCMSVCLOAD m_pfnLoad;
133
134 VBOXHGCMSVCFNTABLE m_fntable;
135
136
137 int loadServiceDLL (void);
138 void unloadServiceDLL (void);
139
140 int InstanceCreate (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort);
141 void InstanceDestroy (void);
142
143 HGCMService ();
144 ~HGCMService () {};
145
146 bool EqualToLoc (HGCMServiceLocation *loc);
147
148 static DECLCALLBACK(void) svcHlpCallComplete (VBOXHGCMCALLHANDLE callHandle, int32_t rc);
149
150 public:
151
152 static int FindService (HGCMService **ppsvc, HGCMServiceLocation *loc);
153 static HGCMService *FindServiceByName (const char *pszServiceName);
154 static int LoadService (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort);
155 void ReleaseService (void);
156
157 uint32_t SizeOfClient (void) { return m_fntable.cbClient; };
158
159 int Connect (uint32_t u32ClientID);
160 int Disconnect (uint32_t u32ClientID);
161 int GuestCall (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], bool fBlock);
162 int HostCall (PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[]);
163};
164
165
166class HGCMClient: public HGCMObject
167{
168 public:
169 ~HGCMClient ();
170
171 int Init (HGCMService *pSvc);
172
173 /** Service that the client is connected to. */
174 HGCMService *pService;
175
176 /** Client specific data. */
177 void *pvData;
178};
179
180HGCMClient::~HGCMClient ()
181{
182 RTMemFree (pvData);
183}
184
185int HGCMClient::Init (HGCMService *pSvc)
186{
187 pService = pSvc;
188
189 if (pService->SizeOfClient () > 0)
190 {
191 pvData = RTMemAllocZ (pService->SizeOfClient ());
192
193 if (!pvData)
194 {
195 return VERR_NO_MEMORY;
196 }
197 }
198
199 return VINF_SUCCESS;
200}
201
202
203#define HGCM_CLIENT_DATA(pService, pClient) (pClient->pvData)
204
205
206/*
207 * Messages processed by worker threads.
208 */
209
210#define HGCMMSGID_SVC_LOAD (0)
211#define HGCMMSGID_SVC_UNLOAD (1)
212#define HGCMMSGID_SVC_CONNECT (2)
213#define HGCMMSGID_SVC_DISCONNECT (3)
214#define HGCMMSGID_GUESTCALL (4)
215
216class HGCMMsgSvcLoad: public HGCMMsgCore
217{
218};
219
220class HGCMMsgSvcUnload: public HGCMMsgCore
221{
222};
223
224class HGCMMsgSvcConnect: public HGCMMsgCore
225{
226 public:
227 /* client identifier */
228 uint32_t u32ClientID;
229};
230
231class HGCMMsgSvcDisconnect: public HGCMMsgCore
232{
233 public:
234 /* client identifier */
235 uint32_t u32ClientID;
236};
237
238class HGCMMsgHeader: public HGCMMsgCore
239{
240 public:
241 HGCMMsgHeader () : pCmd (NULL), pHGCMPort (NULL) {};
242
243 /* Command pointer/identifier. */
244 PVBOXHGCMCMD pCmd;
245
246 /* Port to be informed on message completion. */
247 PPDMIHGCMPORT pHGCMPort;
248};
249
250
251class HGCMMsgCall: public HGCMMsgHeader
252{
253 public:
254 /* client identifier */
255 uint32_t u32ClientID;
256
257 /* function number */
258 uint32_t u32Function;
259
260 /* number of parameters */
261 uint32_t cParms;
262
263 VBOXHGCMSVCPARM *paParms;
264};
265
266
267/*
268 * Messages processed by main HGCM thread.
269 */
270
271#define HGCMMSGID_CONNECT (10)
272#define HGCMMSGID_DISCONNECT (11)
273#define HGCMMSGID_LOAD (12)
274
275#define HGCMMSGID_HOSTCALL (13)
276
277class HGCMMsgConnect: public HGCMMsgHeader
278{
279 public:
280 /* service location */
281 HGCMSERVICELOCATION *pLoc;
282
283 /* client identifier */
284 uint32_t *pu32ClientID;
285
286};
287
288class HGCMMsgDisconnect: public HGCMMsgHeader
289{
290 public:
291 /* client identifier */
292 uint32_t u32ClientID;
293};
294
295class HGCMMsgLoad: public HGCMMsgHeader
296{
297 public:
298 virtual ~HGCMMsgLoad ()
299 {
300 RTStrFree (pszServiceLibrary);
301 RTStrFree (pszServiceName);
302 }
303
304 int Init (const char *pszName, const char *pszLibrary)
305 {
306 pszServiceName = RTStrDup (pszName);
307 pszServiceLibrary = RTStrDup (pszLibrary);
308
309 if (!pszServiceName || !pszServiceLibrary)
310 {
311 RTStrFree (pszServiceLibrary);
312 RTStrFree (pszServiceName);
313 pszServiceLibrary = NULL;
314 pszServiceName = NULL;
315 return VERR_NO_MEMORY;
316 }
317
318 return VINF_SUCCESS;
319 }
320
321 char *pszServiceName;
322 char *pszServiceLibrary;
323};
324
325class HGCMMsgHostCall: public HGCMMsgHeader
326{
327 public:
328 char *pszServiceName;
329
330 /* function number */
331 uint32_t u32Function;
332
333 /* number of parameters */
334 uint32_t cParms;
335
336 VBOXHGCMSVCPARM *paParms;
337};
338
339/* static */ DECLCALLBACK(void) HGCMService::svcHlpCallComplete (VBOXHGCMCALLHANDLE callHandle, int32_t rc)
340{
341 HGCMMsgCore *pMsgCore = (HGCMMsgCore *)callHandle;
342
343 if ( pMsgCore->MsgId () == HGCMMSGID_GUESTCALL
344 || pMsgCore->MsgId () == HGCMMSGID_HOSTCALL)
345 {
346 /* Only call the completion for these messages. The helper
347 * is called by the service, and the service does not get
348 * any other messages.
349 */
350 hgcmMsgComplete (pMsgCore, rc);
351 }
352 else
353 {
354 AssertFailed ();
355 }
356}
357
358HGCMService *HGCMService::sm_pSvcListHead = NULL;
359HGCMService *HGCMService::sm_pSvcListTail = NULL;
360
361HGCMService::HGCMService ()
362 :
363 m_thread (0),
364 m_u32RefCnt (0),
365 m_pSvcNext (NULL),
366 m_pSvcPrev (NULL),
367 m_pszSvcName (NULL),
368 m_pszSvcLibrary (NULL),
369 m_hLdrMod (NIL_RTLDRMOD),
370 m_pfnLoad (NULL)
371{
372 memset (&m_fntable, 0, sizeof (m_fntable));
373}
374
375
376HGCMMsgCore *hgcmMessageAlloc (uint32_t u32MsgId)
377{
378 switch (u32MsgId)
379 {
380 case HGCMMSGID_SVC_LOAD: return new HGCMMsgSvcLoad ();
381 case HGCMMSGID_SVC_UNLOAD: return new HGCMMsgSvcUnload ();
382 case HGCMMSGID_SVC_CONNECT: return new HGCMMsgSvcConnect ();
383 case HGCMMSGID_SVC_DISCONNECT: return new HGCMMsgSvcDisconnect ();
384 case HGCMMSGID_GUESTCALL: return new HGCMMsgCall ();
385
386 case HGCMMSGID_CONNECT: return new HGCMMsgConnect ();
387 case HGCMMSGID_DISCONNECT: return new HGCMMsgDisconnect ();
388 case HGCMMSGID_LOAD: return new HGCMMsgLoad ();
389 case HGCMMSGID_HOSTCALL: return new HGCMMsgHostCall ();
390 default:
391 Log(("hgcmMessageAlloc::Unsupported message number %08X\n", u32MsgId));
392 }
393
394 return NULL;
395}
396
397
398static DECLCALLBACK(void) hgcmMsgCompletionCallback (int32_t result, HGCMMsgCore *pMsgCore)
399{
400 /* Call the VMMDev port interface to issue IRQ notification. */
401 HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)pMsgCore;
402
403 LogFlow(("MAIN::hgcmMsgCompletionCallback: message %p\n", pMsgCore));
404
405 if (pMsgHdr->pHGCMPort)
406 {
407 pMsgHdr->pHGCMPort->pfnCompleted (pMsgHdr->pHGCMPort, result, pMsgHdr->pCmd);
408 }
409
410 return;
411}
412
413
414DECLCALLBACK(void) hgcmServiceThread (HGCMTHREADHANDLE ThreadHandle, void *pvUser)
415{
416 int rc = VINF_SUCCESS;
417
418 HGCMService *pSvc = (HGCMService *)pvUser;
419
420 AssertRelease(pSvc != NULL);
421
422 HGCMMsgCore *pMsgCore = NULL;
423
424 bool bUnloaded = false;
425
426 while (!bUnloaded)
427 {
428 rc = hgcmMsgGet (ThreadHandle, &pMsgCore);
429
430 if (VBOX_FAILURE (rc))
431 {
432 Log(("hgcmServiceThread: message get failed, rc = %Vrc\n", rc));
433
434 RTThreadSleep(100);
435
436 continue;
437 }
438
439 switch (pMsgCore->MsgId ())
440 {
441 case HGCMMSGID_SVC_LOAD:
442 {
443 LogFlow(("HGCMMSGID_SVC_LOAD\n"));
444 rc = pSvc->loadServiceDLL ();
445 } break;
446
447 case HGCMMSGID_SVC_UNLOAD:
448 {
449 LogFlow(("HGCMMSGID_SVC_UNLOAD\n"));
450 pSvc->unloadServiceDLL ();
451 bUnloaded = true;
452 } break;
453
454 case HGCMMSGID_SVC_CONNECT:
455 {
456 LogFlow(("HGCMMSGID_SVC_CONNECT\n"));
457
458 HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pMsgCore;
459
460 rc = VINF_SUCCESS;
461
462 HGCMClient *pClient = (HGCMClient *)hgcmObjReference (pMsg->u32ClientID);
463
464 if (pClient)
465 {
466 rc = pSvc->m_fntable.pfnConnect (pMsg->u32ClientID, HGCM_CLIENT_DATA(pSvc, pClient));
467
468 hgcmObjDereference (pClient);
469 }
470 } break;
471
472 case HGCMMSGID_SVC_DISCONNECT:
473 {
474 LogFlow(("HGCMMSGID_SVC_DISCONNECT\n"));
475
476 HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore;
477
478 rc = VINF_SUCCESS;
479
480 HGCMClient *pClient = (HGCMClient *)hgcmObjReference (pMsg->u32ClientID);
481
482 if (pClient)
483 {
484 rc = pSvc->m_fntable.pfnDisconnect (pMsg->u32ClientID, HGCM_CLIENT_DATA(pSvc, pClient));
485
486 hgcmObjDereference (pClient);
487 }
488 } break;
489
490 case HGCMMSGID_GUESTCALL:
491 {
492 LogFlow(("HGCMMSGID_GUESTCALL\n"));
493
494 HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;
495
496 rc = VINF_SUCCESS;
497
498 HGCMClient *pClient = (HGCMClient *)hgcmObjReference (pMsg->u32ClientID);
499
500 if (pClient)
501 {
502 pSvc->m_fntable.pfnCall ((VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientID, HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function, pMsg->cParms, pMsg->paParms);
503
504 hgcmObjDereference (pClient);
505 }
506 else
507 {
508 rc = VERR_INVALID_HANDLE;
509 }
510 } break;
511
512 case HGCMMSGID_HOSTCALL:
513 {
514 LogFlow(("HGCMMSGID_HOSTCALL\n"));
515
516 HGCMMsgHostCall *pMsg = (HGCMMsgHostCall *)pMsgCore;
517
518 pSvc->m_fntable.pfnHostCall ((VBOXHGCMCALLHANDLE)pMsg, 0, NULL, pMsg->u32Function, pMsg->cParms, pMsg->paParms);
519
520 rc = VINF_SUCCESS;
521 } break;
522
523 default:
524 {
525 Log(("hgcmServiceThread::Unsupported message number %08X\n", pMsgCore->MsgId ()));
526 rc = VERR_NOT_SUPPORTED;
527 } break;
528 }
529
530 if ( pMsgCore->MsgId () != HGCMMSGID_GUESTCALL
531 && pMsgCore->MsgId () != HGCMMSGID_HOSTCALL)
532 {
533 /* For HGCMMSGID_GUESTCALL & HGCMMSGID_HOSTCALL the service
534 * calls the completion helper. Other messages have to be
535 * completed here.
536 */
537 hgcmMsgComplete (pMsgCore, rc);
538 }
539 }
540
541 return;
542}
543
544int HGCMService::InstanceCreate (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort)
545{
546 int rc = VINF_SUCCESS;
547
548 LogFlow(("HGCMService::InstanceCreate: name %s, lib %s\n", pszServiceName, pszServiceLibrary));
549
550 char achThreadName[14];
551
552 RTStrPrintf (achThreadName, sizeof (achThreadName), "HGCM%08X", this);
553
554 /* @todo do a proper fix 0x12345678 -> sizeof */
555 rc = hgcmThreadCreate (&m_thread, achThreadName, hgcmServiceThread, this, 0x12345678 /* sizeof (HGCMMsgCall) */);
556
557 if (VBOX_SUCCESS(rc))
558 {
559 m_pszSvcName = RTStrDup (pszServiceName);
560 m_pszSvcLibrary = RTStrDup (pszServiceLibrary);
561
562 if (!m_pszSvcName || !m_pszSvcLibrary)
563 {
564 RTStrFree (m_pszSvcLibrary);
565 m_pszSvcLibrary = NULL;
566
567 RTStrFree (m_pszSvcName);
568 m_pszSvcName = NULL;
569
570 rc = VERR_NO_MEMORY;
571 }
572 else
573 {
574 m_pHGCMPort = pHGCMPort;
575
576 m_svcHelpers.pfnCallComplete = svcHlpCallComplete;
577 m_svcHelpers.pvInstance = this;
578
579 /* Execute the load request on the service thread. */
580 HGCMMSGHANDLE hMsg;
581
582 rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_LOAD, sizeof (HGCMMsgSvcLoad), hgcmMessageAlloc);
583
584 if (VBOX_SUCCESS(rc))
585 {
586 rc = hgcmMsgSend (hMsg);
587 }
588 }
589 }
590 else
591 {
592 Log(("HGCMService::InstanceCreate: FAILURE: service thread creation\n"));
593 }
594
595 if (VBOX_FAILURE(rc))
596 {
597 InstanceDestroy ();
598 }
599
600 LogFlow(("HGCMService::InstanceCreate rc = %Vrc\n", rc));
601
602 return rc;
603}
604
605void HGCMService::InstanceDestroy (void)
606{
607 HGCMMSGHANDLE hMsg;
608
609 LogFlow(("HGCMService::InstanceDestroy\n"));
610
611 int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_UNLOAD, sizeof (HGCMMsgSvcUnload), hgcmMessageAlloc);
612
613 if (VBOX_SUCCESS(rc))
614 {
615 rc = hgcmMsgSend (hMsg);
616 }
617
618 RTStrFree (m_pszSvcLibrary);
619 m_pszSvcLibrary = NULL;
620 RTStrFree (m_pszSvcName);
621 m_pszSvcName = NULL;
622
623 LogFlow(("HGCMService::InstanceDestroy completed\n"));
624}
625
626bool HGCMService::EqualToLoc (HGCMServiceLocation *loc)
627{
628 if (!loc || (loc->type != VMMDevHGCMLoc_LocalHost && loc->type != VMMDevHGCMLoc_LocalHost_Existing))
629 {
630 return false;
631 }
632
633 if (strcmp (m_pszSvcName, loc->u.host.achName) != 0)
634 {
635 return false;
636 }
637
638 return true;
639}
640
641/** Services are searched by FindService function which is called
642 * by the main HGCM thread during CONNECT message processing.
643 * Reference count of the service is increased.
644 * Services are loaded by the LoadService function.
645 * Note: both methods are executed by the main HGCM thread.
646 */
647/* static */ int HGCMService::FindService (HGCMService **ppsvc, HGCMServiceLocation *loc)
648{
649 HGCMService *psvc = NULL;
650
651 LogFlow(("HGCMService::FindService: loc = %p\n", loc));
652
653 if (!loc || (loc->type != VMMDevHGCMLoc_LocalHost && loc->type != VMMDevHGCMLoc_LocalHost_Existing))
654 {
655 return VERR_INVALID_PARAMETER;
656 }
657
658 LogFlow(("HGCMService::FindService: name %s\n", loc->u.host.achName));
659
660 /* Look at already loaded services. */
661 psvc = sm_pSvcListHead;
662
663 while (psvc)
664 {
665 if (psvc->EqualToLoc (loc))
666 {
667 break;
668 }
669
670 psvc = psvc->m_pSvcNext;
671 }
672
673 LogFlow(("HGCMService::FindService: lookup in the list is %p\n", psvc));
674
675 if (psvc)
676 {
677 ASMAtomicIncU32 (&psvc->m_u32RefCnt);
678
679 *ppsvc = psvc;
680
681 return VINF_SUCCESS;
682 }
683
684 return VERR_ACCESS_DENIED;
685}
686
687/* static */ HGCMService *HGCMService::FindServiceByName (const char *pszServiceName)
688{
689 HGCMService *psvc = sm_pSvcListHead;
690
691 while (psvc)
692 {
693 if (strcmp (psvc->m_pszSvcName, pszServiceName) == 0)
694 {
695 break;
696 }
697
698 psvc = psvc->m_pSvcNext;
699 }
700
701 return psvc;
702}
703
704/* static */ int HGCMService::LoadService (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort)
705{
706 int rc = VINF_SUCCESS;
707
708 HGCMService *psvc = NULL;
709
710 LogFlow(("HGCMService::LoadService: name = %s, lib %s\n", pszServiceName, pszServiceLibrary));
711
712 /* Look at already loaded services to avoid double loading. */
713 psvc = FindServiceByName (pszServiceName);
714
715 if (psvc)
716 {
717 LogFlow(("HGCMService::LoadService: Service already exists %p!!!\n", psvc));
718 }
719 else
720 {
721 psvc = new HGCMService ();
722
723 if (!psvc)
724 {
725 Log(("HGCMService::Load: memory allocation for the service failed!!!\n"));
726 rc = VERR_NO_MEMORY;
727 }
728
729 if (VBOX_SUCCESS(rc))
730 {
731 rc = psvc->InstanceCreate (pszServiceLibrary, pszServiceName, pHGCMPort);
732
733 if (VBOX_SUCCESS(rc))
734 {
735 /* Insert the just created service to list for future references. */
736 psvc->m_pSvcNext = sm_pSvcListHead;
737 psvc->m_pSvcPrev = NULL;
738
739 if (sm_pSvcListHead)
740 {
741 sm_pSvcListHead->m_pSvcPrev = psvc;
742 }
743 else
744 {
745 sm_pSvcListTail = psvc;
746 }
747
748 sm_pSvcListHead = psvc;
749
750 LogFlow(("HGCMService::LoadService: service %p\n", psvc));
751 }
752 }
753 }
754
755 return rc;
756}
757
758void HGCMService::ReleaseService (void)
759{
760 uint32_t u32RefCnt = ASMAtomicDecU32 (&m_u32RefCnt);
761
762 AssertRelease(u32RefCnt != ~0U);
763
764 if (u32RefCnt == 0)
765 {
766 /** @todo We do not unload services. Cache them all. Unloading will be later. HGCMObject! */
767
768 LogFlow(("HGCMService::ReleaseService: no more references.\n"));
769
770// InstanceDestroy ();
771
772// delete this;
773 }
774}
775
776/** Helper function to load a local service DLL.
777 *
778 * @return VBox code
779 */
780int HGCMService::loadServiceDLL (void)
781{
782 LogFlow(("HGCMService::loadServiceDLL: m_pszSvcLibrary = %s\n", m_pszSvcLibrary));
783
784 if (m_pszSvcLibrary == NULL)
785 {
786 return VERR_INVALID_PARAMETER;
787 }
788
789 int rc = VINF_SUCCESS;
790
791 rc = RTLdrLoad (m_pszSvcLibrary, &m_hLdrMod);
792
793 if (VBOX_SUCCESS(rc))
794 {
795 LogFlow(("HGCMService::loadServiceDLL: successfully loaded the library.\n"));
796
797 m_pfnLoad = NULL;
798
799 rc = RTLdrGetSymbol (m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad);
800
801 if (VBOX_FAILURE (rc) || !m_pfnLoad)
802 {
803 Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, rc = %d, m_pfnLoad = %p\n", VBOX_HGCM_SVCLOAD_NAME, rc, m_pfnLoad));
804
805 if (VBOX_SUCCESS(rc))
806 {
807 /* m_pfnLoad was NULL */
808 rc = VERR_SYMBOL_NOT_FOUND;
809 }
810 }
811
812 if (VBOX_SUCCESS(rc))
813 {
814 memset (&m_fntable, 0, sizeof (m_fntable));
815
816 m_fntable.cbSize = sizeof (m_fntable);
817 m_fntable.u32Version = VBOX_HGCM_SVC_VERSION;
818 m_fntable.pHelpers = &m_svcHelpers;
819
820 rc = m_pfnLoad (&m_fntable);
821
822 LogFlow(("HGCMService::loadServiceDLL: m_pfnLoad rc = %Vrc\n", rc));
823
824 if (VBOX_SUCCESS (rc))
825 {
826 if ( m_fntable.pfnUnload == NULL
827 || m_fntable.pfnConnect == NULL
828 || m_fntable.pfnDisconnect == NULL
829 || m_fntable.pfnCall == NULL
830 )
831 {
832 Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n"));
833
834 rc = VERR_INVALID_PARAMETER;
835
836 if (m_fntable.pfnUnload)
837 {
838 m_fntable.pfnUnload ();
839 }
840 }
841 }
842 }
843 }
844 else
845 {
846 LogFlow(("HGCMService::loadServiceDLL: failed to load service library. The service is not available.\n"));
847 m_hLdrMod = NIL_RTLDRMOD;
848 }
849
850 if (VBOX_FAILURE(rc))
851 {
852 unloadServiceDLL ();
853 }
854
855 return rc;
856}
857
858/** Helper function to free a local service DLL.
859 *
860 * @return VBox code
861 */
862void HGCMService::unloadServiceDLL (void)
863{
864 if (m_hLdrMod)
865 {
866 RTLdrClose (m_hLdrMod);
867 }
868
869 memset (&m_fntable, 0, sizeof (m_fntable));
870 m_pfnLoad = NULL;
871 m_hLdrMod = NIL_RTLDRMOD;
872}
873
874
875int HGCMService::Connect (uint32_t u32ClientID)
876{
877 HGCMMSGHANDLE hMsg;
878
879 LogFlow(("MAIN::HGCMService::Connect: client id = %d\n", u32ClientID));
880
881 int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_CONNECT, sizeof (HGCMMsgSvcConnect), hgcmMessageAlloc);
882
883 if (VBOX_SUCCESS(rc))
884 {
885 HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)hgcmObjReference (hMsg);
886
887 AssertRelease(pMsg);
888
889 pMsg->u32ClientID = u32ClientID;
890
891 hgcmObjDereference (pMsg);
892
893 rc = hgcmMsgSend (hMsg);
894 }
895 else
896 {
897 Log(("MAIN::HGCMService::Connect: Message allocation failed: %Vrc\n", rc));
898 }
899
900 return rc;
901}
902
903int HGCMService::Disconnect (uint32_t u32ClientID)
904{
905 int rc = VINF_SUCCESS;
906
907 LogFlow(("MAIN::HGCMService::Disconnect: client id = %d\n", u32ClientID));
908
909 HGCMMSGHANDLE hMsg;
910
911 rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_DISCONNECT, sizeof (HGCMMsgSvcDisconnect), hgcmMessageAlloc);
912
913 if (VBOX_SUCCESS(rc))
914 {
915 HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)hgcmObjReference (hMsg);
916
917 AssertRelease(pMsg);
918
919 pMsg->u32ClientID = u32ClientID;
920
921 hgcmObjDereference (pMsg);
922
923 rc = hgcmMsgSend (hMsg);
924 }
925 else
926 {
927 Log(("MAIN::HGCMService::Disconnect: Message allocation failed: %Vrc\n", rc));
928 }
929
930 return rc;
931}
932
933/* Forward the call request to the dedicated service thread.
934 */
935int HGCMService::GuestCall (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fBlock)
936{
937 HGCMMSGHANDLE hMsg = 0;
938
939 LogFlow(("MAIN::HGCMService::Call\n"));
940
941 int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_GUESTCALL, sizeof (HGCMMsgCall), hgcmMessageAlloc);
942
943 if (VBOX_SUCCESS(rc))
944 {
945 HGCMMsgCall *pMsg = (HGCMMsgCall *)hgcmObjReference (hMsg);
946
947 AssertRelease(pMsg);
948
949 pMsg->pCmd = pCmd;
950 pMsg->pHGCMPort = pHGCMPort;
951
952 pMsg->u32ClientID = u32ClientID;
953 pMsg->u32Function = u32Function;
954 pMsg->cParms = cParms;
955 pMsg->paParms = paParms;
956
957 hgcmObjDereference (pMsg);
958
959 if (fBlock)
960 rc = hgcmMsgSend (hMsg);
961 else
962 rc = hgcmMsgPost (hMsg, hgcmMsgCompletionCallback);
963
964 if (!fBlock && VBOX_SUCCESS(rc))
965 {
966 rc = VINF_HGCM_ASYNC_EXECUTE;
967 }
968 }
969 else
970 {
971 Log(("MAIN::HGCMService::Call: Message allocation failed: %Vrc\n", rc));
972 }
973
974 return rc;
975}
976
977/* Forward the call request to the dedicated service thread.
978 */
979int HGCMService::HostCall (PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
980{
981 HGCMMSGHANDLE hMsg = 0;
982
983 LogFlow(("MAIN::HGCMService::HostCall %s\n", m_pszSvcName));
984
985 int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_HOSTCALL, sizeof (HGCMMsgHostCall), hgcmMessageAlloc);
986
987 if (VBOX_SUCCESS(rc))
988 {
989 HGCMMsgHostCall *pMsg = (HGCMMsgHostCall *)hgcmObjReference (hMsg);
990
991 AssertRelease(pMsg);
992
993 pMsg->pCmd = NULL; /* Not used for host calls. */
994 pMsg->pHGCMPort = NULL; /* Not used for host calls. */
995
996 pMsg->u32Function = u32Function;
997 pMsg->cParms = cParms;
998 pMsg->paParms = paParms;
999
1000 hgcmObjDereference (pMsg);
1001
1002 rc = hgcmMsgSend (hMsg);
1003 }
1004 else
1005 {
1006 Log(("MAIN::HGCMService::Call: Message allocation failed: %Vrc\n", rc));
1007 }
1008
1009 return rc;
1010}
1011
1012/* Main HGCM thread that processes CONNECT/DISCONNECT requests. */
1013static DECLCALLBACK(void) hgcmThread (HGCMTHREADHANDLE ThreadHandle, void *pvUser)
1014{
1015 NOREF(pvUser);
1016
1017 int rc = VINF_SUCCESS;
1018
1019 HGCMMsgCore *pMsgCore = NULL;
1020
1021 for (;;)
1022 {
1023 LogFlow(("hgcmThread: Going to get a message\n"));
1024
1025 rc = hgcmMsgGet (ThreadHandle, &pMsgCore);
1026
1027 if (VBOX_FAILURE (rc))
1028 {
1029 Log(("hgcmThread: message get failed, rc = %Vrc\n"));
1030 RTThreadSleep(100);
1031 continue;
1032 }
1033
1034 switch (pMsgCore->MsgId ())
1035 {
1036 case HGCMMSGID_CONNECT:
1037 {
1038 LogFlow(("HGCMMSGID_CONNECT\n"));
1039
1040 HGCMMsgConnect *pMsg = (HGCMMsgConnect *)pMsgCore;
1041
1042 /* Search if the service exists.
1043 * Create an information structure for the client.
1044 * Inform the service about the client.
1045 * Generate and return the client id.
1046 */
1047
1048 Log(("MAIN::hgcmThread:HGCMMSGID_CONNECT: location type = %d\n", pMsg->pLoc->type));
1049
1050 HGCMService *pService = NULL;
1051
1052 rc = HGCMService::FindService (&pService, pMsg->pLoc);
1053
1054 if (VBOX_SUCCESS (rc))
1055 {
1056 /* Allocate a client information structure */
1057
1058 HGCMClient *pClient = new HGCMClient ();
1059
1060 if (!pClient)
1061 {
1062 Log(("hgcmConnect::Could not allocate HGCMClient\n"));
1063 rc = VERR_NO_MEMORY;
1064 }
1065 else
1066 {
1067 uint32_t handle = hgcmObjGenerateHandle (pClient);
1068
1069 AssertRelease(handle);
1070
1071 rc = pClient->Init (pService);
1072
1073 if (VBOX_SUCCESS(rc))
1074 {
1075 rc = pService->Connect (handle);
1076 }
1077
1078 if (VBOX_FAILURE(rc))
1079 {
1080 hgcmObjDeleteHandle (handle);
1081 }
1082 else
1083 {
1084 *pMsg->pu32ClientID = handle;
1085 }
1086 }
1087 }
1088
1089 if (VBOX_FAILURE(rc))
1090 {
1091 LogFlow(("HGCMMSGID_CONNECT: FAILURE rc = %Vrc\n", rc));
1092
1093 if (pService)
1094 {
1095 pService->ReleaseService ();
1096 }
1097 }
1098
1099 } break;
1100
1101 case HGCMMSGID_DISCONNECT:
1102 {
1103 LogFlow(("HGCMMSGID_DISCONNECT\n"));
1104
1105 HGCMMsgDisconnect *pMsg = (HGCMMsgDisconnect *)pMsgCore;
1106
1107 Log(("MAIN::hgcmThread:HGCMMSGID_DISCONNECT: client id = %d\n", pMsg->u32ClientID));
1108
1109 /* Forward call to the service dedicated HGCM thread. */
1110 HGCMClient *pClient = (HGCMClient *)hgcmObjReference (pMsg->u32ClientID);
1111
1112 if (!pClient)
1113 {
1114 Log(("MAIN::hgcmThread:HGCMMSGID_DISCONNECT: FAILURE resolving client id\n"));
1115 rc = VERR_INVALID_PARAMETER;
1116 }
1117 else
1118 {
1119 rc = pClient->pService->Disconnect (pMsg->u32ClientID);
1120
1121 pClient->pService->ReleaseService ();
1122
1123 hgcmObjDereference (pClient);
1124
1125 hgcmObjDeleteHandle (pMsg->u32ClientID);
1126 }
1127
1128 } break;
1129
1130 case HGCMMSGID_LOAD:
1131 {
1132 LogFlow(("HGCMMSGID_LOAD\n"));
1133
1134 HGCMMsgLoad *pMsg = (HGCMMsgLoad *)pMsgCore;
1135
1136 rc = HGCMService::LoadService (pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pHGCMPort);
1137 } break;
1138
1139
1140 case HGCMMSGID_HOSTCALL:
1141 {
1142 LogFlow(("HGCMMSGID_HOSTCALL at hgcmThread\n"));
1143
1144 HGCMMsgHostCall *pMsg = (HGCMMsgHostCall *)pMsgCore;
1145
1146 HGCMService *pService = HGCMService::FindServiceByName (pMsg->pszServiceName);
1147
1148 if (pService)
1149 {
1150 LogFlow(("HGCMMSGID_HOSTCALL found service, forwarding the call.\n"));
1151 pService->HostCall (NULL, 0, pMsg->u32Function, pMsg->cParms, pMsg->paParms);
1152 }
1153 } break;
1154
1155 default:
1156 {
1157 Log(("hgcmThread: Unsupported message number %08X!!!\n", pMsgCore->MsgId ()));
1158 rc = VERR_NOT_SUPPORTED;
1159 } break;
1160 }
1161
1162 hgcmMsgComplete (pMsgCore, rc);
1163 }
1164
1165 return;
1166}
1167
1168static HGCMTHREADHANDLE g_hgcmThread = 0;
1169
1170/*
1171 * Find a service and inform it about a client connection.
1172 * Main HGCM thread will process this request for serialization.
1173 */
1174int hgcmConnectInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, PHGCMSERVICELOCATION pLoc, uint32_t *pu32ClientID, bool fBlock)
1175{
1176 int rc = VINF_SUCCESS;
1177
1178 LogFlow(("MAIN::hgcmConnectInternal: pHGCMPort = %p, pCmd = %p, loc = %p, pu32ClientID = %p\n",
1179 pHGCMPort, pCmd, pLoc, pu32ClientID));
1180
1181 if (!pHGCMPort || !pCmd || !pLoc || !pu32ClientID)
1182 {
1183 return VERR_INVALID_PARAMETER;
1184 }
1185
1186 HGCMMSGHANDLE hMsg = 0;
1187
1188 rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_CONNECT, sizeof (HGCMMsgConnect), hgcmMessageAlloc);
1189
1190 if (VBOX_SUCCESS(rc))
1191 {
1192 HGCMMsgConnect *pMsg = (HGCMMsgConnect *)hgcmObjReference (hMsg);
1193
1194 AssertRelease(pMsg);
1195
1196 pMsg->pCmd = pCmd;
1197 pMsg->pHGCMPort = pHGCMPort;
1198
1199 pMsg->pLoc = pLoc;
1200 pMsg->pu32ClientID = pu32ClientID;
1201
1202 if (fBlock)
1203 rc = hgcmMsgSend (hMsg);
1204 else
1205 rc = hgcmMsgPost (hMsg, hgcmMsgCompletionCallback);
1206
1207 hgcmObjDereference (pMsg);
1208
1209 LogFlow(("MAIN::hgcmConnectInternal: hgcmMsgPost returned %Vrc\n", rc));
1210
1211 if (!fBlock && VBOX_SUCCESS(rc))
1212 {
1213 rc = VINF_HGCM_ASYNC_EXECUTE;
1214 }
1215 }
1216 else
1217 {
1218 Log(("MAIN::hgcmConnectInternal:Message allocation failed: %Vrc\n", rc));
1219 }
1220
1221
1222 return rc;
1223}
1224
1225/*
1226 * Tell a service that the client is disconnecting.
1227 * Main HGCM thread will process this request for serialization.
1228 */
1229int hgcmDisconnectInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, bool fBlock)
1230{
1231 int rc = VINF_SUCCESS;
1232
1233 LogFlow(("MAIN::hgcmDisconnectInternal: pHGCMPort = %p, pCmd = %p, u32ClientID = %d\n",
1234 pHGCMPort, pCmd, u32ClientID));
1235
1236 if (!pHGCMPort || !pCmd)
1237 {
1238 return VERR_INVALID_PARAMETER;
1239 }
1240
1241 HGCMMSGHANDLE hMsg = 0;
1242
1243 rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_DISCONNECT, sizeof (HGCMMsgDisconnect), hgcmMessageAlloc);
1244
1245 if (VBOX_SUCCESS(rc))
1246 {
1247 HGCMMsgDisconnect *pMsg = (HGCMMsgDisconnect *)hgcmObjReference (hMsg);
1248
1249 AssertRelease(pMsg);
1250
1251 pMsg->pCmd = pCmd;
1252 pMsg->pHGCMPort = pHGCMPort;
1253
1254 pMsg->u32ClientID = u32ClientID;
1255
1256 if (fBlock)
1257 rc = hgcmMsgSend (hMsg);
1258 else
1259 rc = hgcmMsgPost (hMsg, hgcmMsgCompletionCallback);
1260
1261 hgcmObjDereference (pMsg);
1262
1263 if (!fBlock && VBOX_SUCCESS(rc))
1264 {
1265 rc = VINF_HGCM_ASYNC_EXECUTE;
1266 }
1267 }
1268 else
1269 {
1270 Log(("MAIN::hgcmDisconnectInternal: Message allocation failed: %Vrc\n", rc));
1271 }
1272
1273
1274 return rc;
1275}
1276
1277int hgcmLoadInternal (const char *pszServiceName, const char *pszServiceLibrary)
1278{
1279 int rc = VINF_SUCCESS;
1280
1281 LogFlow(("MAIN::hgcmLoadInternal: name = %s, lib = %s\n",
1282 pszServiceName, pszServiceLibrary));
1283
1284 if (!pszServiceName || !pszServiceLibrary)
1285 {
1286 return VERR_INVALID_PARAMETER;
1287 }
1288
1289 HGCMMSGHANDLE hMsg = 0;
1290
1291 rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_LOAD, sizeof (HGCMMsgLoad), hgcmMessageAlloc);
1292
1293 if (VBOX_SUCCESS(rc))
1294 {
1295 HGCMMsgLoad *pMsg = (HGCMMsgLoad *)hgcmObjReference (hMsg);
1296
1297 AssertRelease(pMsg);
1298
1299 pMsg->pHGCMPort = NULL; /* Not used by the call. */
1300
1301 rc = pMsg->Init (pszServiceName, pszServiceLibrary);
1302
1303 if (VBOX_SUCCESS (rc))
1304 {
1305 rc = hgcmMsgSend (hMsg);
1306 }
1307
1308 hgcmObjDereference (pMsg);
1309
1310 LogFlow(("MAIN::hgcm:LoadInternal: hgcmMsgSend returned %Vrc\n", rc));
1311 }
1312 else
1313 {
1314 Log(("MAIN::hgcmLoadInternal:Message allocation failed: %Vrc\n", rc));
1315 }
1316
1317
1318 return rc;
1319}
1320
1321/*
1322 * Call a service.
1323 * The service dedicated thread will process this request.
1324 */
1325int hgcmGuestCallInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], bool fBlock)
1326{
1327 int rc = VINF_SUCCESS;
1328
1329 LogFlow(("MAIN::hgcmCallInternal: pHGCMPort = %p, pCmd = %p, u32ClientID = %d, u32Function = %d, cParms = %d, aParms = %p\n",
1330 pHGCMPort, pCmd, u32ClientID, u32Function, cParms, aParms));
1331
1332 if (!pHGCMPort || !pCmd)
1333 {
1334 return VERR_INVALID_PARAMETER;
1335 }
1336
1337 HGCMClient *pClient = (HGCMClient *)hgcmObjReference (u32ClientID);
1338
1339 if (!pClient)
1340 {
1341 Log(("MAIN::hgcmCallInternal: FAILURE resolving client id %d\n", u32ClientID));
1342 return VERR_INVALID_PARAMETER;
1343 }
1344
1345 AssertRelease(pClient->pService);
1346
1347 rc = pClient->pService->GuestCall (pHGCMPort, pCmd, u32ClientID, u32Function, cParms, aParms, fBlock);
1348
1349 hgcmObjDereference (pClient);
1350
1351 return rc;
1352}
1353
1354/*
1355 * Call a service.
1356 * The service dedicated thread will process this request.
1357 */
1358int hgcmHostCallInternal (const char *pszServiceName, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[])
1359{
1360 int rc = VINF_SUCCESS;
1361
1362 LogFlow(("MAIN::hgcmHostCallInternal: service = %s, u32Function = %d, cParms = %d, aParms = %p\n",
1363 pszServiceName, u32Function, cParms, aParms));
1364
1365 if (!pszServiceName)
1366 {
1367 return VERR_INVALID_PARAMETER;
1368 }
1369
1370 HGCMMSGHANDLE hMsg = 0;
1371
1372 rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_HOSTCALL, sizeof (HGCMMsgHostCall), hgcmMessageAlloc);
1373
1374 if (VBOX_SUCCESS(rc))
1375 {
1376 HGCMMsgHostCall *pMsg = (HGCMMsgHostCall *)hgcmObjReference (hMsg);
1377
1378 AssertRelease(pMsg);
1379
1380 pMsg->pHGCMPort = NULL; /* Not used. */
1381
1382 pMsg->pszServiceName = (char *)pszServiceName;
1383 pMsg->u32Function = u32Function;
1384 pMsg->cParms = cParms;
1385 pMsg->paParms = &aParms[0];
1386
1387 rc = hgcmMsgSend (hMsg);
1388
1389 hgcmObjDereference (pMsg);
1390
1391 LogFlow(("MAIN::hgcm:HostCallInternal: hgcmMsgSend returned %Vrc\n", rc));
1392 }
1393 else
1394 {
1395 Log(("MAIN::hgcmHostCallInternal:Message allocation failed: %Vrc\n", rc));
1396 }
1397
1398 return rc;
1399}
1400
1401
1402int hgcmInit (void)
1403{
1404 int rc = VINF_SUCCESS;
1405
1406 Log(("MAIN::hgcmInit\n"));
1407
1408 rc = hgcmThreadInit ();
1409
1410 if (VBOX_FAILURE(rc))
1411 {
1412 Log(("FAILURE: Can't init worker threads.\n"));
1413 }
1414 else
1415 {
1416 /* Start main HGCM thread that will process Connect/Disconnect requests. */
1417
1418 /* @todo do a proper fix 0x12345678 -> sizeof */
1419 rc = hgcmThreadCreate (&g_hgcmThread, "MainHGCMthread", hgcmThread, NULL, 0x12345678 /*sizeof (HGCMMsgConnect)*/);
1420
1421 if (VBOX_FAILURE (rc))
1422 {
1423 Log(("FAILURE: HGCM initialization.\n"));
1424 }
1425 }
1426
1427 LogFlow(("MAIN::hgcmInit: rc = %Vrc\n", rc));
1428
1429 return rc;
1430}
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