VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/req.cpp@ 93174

Last change on this file since 93174 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 19.5 KB
Line 
1/* $Id: req.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * IPRT - Request packets
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/req.h>
32#include "internal/iprt.h"
33
34#include <iprt/assert.h>
35#include <iprt/asm.h>
36#include <iprt/err.h>
37#include <iprt/string.h>
38#include <iprt/time.h>
39#include <iprt/semaphore.h>
40#include <iprt/thread.h>
41#include <iprt/log.h>
42#include <iprt/mem.h>
43
44#include "internal/req.h"
45#include "internal/magics.h"
46
47
48/*********************************************************************************************************************************
49* Internal Functions *
50*********************************************************************************************************************************/
51
52
53/**
54 * Allocate a new request from the heap.
55 *
56 * @returns IPRT status code.
57 * @param enmType The reques type.
58 * @param fPoolOrQueue The owner type.
59 * @param pvOwner The owner.
60 * @param phReq Where to return the request handle.
61 */
62DECLHIDDEN(int) rtReqAlloc(RTREQTYPE enmType, bool fPoolOrQueue, void *pvOwner, PRTREQ *phReq)
63{
64 PRTREQ pReq = (PRTREQ)RTMemAllocZ(sizeof(*pReq));
65 if (RT_UNLIKELY(!pReq))
66 return VERR_NO_MEMORY;
67
68 /*
69 * Create the semaphore used for waiting.
70 */
71 int rc = RTSemEventCreate(&pReq->EventSem);
72 AssertRCReturnStmt(rc, RTMemFree(pReq), rc);
73
74 /*
75 * Initialize the packet and return it.
76 */
77 pReq->u32Magic = RTREQ_MAGIC;
78 pReq->fEventSemClear = true;
79 pReq->fSignalPushBack = true;
80 pReq->fPoolOrQueue = fPoolOrQueue;
81 pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
82 pReq->enmState = RTREQSTATE_ALLOCATED;
83 pReq->pNext = NULL;
84 pReq->uOwner.pv = pvOwner;
85 pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
86 pReq->enmType = enmType;
87 pReq->cRefs = 1;
88
89 *phReq = pReq;
90 return VINF_SUCCESS;
91}
92
93
94/**
95 * Re-initializes a request when it's being recycled.
96 *
97 * @returns IRPT status code, the request is freed on failure.
98 * @param pReq The request.
99 * @param enmType The request type.
100 */
101DECLHIDDEN(int) rtReqReInit(PRTREQINT pReq, RTREQTYPE enmType)
102{
103 Assert(pReq->u32Magic == RTREQ_MAGIC);
104 Assert(pReq->enmType == RTREQTYPE_INVALID);
105 Assert(pReq->enmState == RTREQSTATE_FREE);
106 Assert(pReq->cRefs == 0);
107
108 /*
109 * Make sure the event sem is not signaled.
110 */
111 if (!pReq->fEventSemClear)
112 {
113 int rc = RTSemEventWait(pReq->EventSem, 0);
114 if (rc != VINF_SUCCESS && rc != VERR_TIMEOUT)
115 {
116 /*
117 * This shall not happen, but if it does we'll just destroy
118 * the semaphore and create a new one.
119 */
120 AssertMsgFailed(("rc=%Rrc from RTSemEventWait(%#x).\n", rc, pReq->EventSem));
121 RTSemEventDestroy(pReq->EventSem);
122 rc = RTSemEventCreate(&pReq->EventSem);
123 if (RT_FAILURE(rc))
124 {
125 AssertRC(rc);
126 pReq->EventSem = NIL_RTSEMEVENT;
127 rtReqFreeIt(pReq);
128 return rc;
129 }
130 }
131 pReq->fEventSemClear = true;
132 }
133 else
134 Assert(RTSemEventWait(pReq->EventSem, 0) == VERR_TIMEOUT);
135
136 /*
137 * Initialize the packet and return it.
138 */
139 ASMAtomicWriteNullPtr(&pReq->pNext);
140 pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
141 pReq->enmState = RTREQSTATE_ALLOCATED;
142 pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
143 pReq->enmType = enmType;
144 pReq->cRefs = 1;
145 return VINF_SUCCESS;
146}
147
148
149RTDECL(uint32_t) RTReqRetain(PRTREQ hReq)
150{
151 PRTREQINT pReq = hReq;
152 AssertPtrReturn(pReq, UINT32_MAX);
153 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
154
155 return ASMAtomicIncU32(&pReq->cRefs);
156}
157RT_EXPORT_SYMBOL(RTReqRetain);
158
159
160/**
161 * Frees a request.
162 *
163 * @param pReq The request.
164 */
165DECLHIDDEN(void) rtReqFreeIt(PRTREQINT pReq)
166{
167 Assert(pReq->u32Magic == RTREQ_MAGIC);
168 Assert(pReq->cRefs == 0);
169
170 pReq->u32Magic = RTREQ_MAGIC_DEAD;
171 RTSemEventDestroy(pReq->EventSem);
172 pReq->EventSem = NIL_RTSEMEVENT;
173 RTSemEventMultiDestroy(pReq->hPushBackEvt);
174 pReq->hPushBackEvt = NIL_RTSEMEVENTMULTI;
175 RTMemFree(pReq);
176}
177
178
179RTDECL(uint32_t) RTReqRelease(PRTREQ hReq)
180{
181 /*
182 * Ignore NULL and validate the request.
183 */
184 if (!hReq)
185 return 0;
186 PRTREQINT pReq = hReq;
187 AssertPtrReturn(pReq, UINT32_MAX);
188 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
189
190 /*
191 * Drop a reference, recycle the request when we reach 0.
192 */
193 uint32_t cRefs = ASMAtomicDecU32(&pReq->cRefs);
194 if (cRefs == 0)
195 {
196 /*
197 * Check packet state.
198 */
199 RTREQSTATE const enmState = pReq->enmState;
200 switch (enmState)
201 {
202 case RTREQSTATE_ALLOCATED:
203 case RTREQSTATE_COMPLETED:
204 break;
205 default:
206 AssertMsgFailedReturn(("Invalid state %d!\n", enmState), 0);
207 }
208
209 /*
210 * Make it a free packet and put it into one of the free packet lists.
211 */
212 pReq->enmState = RTREQSTATE_FREE;
213 pReq->iStatusX = VERR_RT_REQUEST_STATUS_FREED;
214 pReq->enmType = RTREQTYPE_INVALID;
215
216 bool fRecycled;
217 if (pReq->fPoolOrQueue)
218 fRecycled = rtReqPoolRecycle(pReq->uOwner.hPool, pReq);
219 else
220 fRecycled = rtReqQueueRecycle(pReq->uOwner.hQueue, pReq);
221 if (!fRecycled)
222 rtReqFreeIt(pReq);
223 }
224
225 return cRefs;
226}
227RT_EXPORT_SYMBOL(RTReqRelease);
228
229
230RTDECL(int) RTReqSubmit(PRTREQ hReq, RTMSINTERVAL cMillies)
231{
232 LogFlow(("RTReqSubmit: hReq=%p cMillies=%d\n", hReq, cMillies));
233
234 /*
235 * Verify the supplied package.
236 */
237 PRTREQINT pReq = hReq;
238 AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
239 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
240 AssertMsgReturn(pReq->enmState == RTREQSTATE_ALLOCATED, ("%d\n", pReq->enmState), VERR_RT_REQUEST_STATE);
241 AssertMsgReturn(pReq->uOwner.hQueue && !pReq->pNext && pReq->EventSem != NIL_RTSEMEVENT,
242 ("Invalid request package! Anyone cooking their own packages???\n"),
243 VERR_RT_REQUEST_INVALID_PACKAGE);
244 AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
245 ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
246 pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
247 VERR_RT_REQUEST_INVALID_TYPE);
248
249 /*
250 * Insert it. Always grab a reference for the queue (we used to
251 * donate the caller's reference in the NO_WAIT case once upon a time).
252 */
253 pReq->uSubmitNanoTs = RTTimeNanoTS();
254 pReq->enmState = RTREQSTATE_QUEUED;
255 unsigned fFlags = ((RTREQ volatile *)pReq)->fFlags; /* volatile paranoia */
256 RTReqRetain(pReq);
257
258 if (!pReq->fPoolOrQueue)
259 rtReqQueueSubmit(pReq->uOwner.hQueue, pReq);
260 else
261 rtReqPoolSubmit(pReq->uOwner.hPool, pReq);
262
263 /*
264 * Wait and return.
265 */
266 int rc = VINF_SUCCESS;
267 if (!(fFlags & RTREQFLAGS_NO_WAIT))
268 rc = RTReqWait(pReq, cMillies);
269
270 LogFlow(("RTReqSubmit: returns %Rrc\n", rc));
271 return rc;
272}
273RT_EXPORT_SYMBOL(RTReqSubmit);
274
275
276RTDECL(int) RTReqWait(PRTREQ hReq, RTMSINTERVAL cMillies)
277{
278 LogFlow(("RTReqWait: hReq=%p cMillies=%d\n", hReq, cMillies));
279
280 /*
281 * Verify the supplied package.
282 */
283 PRTREQINT pReq = hReq;
284 AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
285 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
286 RTREQSTATE enmState = pReq->enmState;
287 AssertMsgReturn( enmState == RTREQSTATE_QUEUED
288 || enmState == RTREQSTATE_PROCESSING
289 || enmState == RTREQSTATE_COMPLETED
290 || enmState == RTREQSTATE_CANCELLED,
291 ("Invalid state %d\n", enmState),
292 VERR_RT_REQUEST_STATE);
293 AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
294 ("Invalid request package! Anyone cooking their own packages???\n"),
295 VERR_RT_REQUEST_INVALID_PACKAGE);
296 AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
297 ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
298 pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
299 VERR_RT_REQUEST_INVALID_TYPE);
300
301 /*
302 * Wait on the package.
303 */
304 int rc;
305 if (cMillies != RT_INDEFINITE_WAIT)
306 rc = RTSemEventWait(pReq->EventSem, cMillies);
307 else
308 {
309 do
310 {
311 rc = RTSemEventWait(pReq->EventSem, RT_INDEFINITE_WAIT);
312 Assert(rc != VERR_TIMEOUT);
313 } while (pReq->enmState != RTREQSTATE_COMPLETED);
314 }
315 if (rc == VINF_SUCCESS)
316 ASMAtomicWriteBool(&pReq->fEventSemClear, true);
317 if (pReq->enmState == RTREQSTATE_COMPLETED)
318 rc = VINF_SUCCESS;
319 LogFlow(("RTReqWait: returns %Rrc\n", rc));
320 Assert(rc != VERR_INTERRUPTED);
321 Assert(pReq->cRefs >= 1);
322 return rc;
323}
324RT_EXPORT_SYMBOL(RTReqWait);
325
326
327RTDECL(int) RTReqCancel(PRTREQ hReq)
328{
329 LogFlow(("RTReqCancel: hReq=%p\n", hReq));
330
331 /*
332 * Verify the supplied package.
333 */
334 PRTREQINT pReq = hReq;
335 AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
336 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
337 AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
338 ("Invalid request package! Anyone cooking their own packages???\n"),
339 VERR_RT_REQUEST_INVALID_PACKAGE);
340 AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
341 ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
342 pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
343 VERR_RT_REQUEST_INVALID_TYPE);
344
345 /*
346 * Try cancel the request itself by changing its state.
347 */
348 int rc;
349 if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_CANCELLED, RTREQSTATE_QUEUED))
350 {
351 if (pReq->fPoolOrQueue)
352 rtReqPoolCancel(pReq->uOwner.hPool, pReq);
353 rc = VINF_SUCCESS;
354 }
355 else
356 {
357 Assert(pReq->enmState == RTREQSTATE_PROCESSING || pReq->enmState == RTREQSTATE_COMPLETED);
358 rc = VERR_RT_REQUEST_STATE;
359 }
360
361 LogFlow(("RTReqCancel: returns %Rrc\n", rc));
362 return rc;
363}
364RT_EXPORT_SYMBOL(RTReqCancel);
365
366
367RTDECL(int) RTReqGetStatus(PRTREQ hReq)
368{
369 PRTREQINT pReq = hReq;
370 AssertPtrReturn(pReq, VERR_INVALID_POINTER);
371 AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_POINTER);
372 return pReq->iStatusX;
373}
374RT_EXPORT_SYMBOL(RTReqGetStatus);
375
376
377
378/**
379 * Process one request.
380 *
381 * @returns IPRT status code.
382 *
383 * @param pReq Request packet to process.
384 */
385DECLHIDDEN(int) rtReqProcessOne(PRTREQINT pReq)
386{
387 LogFlow(("rtReqProcessOne: pReq=%p type=%d fFlags=%#x\n", pReq, pReq->enmType, pReq->fFlags));
388
389 /*
390 * Try switch the request status to processing.
391 */
392 int rcRet = VINF_SUCCESS; /* the return code of this function. */
393 int rcReq = VERR_NOT_IMPLEMENTED; /* the request status. */
394 if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_PROCESSING, RTREQSTATE_QUEUED))
395 {
396 /*
397 * Process the request.
398 */
399 pReq->enmState = RTREQSTATE_PROCESSING;
400 switch (pReq->enmType)
401 {
402 /*
403 * A packed down call frame.
404 */
405 case RTREQTYPE_INTERNAL:
406 {
407 uintptr_t *pauArgs = &pReq->u.Internal.aArgs[0];
408 union
409 {
410 PFNRT pfn;
411 DECLCALLBACKMEMBER(int, pfn00,(void));
412 DECLCALLBACKMEMBER(int, pfn01,(uintptr_t));
413 DECLCALLBACKMEMBER(int, pfn02,(uintptr_t, uintptr_t));
414 DECLCALLBACKMEMBER(int, pfn03,(uintptr_t, uintptr_t, uintptr_t));
415 DECLCALLBACKMEMBER(int, pfn04,(uintptr_t, uintptr_t, uintptr_t, uintptr_t));
416 DECLCALLBACKMEMBER(int, pfn05,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
417 DECLCALLBACKMEMBER(int, pfn06,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
418 DECLCALLBACKMEMBER(int, pfn07,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
419 DECLCALLBACKMEMBER(int, pfn08,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
420 DECLCALLBACKMEMBER(int, pfn09,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
421 DECLCALLBACKMEMBER(int, pfn10,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
422 DECLCALLBACKMEMBER(int, pfn11,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
423 DECLCALLBACKMEMBER(int, pfn12,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
424 } u;
425 u.pfn = pReq->u.Internal.pfn;
426#ifndef RT_ARCH_X86
427 switch (pReq->u.Internal.cArgs)
428 {
429 case 0: rcRet = u.pfn00(); break;
430 case 1: rcRet = u.pfn01(pauArgs[0]); break;
431 case 2: rcRet = u.pfn02(pauArgs[0], pauArgs[1]); break;
432 case 3: rcRet = u.pfn03(pauArgs[0], pauArgs[1], pauArgs[2]); break;
433 case 4: rcRet = u.pfn04(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3]); break;
434 case 5: rcRet = u.pfn05(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4]); break;
435 case 6: rcRet = u.pfn06(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5]); break;
436 case 7: rcRet = u.pfn07(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6]); break;
437 case 8: rcRet = u.pfn08(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7]); break;
438 case 9: rcRet = u.pfn09(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8]); break;
439 case 10: rcRet = u.pfn10(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9]); break;
440 case 11: rcRet = u.pfn11(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10]); break;
441 case 12: rcRet = u.pfn12(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10], pauArgs[11]); break;
442 default:
443 AssertReleaseMsgFailed(("cArgs=%d\n", pReq->u.Internal.cArgs));
444 rcRet = rcReq = VERR_INTERNAL_ERROR;
445 break;
446 }
447#else /* RT_ARCH_X86 */
448 size_t cbArgs = pReq->u.Internal.cArgs * sizeof(uintptr_t);
449# ifdef __GNUC__
450 __asm__ __volatile__("movl %%esp, %%edx\n\t"
451 "subl %2, %%esp\n\t"
452 "andl $0xfffffff0, %%esp\n\t"
453 "shrl $2, %2\n\t"
454 "movl %%esp, %%edi\n\t"
455 "rep movsl\n\t"
456 "movl %%edx, %%edi\n\t"
457 "call *%%eax\n\t"
458 "mov %%edi, %%esp\n\t"
459 : "=a" (rcRet),
460 "=S" (pauArgs),
461 "=c" (cbArgs)
462 : "0" (u.pfn),
463 "1" (pauArgs),
464 "2" (cbArgs)
465 : "edi", "edx");
466# else
467 __asm
468 {
469 xor edx, edx /* just mess it up. */
470 mov eax, u.pfn
471 mov ecx, cbArgs
472 shr ecx, 2
473 mov esi, pauArgs
474 mov ebx, esp
475 sub esp, cbArgs
476 and esp, 0xfffffff0
477 mov edi, esp
478 rep movsd
479 call eax
480 mov esp, ebx
481 mov rcRet, eax
482 }
483# endif
484#endif /* RT_ARCH_X86 */
485 if ((pReq->fFlags & (RTREQFLAGS_RETURN_MASK)) == RTREQFLAGS_VOID)
486 rcRet = VINF_SUCCESS;
487 rcReq = rcRet;
488 break;
489 }
490
491 default:
492 AssertMsgFailed(("pReq->enmType=%d\n", pReq->enmType));
493 rcReq = VERR_NOT_IMPLEMENTED;
494 break;
495 }
496 }
497 else
498 {
499 Assert(pReq->enmState == RTREQSTATE_CANCELLED);
500 rcReq = VERR_CANCELLED;
501 }
502
503 /*
504 * Complete the request and then release our request handle reference.
505 */
506 pReq->iStatusX = rcReq;
507 pReq->enmState = RTREQSTATE_COMPLETED;
508 if (pReq->fFlags & RTREQFLAGS_NO_WAIT)
509 LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc (no wait)\n",
510 pReq, rcReq, rcRet));
511 else
512 {
513 /* Notify the waiting thread. */
514 LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc - notifying waiting thread\n",
515 pReq, rcReq, rcRet));
516 ASMAtomicWriteBool(&pReq->fEventSemClear, false);
517 int rc2 = RTSemEventSignal(pReq->EventSem);
518 if (rc2 != VINF_SUCCESS)
519 {
520 AssertRC(rc2);
521 rcRet = rc2;
522 }
523 }
524 RTReqRelease(pReq);
525 return rcRet;
526}
527
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