VirtualBox

source: vbox/trunk/src/VBox/Runtime/generic/semsrw-generic.cpp@ 13040

Last change on this file since 13040 was 12892, checked in by vboxsync, 16 years ago

AssertMsgRC

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 19.4 KB
Line 
1/* $Id: semsrw-generic.cpp 12892 2008-10-02 07:22:55Z vboxsync $ */
2/** @file
3 * IPRT - Read-Write Semaphore, Generic.
4 *
5 * This is a generic implementation for OSes which don't have
6 * native RW semaphores.
7 */
8
9/*
10 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
11 *
12 * This file is part of VirtualBox Open Source Edition (OSE), as
13 * available from http://www.virtualbox.org. This file is free software;
14 * you can redistribute it and/or modify it under the terms of the GNU
15 * General Public License (GPL) as published by the Free Software
16 * Foundation, in version 2 as it comes in the "COPYING" file of the
17 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19 *
20 * The contents of this file may alternatively be used under the terms
21 * of the Common Development and Distribution License Version 1.0
22 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
23 * VirtualBox OSE distribution, in which case the provisions of the
24 * CDDL are applicable instead of those of the GPL.
25 *
26 * You may elect to license modified versions of this file under the
27 * terms and conditions of either the GPL or the CDDL or both.
28 *
29 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
30 * Clara, CA 95054 USA or visit http://www.sun.com if you need
31 * additional information or have any questions.
32 */
33
34/** @todo fix generic RW sems. (reimplement) */
35#define USE_CRIT_SECT
36
37
38/*******************************************************************************
39* Header Files *
40*******************************************************************************/
41#include <iprt/semaphore.h>
42#include <iprt/alloc.h>
43#include <iprt/time.h>
44#include <iprt/asm.h>
45#include <iprt/assert.h>
46#include <iprt/thread.h>
47#include <iprt/err.h>
48#ifdef USE_CRIT_SECT
49#include <iprt/critsect.h>
50#endif
51
52
53
54/*******************************************************************************
55* Structures and Typedefs *
56*******************************************************************************/
57/** Internal representation of a Read-Write semaphore for the
58 * Generic implementation. */
59struct RTSEMRWINTERNAL
60{
61#ifdef USE_CRIT_SECT
62 /** Critical section. */
63 RTCRITSECT CritSect;
64#else
65 /** Magic (RTSEMRW_MAGIC). */
66 uint32_t u32Magic;
67 /** This critical section serializes the access to and updating of the structure members. */
68 RTCRITSECT CritSect;
69 /** The current number of readers. */
70 uint32_t cReaders;
71 /** The number of readers waiting. */
72 uint32_t cReadersWaiting;
73 /** The current number of waiting writers. */
74 uint32_t cWritersWaiting;
75 /** The handle of the event object on which the waiting readers block. (manual reset). */
76 RTSEMEVENTMULTI EventReaders;
77 /** The handle of the event object on which the waiting writers block. (manual reset). */
78 RTSEMEVENTMULTI EventWriters;
79 /** The current state of the read-write lock. */
80 KPRF_TYPE(,RWLOCKSTATE) enmState;
81
82#endif
83};
84
85
86/**
87 * Validate a read-write semaphore handle passed to one of the interface.
88 *
89 * @returns true if valid.
90 * @returns false if invalid.
91 * @param pIntRWSem Pointer to the read-write semaphore to validate.
92 */
93inline bool rtsemRWValid(struct RTSEMRWINTERNAL *pIntRWSem)
94{
95 if (!VALID_PTR(pIntRWSem))
96 return false;
97
98#ifdef USE_CRIT_SECT
99 if (pIntRWSem->CritSect.u32Magic != RTCRITSECT_MAGIC)
100 return false;
101#else
102 if (pIntRWSem->u32Check != (uint32_t)~0)
103 return false;
104#endif
105 return true;
106}
107
108
109RTDECL(int) RTSemRWCreate(PRTSEMRW pRWSem)
110{
111 int rc;
112
113 /*
114 * Allocate memory.
115 */
116 struct RTSEMRWINTERNAL *pIntRWSem = (struct RTSEMRWINTERNAL *)RTMemAlloc(sizeof(struct RTSEMRWINTERNAL));
117 if (pIntRWSem)
118 {
119#ifdef USE_CRIT_SECT
120 rc = RTCritSectInit(&pIntRWSem->CritSect);
121 if (RT_SUCCESS(rc))
122 {
123 *pRWSem = pIntRWSem;
124 return VINF_SUCCESS;
125 }
126#else
127 /*
128 * Create the semaphores.
129 */
130 rc = RTSemEventCreate(&pIntRWSem->WriteEvent);
131 if (RT_SUCCESS(rc))
132 {
133 rc = RTSemEventMultiCreate(&pIntRWSem->ReadEvent);
134 if (RT_SUCCESS(rc))
135 {
136 rc = RTSemMutexCreate(&pIntRWSem->Mutex);
137 if (RT_SUCCESS(rc))
138 {
139 /*
140 * Signal the read semaphore and initialize other variables.
141 */
142 rc = RTSemEventMultiSignal(pIntRWSem->ReadEvent);
143 if (RT_SUCCESS(rc))
144 {
145 pIntRWSem->cReaders = 0;
146 pIntRWSem->cWriters = 0;
147 pIntRWSem->WROwner = NIL_RTTHREAD;
148 pIntRWSem->u32Check = ~0;
149 *pRWSem = pIntRWSem;
150 return VINF_SUCCESS;
151 }
152 RTSemMutexDestroy(pIntRWSem->Mutex);
153 }
154 RTSemEventMultiDestroy(pIntRWSem->ReadEvent);
155 }
156 RTSemEventDestroy(pIntRWSem->WriteEvent);
157 }
158#endif
159 RTMemFree(pIntRWSem);
160 }
161 else
162 rc = VERR_NO_MEMORY;
163
164 return rc;
165}
166
167
168RTDECL(int) RTSemRWDestroy(RTSEMRW RWSem)
169{
170 /*
171 * Validate handle.
172 */
173 if (!rtsemRWValid(RWSem))
174 {
175 AssertMsgFailed(("Invalid handle %p!\n", RWSem));
176 return VERR_INVALID_HANDLE;
177 }
178
179#ifdef USE_CRIT_SECT
180 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
181 int rc = RTCritSectDelete(&pIntRWSem->CritSect);
182 if (RT_SUCCESS(rc))
183 RTMemFree(pIntRWSem);
184 return rc;
185#else
186
187 /*
188 * Check if busy.
189 */
190 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
191 int rc = RTSemMutexRequest(pIntRWSem->Mutex, 32);
192 if (RT_SUCCESS(rc))
193 {
194 if (!pIntRWSem->cReaders && !pIntRWSem->cWriters)
195 {
196 /*
197 * Make it invalid and unusable.
198 */
199 ASMAtomicXchgU32(&pIntRWSem->u32Check, 0);
200 ASMAtomicXchgU32(&pIntRWSem->cReaders, ~0);
201
202 /*
203 * Do actual cleanup.
204 * None of these can now fail excep for the mutex which
205 * can be a little bit busy.
206 */
207 rc = RTSemEventMultiDestroy(pIntRWSem->ReadEvent);
208 AssertMsgRC(rc, ("RTSemEventMultiDestroy failed! rc=%d\n", rc));
209 pIntRWSem->ReadEvent = NIL_RTSEMEVENTMULTI;
210
211 rc = RTSemEventDestroy(pIntRWSem->WriteEvent);
212 AssertMsgRC(rc, ("RTSemEventDestroy failed! rc=%d\n", rc));
213 pIntRWSem->WriteEvent = NIL_RTSEMEVENT;
214
215 RTSemMutexRelease(pIntRWSem->Mutex);
216 for (unsigned i = 32; i > 0; i--)
217 {
218 rc = RTSemMutexDestroy(pIntRWSem->Mutex);
219 if (RT_SUCCESS(rc))
220 break;
221 RTThreadSleep(1);
222 }
223 AssertMsgRC(rc, ("RTSemMutexDestroy failed! rc=%d\n", rc));
224 pIntRWSem->Mutex = (RTSEMMUTEX)0;
225
226 RTMemFree(pIntRWSem);
227 rc = VINF_SUCCESS;
228 }
229 else
230 {
231 rc = VERR_SEM_BUSY;
232 RTSemMutexRelease(pIntRWSem->Mutex);
233 }
234 }
235 else
236 rc = rc == VERR_TIMEOUT ? VERR_SEM_BUSY : rc;
237
238 return VINF_SUCCESS;
239#endif
240}
241
242
243RTDECL(int) RTSemRWRequestRead(RTSEMRW RWSem, unsigned cMillies)
244{
245 /*
246 * Validate handle.
247 */
248 if (!rtsemRWValid(RWSem))
249 {
250 AssertMsgFailed(("Invalid handle %p!\n", RWSem));
251 return VERR_INVALID_HANDLE;
252 }
253
254#ifdef USE_CRIT_SECT
255 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
256 return RTCritSectEnter(&pIntRWSem->CritSect);
257#else
258
259 /*
260 * Take mutex and check if already reader.
261 */
262 //RTTHREAD Self = RTThreadSelf();
263 RTTHREAD Self = (RTTHREAD)RTThreadNativeSelf();
264 unsigned cMilliesInitial = cMillies;
265 uint64_t tsStart = 0;
266 if (cMillies != RTSEM_INDEFINITE_WAIT)
267 tsStart = RTTimeNanoTS();
268
269 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
270 int rc = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
271 if (RT_FAILURE(rc))
272 {
273 AssertMsgFailed(("RTSemMutexRequest failed on rwsem %p, rc=%d\n", RWSem, rc));
274 return rc;
275 }
276
277 unsigned i = pIntRWSem->cReaders;
278 while (i-- > 0)
279 {
280 if (pIntRWSem->aReaders[i].Thread == Self)
281 {
282 if (pIntRWSem->aReaders[i].cNesting + 1 < (unsigned)~0)
283 pIntRWSem->aReaders[i].cNesting++;
284 else
285 {
286 AssertMsgFailed(("Too many requests for one thread!\n"));
287 rc = RTSemMutexRelease(pIntRWSem->Mutex);
288 AssertMsgRC(rc, ("RTSemMutexRelease failed rc=%d\n", rc));
289 return VERR_TOO_MANY_SEM_REQUESTS;
290 }
291 }
292 }
293
294
295 for (;;)
296 {
297 /*
298 * Check if the stat of the affairs allow read access.
299 */
300 if (pIntRWSem->u32Check == (uint32_t)~0)
301 {
302 if (pIntRWSem->cWriters == 0)
303 {
304 if (pIntRWSem->cReaders < ELEMENTS(pIntRWSem->aReaders))
305 {
306 /*
307 * Add ourselves to the list of readers and return.
308 */
309 i = pIntRWSem->cReaders;
310 pIntRWSem->aReaders[i].Thread = Self;
311 pIntRWSem->aReaders[i].cNesting = 1;
312 ASMAtomicXchgU32(&pIntRWSem->cReaders, i + 1);
313
314 RTSemMutexRelease(pIntRWSem->Mutex);
315 return VINF_SUCCESS;
316 }
317 else
318 {
319 AssertMsgFailed(("Too many readers! How come we have so many threads!?!\n"));
320 rc = VERR_TOO_MANY_SEM_REQUESTS;
321 }
322 }
323 #if 0 /* any action here shouldn't be necessary */
324 else
325 {
326 rc = RTSemEventMultiReset(pIntRWSem->ReadEvent);
327 AssertMsgRC(rc, ("RTSemEventMultiReset failed on RWSem %p, rc=%d\n", RWSem, rc));
328 }
329 #endif
330 }
331 else
332 rc = VERR_SEM_DESTROYED;
333 RTSemMutexRelease(pIntRWSem->Mutex);
334 if (RT_FAILURE(rc))
335 break;
336
337
338 /*
339 * Wait till it's ready for reading.
340 */
341 if (cMillies != RTSEM_INDEFINITE_WAIT)
342 {
343 int64_t tsDelta = RTTimeNanoTS() - tsStart;
344 if (tsDelta >= 1000000)
345 {
346 cMillies = cMilliesInitial - (unsigned)(tsDelta / 1000000);
347 if (cMillies > cMilliesInitial)
348 cMillies = cMilliesInitial ? 1 : 0;
349 }
350 }
351 rc = RTSemEventMultiWait(pIntRWSem->ReadEvent, cMillies);
352 if (RT_FAILURE(rc))
353 {
354 AssertMsg(rc == VERR_TIMEOUT, ("RTSemEventMultiWait failed on rwsem %p, rc=%d\n", RWSem, rc));
355 break;
356 }
357
358 /*
359 * Get Mutex.
360 */
361 rc = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
362 if (RT_FAILURE(rc))
363 {
364 AssertMsgFailed(("RTSemMutexRequest failed on rwsem %p, rc=%d\n", RWSem, rc));
365 break;
366 }
367 }
368
369 return rc;
370#endif
371}
372
373
374RTDECL(int) RTSemRWRequestReadNoResume(RTSEMRW RWSem, unsigned cMillies)
375{
376 return RTSemRWRequestRead(RWSem, cMillies);
377}
378
379
380RTDECL(int) RTSemRWReleaseRead(RTSEMRW RWSem)
381{
382 /*
383 * Validate handle.
384 */
385 if (!rtsemRWValid(RWSem))
386 {
387 AssertMsgFailed(("Invalid handle %p!\n", RWSem));
388 return VERR_INVALID_HANDLE;
389 }
390
391#ifdef USE_CRIT_SECT
392 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
393 return RTCritSectLeave(&pIntRWSem->CritSect);
394#else
395
396 /*
397 * Take Mutex.
398 */
399 //RTTHREAD Self = RTThreadSelf();
400 RTTHREAD Self = (RTTHREAD)RTThreadNativeSelf();
401 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
402 int rc = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
403 if (RT_SUCCESS(rc))
404 {
405 unsigned i = pIntRWSem->cReaders;
406 while (i-- > 0)
407 {
408 if (pIntRWSem->aReaders[i].Thread == Self)
409 {
410 AssertMsg(pIntRWSem->WROwner == NIL_RTTHREAD, ("Impossible! Writers and Readers are exclusive!\n"));
411
412 if (pIntRWSem->aReaders[i].cNesting <= 1)
413 {
414 pIntRWSem->aReaders[i] = pIntRWSem->aReaders[pIntRWSem->cReaders - 1];
415 ASMAtomicXchgU32(&pIntRWSem->cReaders, pIntRWSem->cReaders - 1);
416
417 /* Kick off writers? */
418 if ( pIntRWSem->cWriters > 0
419 && pIntRWSem->cReaders == 0)
420 {
421 rc = RTSemEventSignal(pIntRWSem->WriteEvent);
422 AssertMsgRC(rc, ("Failed to signal writers on rwsem %p, rc=%d\n", RWSem, rc));
423 }
424 }
425 else
426 pIntRWSem->aReaders[i].cNesting--;
427
428 RTSemMutexRelease(pIntRWSem->Mutex);
429 return VINF_SUCCESS;
430 }
431 }
432
433 RTSemMutexRelease(pIntRWSem->Mutex);
434 rc = VERR_NOT_OWNER;
435 AssertMsgFailed(("Not reader of rwsem %p\n", RWSem));
436 }
437 else
438 AssertMsgFailed(("RTSemMutexRequest failed on rwsem %p, rc=%d\n", RWSem, rc));
439
440 return rc;
441#endif
442}
443
444
445
446RTDECL(int) RTSemRWRequestWrite(RTSEMRW RWSem, unsigned cMillies)
447{
448 /*
449 * Validate handle.
450 */
451 if (!rtsemRWValid(RWSem))
452 {
453 AssertMsgFailed(("Invalid handle %p!\n", RWSem));
454 return VERR_INVALID_HANDLE;
455 }
456
457#ifdef USE_CRIT_SECT
458 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
459 return RTCritSectEnter(&pIntRWSem->CritSect);
460#else
461
462 /*
463 * Get Mutex.
464 */
465 //RTTHREAD Self = RTThreadSelf();
466 RTTHREAD Self = (RTTHREAD)RTThreadNativeSelf();
467 unsigned cMilliesInitial = cMillies;
468 uint64_t tsStart = 0;
469 if (cMillies != RTSEM_INDEFINITE_WAIT)
470 tsStart = RTTimeNanoTS();
471
472 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
473 int rc = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
474 if (RT_FAILURE(rc))
475 {
476 AssertMsgFailed(("RTSemMutexWait failed on rwsem %p, rc=%d\n", RWSem, rc));
477 return rc;
478 }
479
480 /*
481 * Check that we're not a reader.
482 */
483 unsigned i = pIntRWSem->cReaders;
484 while (i-- > 0)
485 {
486 if (pIntRWSem->aReaders[i].Thread == Self)
487 {
488 AssertMsgFailed(("Deadlock - requested write access while being a reader! rwsem %p.\n", RWSem));
489 RTSemMutexRelease(pIntRWSem->Mutex);
490 return VERR_DEADLOCK;
491 }
492 }
493
494
495 /*
496 * Reset the reader event semaphore and increment number of readers.
497 */
498 rc = RTSemEventMultiReset(pIntRWSem->ReadEvent);
499 if (RT_FAILURE(rc))
500 {
501 AssertMsgFailed(("Failed to reset readers, rwsem %p, rc=%d.\n", RWSem, rc));
502 RTSemMutexRelease(pIntRWSem->Mutex);
503 return rc;
504 }
505 ASMAtomicXchgU32(&pIntRWSem->cWriters, pIntRWSem->cWriters + 1);
506
507 /*
508 * Wait while there are other threads owning this sem.
509 */
510 while ( pIntRWSem->WROwner != NIL_RTTHREAD
511 || pIntRWSem->cReaders > 0)
512 {
513 AssertMsg(pIntRWSem->WROwner == NIL_RTTHREAD || pIntRWSem->cWriters > 1,
514 ("The lock is write owned by there is only one waiter...\n"));
515
516 /*
517 * Release the mutex and wait on the writer semaphore.
518 */
519 rc = RTSemMutexRelease(pIntRWSem->Mutex);
520 if (RT_FAILURE(rc))
521 {
522 AssertMsgFailed(("RTSemMutexRelease failed on rwsem %p, rc=%d\n", RWSem, rc));
523 return VERR_SEM_DESTROYED;
524 }
525
526 /*
527 * Wait.
528 */
529 if (cMillies != RTSEM_INDEFINITE_WAIT)
530 {
531 int64_t tsDelta = RTTimeNanoTS() - tsStart;
532 if (tsDelta >= 1000000)
533 {
534 cMillies = cMilliesInitial - (unsigned)(tsDelta / 1000000);
535 if (cMillies > cMilliesInitial)
536 cMillies = cMilliesInitial ? 1 : 0;
537 }
538 }
539 rc = RTSemEventWait(pIntRWSem->WriteEvent, cMillies);
540
541 /*
542 * Check that the semaphore wasn't destroyed while we waited.
543 */
544 if ( rc == VERR_SEM_DESTROYED
545 || pIntRWSem->u32Check != (uint32_t)~0)
546 return VERR_SEM_DESTROYED;
547
548 /*
549 * Attempt take the mutex.
550 */
551 int rc2 = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
552 if (RT_FAILURE(rc) || RT_FAILURE(rc2))
553 {
554 AssertMsgRC(rc2, ("RTSemMutexRequest failed on rwsem %p, rc=%d\n", RWSem, rc2));
555 if (RT_SUCCESS(rc))
556 rc = rc2;
557 else
558 AssertMsg(rc == VERR_TIMEOUT, ("RTSemEventWait failed on rwsem %p, rc=%d\n", RWSem, rc));
559
560 /*
561 * Remove our selves from the writers queue.
562 */
563 /** @todo write an atomic dec function! (it's too late for that kind of stuff tonight) */
564 if (pIntRWSem->cWriters > 0)
565 ASMAtomicXchgU32(&pIntRWSem->cWriters, pIntRWSem->cWriters - 1);
566 if (!pIntRWSem->cWriters)
567 RTSemEventMultiSignal(pIntRWSem->ReadEvent);
568 if (RT_SUCCESS(rc2))
569 RTSemMutexRelease(pIntRWSem->Mutex);
570 return rc;
571 }
572
573 AssertMsg(pIntRWSem->WROwner == NIL_RTTHREAD, ("We woke up an there is owner! %#x\n", pIntRWSem->WROwner));
574 AssertMsg(!pIntRWSem->cReaders, ("We woke up an there are readers around!\n"));
575 }
576
577 /*
578 * If we get here we own the mutex and we are ready to take
579 * the read-write ownership.
580 */
581 ASMAtomicXchgPtr((void * volatile *)&pIntRWSem->WROwner, (void *)Self);
582 rc = RTSemMutexRelease(pIntRWSem->Mutex);
583 AssertMsgRC(rc, ("RTSemMutexRelease failed. rc=%d\n", rc));
584
585 return VINF_SUCCESS;
586#endif
587}
588
589
590RTDECL(int) RTSemRWRequestWriteNoResume(RTSEMRW RWSem, unsigned cMillies)
591{
592 return RTSemRWRequestWrite(RWSem, cMillies);
593}
594
595
596
597RTDECL(int) RTSemRWReleaseWrite(RTSEMRW RWSem)
598{
599 /*
600 * Validate handle.
601 */
602 if (!rtsemRWValid(RWSem))
603 {
604 AssertMsgFailed(("Invalid handle %p!\n", RWSem));
605 return VERR_INVALID_HANDLE;
606 }
607
608#ifdef USE_CRIT_SECT
609 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
610 return RTCritSectLeave(&pIntRWSem->CritSect);
611#else
612
613 /*
614 * Check if owner.
615 */
616 //RTTHREAD Self = RTThreadSelf();
617 RTTHREAD Self = (RTTHREAD)RTThreadNativeSelf();
618 struct RTSEMRWINTERNAL *pIntRWSem = RWSem;
619 if (pIntRWSem->WROwner != Self)
620 {
621 AssertMsgFailed(("Not read-write owner of rwsem %p.\n", RWSem));
622 return VERR_NOT_OWNER;
623 }
624
625 /*
626 * Request the mutex.
627 */
628 int rc = RTSemMutexRequest(pIntRWSem->Mutex, RTSEM_INDEFINITE_WAIT);
629 if (RT_FAILURE(rc))
630 {
631 AssertMsgFailed(("RTSemMutexWait failed on rwsem %p, rc=%d\n", RWSem, rc));
632 return rc;
633 }
634
635 /*
636 * Release ownership and remove ourselves from the writers count.
637 */
638 ASMAtomicXchgPtr((void * volatile *)&pIntRWSem->WROwner, (void *)NIL_RTTHREAD);
639 Assert(pIntRWSem->cWriters > 0);
640 ASMAtomicXchgU32(&pIntRWSem->cWriters, pIntRWSem->cWriters - 1);
641
642 /*
643 * Release the readers if no more writers.
644 */
645 if (!pIntRWSem->cWriters)
646 {
647 rc = RTSemEventMultiSignal(pIntRWSem->ReadEvent);
648 AssertMsgRC(rc, ("RTSemEventMultiSignal failed for rwsem %p, rc=%d.\n", RWSem, rc));
649 }
650 rc = RTSemMutexRelease(pIntRWSem->Mutex);
651 AssertMsgRC(rc, ("RTSemEventMultiSignal failed for rwsem %p, rc=%d.\n", RWSem, rc));
652
653 return VINF_SUCCESS;
654#endif
655}
656
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