VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c@ 51789

最後變更 在這個檔案從51789是 51770,由 vboxsync 提交於 10 年 前

Merged in iprt++ dev branch.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 17.1 KB
 
1/* $Id: SUPDrv-freebsd.c 51770 2014-07-01 18:14:02Z vboxsync $ */
2/** @file
3 * VBoxDrv - The VirtualBox Support Driver - FreeBSD specifics.
4 */
5
6/*
7 * Copyright (c) 2007 knut st. osmundsen <[email protected]>
8 *
9 * Permission is hereby granted, free of charge, to any person
10 * obtaining a copy of this software and associated documentation
11 * files (the "Software"), to deal in the Software without
12 * restriction, including without limitation the rights to use,
13 * copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following
16 * conditions:
17 *
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 * OTHER DEALINGS IN THE SOFTWARE.
29 */
30
31/*******************************************************************************
32* Header Files *
33*******************************************************************************/
34#define LOG_GROUP LOG_GROUP_SUP_DRV
35/* Deal with conflicts first. */
36#include <sys/param.h>
37#undef PVM
38#include <sys/types.h>
39#include <sys/module.h>
40#include <sys/systm.h>
41#include <sys/errno.h>
42#include <sys/kernel.h>
43#include <sys/fcntl.h>
44#include <sys/conf.h>
45#include <sys/uio.h>
46
47#include "../SUPDrvInternal.h"
48#include <VBox/version.h>
49#include <iprt/initterm.h>
50#include <iprt/string.h>
51#include <iprt/spinlock.h>
52#include <iprt/process.h>
53#include <iprt/assert.h>
54#include <iprt/uuid.h>
55#include <VBox/log.h>
56#include <iprt/alloc.h>
57#include <iprt/err.h>
58#include <iprt/asm.h>
59
60#ifdef VBOX_WITH_HARDENING
61# define VBOXDRV_PERM 0600
62#else
63# define VBOXDRV_PERM 0666
64#endif
65
66/*******************************************************************************
67* Internal Functions *
68*******************************************************************************/
69static int VBoxDrvFreeBSDModuleEvent(struct module *pMod, int enmEventType, void *pvArg);
70static int VBoxDrvFreeBSDLoad(void);
71static int VBoxDrvFreeBSDUnload(void);
72
73static d_open_t VBoxDrvFreeBSDOpenUsr;
74static d_open_t VBoxDrvFreeBSDOpenSys;
75static void VBoxDrvFreeBSDDtr(void *pData);
76static d_ioctl_t VBoxDrvFreeBSDIOCtl;
77static int VBoxDrvFreeBSDIOCtlSlow(PSUPDRVSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd);
78
79
80/*******************************************************************************
81* Global Variables *
82*******************************************************************************/
83/**
84 * Module info structure used by the kernel.
85 */
86static moduledata_t g_VBoxDrvFreeBSDModule =
87{
88 "vboxdrv",
89 VBoxDrvFreeBSDModuleEvent,
90 NULL
91};
92
93/** Declare the module as a pseudo device. */
94DECLARE_MODULE(vboxdrv, g_VBoxDrvFreeBSDModule, SI_SUB_PSEUDO, SI_ORDER_ANY);
95MODULE_VERSION(vboxdrv, 1);
96
97/**
98 * The /dev/vboxdrv character device entry points.
99 */
100static struct cdevsw g_VBoxDrvFreeBSDChrDevSwSys =
101{
102 .d_version = D_VERSION,
103 .d_open = VBoxDrvFreeBSDOpenSys,
104 .d_ioctl = VBoxDrvFreeBSDIOCtl,
105 .d_name = "vboxdrv"
106};
107/** The /dev/vboxdrv character device. */
108static struct cdev *g_pVBoxDrvFreeBSDChrDevSys;
109
110/**
111 * The /dev/vboxdrvu character device entry points.
112 */
113static struct cdevsw g_VBoxDrvFreeBSDChrDevSwUsr =
114{
115 .d_version = D_VERSION,
116 .d_open = VBoxDrvFreeBSDOpenUsr,
117 .d_ioctl = VBoxDrvFreeBSDIOCtl,
118 .d_name = "vboxdrvu"
119};
120/** The /dev/vboxdrvu character device. */
121static struct cdev *g_pVBoxDrvFreeBSDChrDevUsr;
122
123/** Reference counter. */
124static volatile uint32_t g_cUsers;
125
126/** The device extention. */
127static SUPDRVDEVEXT g_VBoxDrvFreeBSDDevExt;
128
129/**
130 * Module event handler.
131 *
132 * @param pMod The module structure.
133 * @param enmEventType The event type (modeventtype_t).
134 * @param pvArg Module argument. NULL.
135 *
136 * @return 0 on success, errno.h status code on failure.
137 */
138static int VBoxDrvFreeBSDModuleEvent(struct module *pMod, int enmEventType, void *pvArg)
139{
140 int rc;
141 switch (enmEventType)
142 {
143 case MOD_LOAD:
144 rc = VBoxDrvFreeBSDLoad();
145 break;
146
147 case MOD_UNLOAD:
148 mtx_unlock(&Giant);
149 rc = VBoxDrvFreeBSDUnload();
150 mtx_lock(&Giant);
151 break;
152
153 case MOD_SHUTDOWN:
154 case MOD_QUIESCE:
155 default:
156 return EOPNOTSUPP;
157 }
158
159 if (RT_SUCCESS(rc))
160 return 0;
161 return RTErrConvertToErrno(rc);
162}
163
164
165static int VBoxDrvFreeBSDLoad(void)
166{
167 g_cUsers = 0;
168
169 /*
170 * Initialize the runtime.
171 */
172 int rc = RTR0Init(0);
173 if (RT_SUCCESS(rc))
174 {
175 Log(("VBoxDrvFreeBSDLoad:\n"));
176
177 /*
178 * Initialize the device extension.
179 */
180 rc = supdrvInitDevExt(&g_VBoxDrvFreeBSDDevExt, sizeof(SUPDRVSESSION));
181 if (RT_SUCCESS(rc))
182 {
183 /*
184 * Configure character devices. Add symbolic links for compatibility.
185 */
186 g_pVBoxDrvFreeBSDChrDevSys = make_dev(&g_VBoxDrvFreeBSDChrDevSwSys, 0, UID_ROOT, GID_WHEEL, VBOXDRV_PERM, "vboxdrv");
187 g_pVBoxDrvFreeBSDChrDevUsr = make_dev(&g_VBoxDrvFreeBSDChrDevSwUsr, 1, UID_ROOT, GID_WHEEL, 0666, "vboxdrvu");
188 return VINF_SUCCESS;
189 }
190
191 printf("vboxdrv: supdrvInitDevExt failed, rc=%d\n", rc);
192 RTR0Term();
193 }
194 else
195 printf("vboxdrv: RTR0Init failed, rc=%d\n", rc);
196 return rc;
197}
198
199static int VBoxDrvFreeBSDUnload(void)
200{
201 Log(("VBoxDrvFreeBSDUnload:\n"));
202
203 if (g_cUsers > 0)
204 return VERR_RESOURCE_BUSY;
205
206 /*
207 * Reserve what we did in VBoxDrvFreeBSDInit.
208 */
209 destroy_dev(g_pVBoxDrvFreeBSDChrDevUsr);
210 destroy_dev(g_pVBoxDrvFreeBSDChrDevSys);
211
212 supdrvDeleteDevExt(&g_VBoxDrvFreeBSDDevExt);
213
214 RTR0TermForced();
215
216 memset(&g_VBoxDrvFreeBSDDevExt, 0, sizeof(g_VBoxDrvFreeBSDDevExt));
217 return VINF_SUCCESS;
218}
219
220
221/**
222 *
223 * @returns 0 on success, errno on failure.
224 * EBUSY if the device is used by someone else.
225 * @param pDev The device node.
226 * @param fOpen The open flags.
227 * @param pTd The thread.
228 * @param iDevType ???
229 */
230static int vboxdrvFreeBSDOpenCommon(struct cdev *pDev, int fOpen, int iDevtype, struct thread *pTd, bool fUnrestricted)
231{
232 PSUPDRVSESSION pSession;
233 int rc;
234
235 /*
236 * Let's be a bit picky about the flags...
237 */
238 if (fOpen != (FREAD | FWRITE /*=O_RDWR*/))
239 {
240 Log(("VBoxDrvFreeBSDOpen: fOpen=%#x expected %#x\n", fOpen, O_RDWR));
241 return EINVAL;
242 }
243
244 /*
245 * Create a new session.
246 */
247 rc = supdrvCreateSession(&g_VBoxDrvFreeBSDDevExt, true /* fUser */, fUnrestricted, &pSession);
248 if (RT_SUCCESS(rc))
249 {
250 /** @todo get (r)uid and (r)gid.
251 pSession->Uid = stuff;
252 pSession->Gid = stuff; */
253 devfs_set_cdevpriv(pSession, VBoxDrvFreeBSDDtr);
254 Log(("VBoxDrvFreeBSDOpen: pSession=%p\n", pSession));
255 ASMAtomicIncU32(&g_cUsers);
256 return 0;
257 }
258
259 return RTErrConvertToErrno(rc);
260}
261
262
263/** For vboxdrv. */
264static int VBoxDrvFreeBSDOpenSys(struct cdev *pDev, int fOpen, int iDevtype, struct thread *pTd)
265{
266 return vboxdrvFreeBSDOpenCommon(pDev, fOpen, iDevtype, pTd, true);
267}
268
269
270/** For vboxdrvu. */
271static int VBoxDrvFreeBSDOpenUsr(struct cdev *pDev, int fOpen, int iDevtype, struct thread *pTd)
272{
273 return vboxdrvFreeBSDOpenCommon(pDev, fOpen, iDevtype, pTd, false);
274}
275
276
277/**
278 * Close a file device previously opened by VBoxDrvFreeBSDOpen
279 *
280 * @returns 0 on success.
281 * @param pDev The device.
282 * @param fFile The file descriptor flags.
283 * @param DevType The device type (CHR.
284 * @param pTd The calling thread.
285 */
286static void VBoxDrvFreeBSDDtr(void *pData)
287{
288 PSUPDRVSESSION pSession = pData;
289 Log(("VBoxDrvFreeBSDDtr: pSession=%p\n", pSession));
290
291 /*
292 * Close the session.
293 */
294 supdrvSessionRelease(pSession);
295 ASMAtomicDecU32(&g_cUsers);
296}
297
298
299/**
300 * I/O control request.
301 *
302 * @returns depends...
303 * @param pDev The device.
304 * @param ulCmd The command.
305 * @param pvData Pointer to the data.
306 * @param fFile The file descriptor flags.
307 * @param pTd The calling thread.
308 */
309static int VBoxDrvFreeBSDIOCtl(struct cdev *pDev, u_long ulCmd, caddr_t pvData, int fFile, struct thread *pTd)
310{
311 PSUPDRVSESSION pSession;
312 devfs_get_cdevpriv((void **)&pSession);
313
314 /*
315 * Deal with the fast ioctl path first.
316 */
317 if ( ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN
318 || ulCmd == SUP_IOCTL_FAST_DO_HM_RUN
319 || ulCmd == SUP_IOCTL_FAST_DO_NOP)
320 && pSession->fUnrestricted == true)
321 return supdrvIOCtlFast(ulCmd, *(uint32_t *)pvData, &g_VBoxDrvFreeBSDDevExt, pSession);
322
323 return VBoxDrvFreeBSDIOCtlSlow(pSession, ulCmd, pvData, pTd);
324}
325
326
327/**
328 * Deal with the 'slow' I/O control requests.
329 *
330 * @returns 0 on success, appropriate errno on failure.
331 * @param pSession The session.
332 * @param ulCmd The command.
333 * @param pvData The request data.
334 * @param pTd The calling thread.
335 */
336static int VBoxDrvFreeBSDIOCtlSlow(PSUPDRVSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd)
337{
338 PSUPREQHDR pHdr;
339 uint32_t cbReq = IOCPARM_LEN(ulCmd);
340 void *pvUser = NULL;
341
342 /*
343 * Buffered request?
344 */
345 if ((IOC_DIRMASK & ulCmd) == IOC_INOUT)
346 {
347 pHdr = (PSUPREQHDR)pvData;
348 if (RT_UNLIKELY(cbReq < sizeof(*pHdr)))
349 {
350 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: cbReq=%#x < %#x; ulCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), ulCmd));
351 return EINVAL;
352 }
353 if (RT_UNLIKELY((pHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC))
354 {
355 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: bad magic fFlags=%#x; ulCmd=%#lx\n", pHdr->fFlags, ulCmd));
356 return EINVAL;
357 }
358 if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq
359 || pHdr->cbIn < sizeof(*pHdr)
360 || pHdr->cbOut < sizeof(*pHdr)))
361 {
362 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: max(%#x,%#x) != %#x; ulCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, ulCmd));
363 return EINVAL;
364 }
365 }
366 /*
367 * Big unbuffered request?
368 */
369 else if ((IOC_DIRMASK & ulCmd) == IOC_VOID && !cbReq)
370 {
371 /*
372 * Read the header, validate it and figure out how much that needs to be buffered.
373 */
374 SUPREQHDR Hdr;
375 pvUser = *(void **)pvData;
376 int rc = copyin(pvUser, &Hdr, sizeof(Hdr));
377 if (RT_UNLIKELY(rc))
378 {
379 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyin(%p,Hdr,) -> %#x; ulCmd=%#lx\n", pvUser, rc, ulCmd));
380 return rc;
381 }
382 if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC))
383 {
384 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: bad magic fFlags=%#x; ulCmd=%#lx\n", Hdr.fFlags, ulCmd));
385 return EINVAL;
386 }
387 cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut);
388 if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr)
389 || Hdr.cbOut < sizeof(Hdr)
390 || cbReq > _1M*16))
391 {
392 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: max(%#x,%#x); ulCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, ulCmd));
393 return EINVAL;
394 }
395
396 /*
397 * Allocate buffer and copy in the data.
398 */
399 pHdr = (PSUPREQHDR)RTMemTmpAlloc(cbReq);
400 if (RT_UNLIKELY(!pHdr))
401 {
402 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: failed to allocate buffer of %d bytes; ulCmd=%#lx\n", cbReq, ulCmd));
403 return ENOMEM;
404 }
405 rc = copyin(pvUser, pHdr, Hdr.cbIn);
406 if (RT_UNLIKELY(rc))
407 {
408 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyin(%p,%p,%#x) -> %#x; ulCmd=%#lx\n",
409 pvUser, pHdr, Hdr.cbIn, rc, ulCmd));
410 RTMemTmpFree(pHdr);
411 return rc;
412 }
413 }
414 else
415 {
416 Log(("VBoxDrvFreeBSDIOCtlSlow: huh? cbReq=%#x ulCmd=%#lx\n", cbReq, ulCmd));
417 return EINVAL;
418 }
419
420 /*
421 * Process the IOCtl.
422 */
423 int rc = supdrvIOCtl(ulCmd, &g_VBoxDrvFreeBSDDevExt, pSession, pHdr);
424 if (RT_LIKELY(!rc))
425 {
426 /*
427 * If unbuffered, copy back the result before returning.
428 */
429 if (pvUser)
430 {
431 uint32_t cbOut = pHdr->cbOut;
432 if (cbOut > cbReq)
433 {
434 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, ulCmd));
435 cbOut = cbReq;
436 }
437 rc = copyout(pHdr, pvUser, cbOut);
438 if (RT_UNLIKELY(rc))
439 OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyout(%p,%p,%#x) -> %d; uCmd=%#lx!\n", pHdr, pvUser, cbOut, rc, ulCmd));
440
441 Log(("VBoxDrvFreeBSDIOCtlSlow: returns %d / %d ulCmd=%lx\n", 0, pHdr->rc, ulCmd));
442
443 /* cleanup */
444 RTMemTmpFree(pHdr);
445 }
446 }
447 else
448 {
449 /*
450 * The request failed, just clean up.
451 */
452 if (pvUser)
453 RTMemTmpFree(pHdr);
454
455 Log(("VBoxDrvFreeBSDIOCtlSlow: ulCmd=%lx pData=%p failed, rc=%d\n", ulCmd, pvData, rc));
456 rc = EINVAL;
457 }
458
459 return rc;
460}
461
462
463/**
464 * The SUPDRV IDC entry point.
465 *
466 * @returns VBox status code, see supdrvIDC.
467 * @param iReq The request code.
468 * @param pReq The request.
469 */
470int VBOXCALL SUPDrvFreeBSDIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq)
471{
472 PSUPDRVSESSION pSession;
473
474 /*
475 * Some quick validations.
476 */
477 if (RT_UNLIKELY(!VALID_PTR(pReq)))
478 return VERR_INVALID_POINTER;
479
480 pSession = pReq->pSession;
481 if (pSession)
482 {
483 if (RT_UNLIKELY(!VALID_PTR(pReq->pSession)))
484 return VERR_INVALID_PARAMETER;
485 if (RT_UNLIKELY(pSession->pDevExt != &g_VBoxDrvFreeBSDDevExt))
486 return VERR_INVALID_PARAMETER;
487 }
488 else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT))
489 return VERR_INVALID_PARAMETER;
490
491 /*
492 * Do the job.
493 */
494 return supdrvIDC(uReq, &g_VBoxDrvFreeBSDDevExt, pSession, pReq);
495}
496
497
498void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession)
499{
500 NOREF(pDevExt);
501 NOREF(pSession);
502}
503
504
505void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser)
506{
507 NOREF(pDevExt); NOREF(pSession); NOREF(pvUser);
508}
509
510
511void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser)
512{
513 NOREF(pDevExt); NOREF(pSession); NOREF(pvUser);
514}
515
516
517void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession)
518{
519 NOREF(pObj);
520 NOREF(pSession);
521}
522
523
524bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc)
525{
526 NOREF(pObj);
527 NOREF(pSession);
528 NOREF(pszObjName);
529 NOREF(prc);
530 return false;
531}
532
533
534bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt)
535{
536 return false;
537}
538
539
540int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename)
541{
542 NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename);
543 return VERR_NOT_SUPPORTED;
544}
545
546
547void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage)
548{
549 NOREF(pDevExt); NOREF(pImage);
550}
551
552
553int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, const uint8_t *pbImageBits)
554{
555 NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits);
556 return VERR_NOT_SUPPORTED;
557}
558
559
560int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq)
561{
562 NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq);
563 return VERR_NOT_SUPPORTED;
564}
565
566
567void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage)
568{
569 NOREF(pDevExt); NOREF(pImage);
570}
571
572
573#ifdef SUPDRV_WITH_MSR_PROBER
574
575int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue)
576{
577 NOREF(uMsr); NOREF(idCpu); NOREF(puValue);
578 return VERR_NOT_SUPPORTED;
579}
580
581
582int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue)
583{
584 NOREF(uMsr); NOREF(idCpu); NOREF(uValue);
585 return VERR_NOT_SUPPORTED;
586}
587
588
589int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq)
590{
591 NOREF(idCpu); NOREF(pReq);
592 return VERR_NOT_SUPPORTED;
593}
594
595#endif /* SUPDRV_WITH_MSR_PROBER */
596
597
598SUPR0DECL(int) SUPR0Printf(const char *pszFormat, ...)
599{
600 va_list va;
601 char szMsg[256];
602 int cch;
603
604 va_start(va, pszFormat);
605 cch = RTStrPrintfV(szMsg, sizeof(szMsg), pszFormat, va);
606 va_end(va);
607
608 printf("%s", szMsg);
609
610 return cch;
611}
612
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette