VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp@ 102797

Last change on this file since 102797 was 102797, checked in by vboxsync, 11 months ago

Devices/DrvIntNet,NetworkServices/{VBoxIntNetSwitch,IntNetIf}: Plug a
few more memory leaks in VBoxIntNetSwitch which affect macOS hosts
configured to use 'Internal Networking'. ticketref:21752

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.8 KB
Line 
1/* $Id: VBoxIntNetSwitch.cpp 102797 2024-01-09 15:01:16Z vboxsync $ */
2/** @file
3 * Internal networking - Wrapper for the R0 network service.
4 *
5 * This is a bit hackish as we're mixing context here, however it is
6 * very useful when making changes to the internal networking service.
7 */
8
9/*
10 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
11 *
12 * This file is part of VirtualBox base platform packages, as
13 * available from https://www.virtualbox.org.
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation, in version 3 of the
18 * License.
19 *
20 * This program is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, see <https://www.gnu.org/licenses>.
27 *
28 * SPDX-License-Identifier: GPL-3.0-only
29 */
30
31
32/*********************************************************************************************************************************
33* Header Files *
34*********************************************************************************************************************************/
35#define IN_INTNET_TESTCASE
36#define IN_INTNET_R3
37#include "IntNetSwitchInternal.h"
38
39#include <VBox/err.h>
40#include <VBox/vmm/vmm.h>
41#include <iprt/asm.h>
42#include <iprt/critsect.h>
43#include <iprt/initterm.h>
44#include <iprt/mem.h>
45#include <iprt/message.h>
46#include <iprt/string.h>
47#include <iprt/thread.h>
48#include <iprt/semaphore.h>
49#include <iprt/time.h>
50
51#include <xpc/xpc.h>
52
53
54/*********************************************************************************************************************************
55* Defined Constants And Macros *
56*********************************************************************************************************************************/
57
58
59/*********************************************************************************************************************************
60* Structures and Typedefs *
61*********************************************************************************************************************************/
62
63/**
64 * Registered object.
65 * This takes care of reference counting and tracking data for access checks.
66 */
67typedef struct SUPDRVOBJ
68{
69 /** Pointer to the next in the global list. */
70 struct SUPDRVOBJ * volatile pNext;
71 /** Pointer to the object destructor.
72 * This may be set to NULL if the image containing the destructor get unloaded. */
73 PFNSUPDRVDESTRUCTOR pfnDestructor;
74 /** User argument 1. */
75 void *pvUser1;
76 /** User argument 2. */
77 void *pvUser2;
78 /** The total sum of all per-session usage. */
79 uint32_t volatile cUsage;
80} SUPDRVOBJ, *PSUPDRVOBJ;
81
82
83/**
84 * The per-session object usage record.
85 */
86typedef struct SUPDRVUSAGE
87{
88 /** Pointer to the next in the list. */
89 struct SUPDRVUSAGE * volatile pNext;
90 /** Pointer to the object we're recording usage for. */
91 PSUPDRVOBJ pObj;
92 /** The usage count. */
93 uint32_t volatile cUsage;
94} SUPDRVUSAGE, *PSUPDRVUSAGE;
95
96
97/**
98 * Device extension.
99 */
100typedef struct SUPDRVDEVEXT
101{
102 /** Number of references to this service. */
103 uint32_t volatile cRefs;
104 /** Critical section to serialize the initialization, usage counting and objects. */
105 RTCRITSECT CritSect;
106 /** List of registered objects. Protected by the spinlock. */
107 PSUPDRVOBJ volatile pObjs;
108} SUPDRVDEVEXT;
109typedef SUPDRVDEVEXT *PSUPDRVDEVEXT;
110
111
112/**
113 * Per session data.
114 * This is mainly for memory tracking.
115 */
116typedef struct SUPDRVSESSION
117{
118 PSUPDRVDEVEXT pDevExt;
119 /** List of generic usage records. (protected by SUPDRVDEVEXT::CritSect) */
120 PSUPDRVUSAGE volatile pUsage;
121 /** The XPC connection handle for this session. */
122 xpc_connection_t hXpcCon;
123 /** The intnet interface handle to wait on. */
124 INTNETIFHANDLE hIfWait;
125 /** Flag whether a receive wait was initiated. */
126 bool volatile fRecvWait;
127 /** Flag whether there is something to receive. */
128 bool volatile fRecvAvail;
129} SUPDRVSESSION;
130
131
132/*********************************************************************************************************************************
133* Global Variables *
134*********************************************************************************************************************************/
135static SUPDRVDEVEXT g_DevExt;
136
137
138INTNETR3DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType,
139 PFNSUPDRVDESTRUCTOR pfnDestructor, void *pvUser1, void *pvUser2)
140{
141 RT_NOREF(enmType);
142
143 PSUPDRVOBJ pObj = (PSUPDRVOBJ)RTMemAllocZ(sizeof(*pObj));
144 if (!pObj)
145 return NULL;
146 pObj->cUsage = 1;
147 pObj->pfnDestructor = pfnDestructor;
148 pObj->pvUser1 = pvUser1;
149 pObj->pvUser2 = pvUser2;
150
151 /*
152 * Insert the object and create the session usage record.
153 */
154 PSUPDRVUSAGE pUsage = (PSUPDRVUSAGE)RTMemAlloc(sizeof(*pUsage));
155 if (!pUsage)
156 {
157 RTMemFree(pObj);
158 return NULL;
159 }
160
161 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
162 RTCritSectEnter(&pDevExt->CritSect);
163
164 /* The object. */
165 pObj->pNext = pDevExt->pObjs;
166 pDevExt->pObjs = pObj;
167
168 /* The session record. */
169 pUsage->cUsage = 1;
170 pUsage->pObj = pObj;
171 pUsage->pNext = pSession->pUsage;
172 pSession->pUsage = pUsage;
173
174 RTCritSectLeave(&pDevExt->CritSect);
175 return pObj;
176}
177
178
179INTNETR3DECL(int) SUPR0ObjAddRefEx(void *pvObj, PSUPDRVSESSION pSession, bool fNoBlocking)
180{
181 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
182 PSUPDRVOBJ pObj = (PSUPDRVOBJ)pvObj;
183 int rc = VINF_SUCCESS;
184 PSUPDRVUSAGE pUsage;
185
186 RT_NOREF(fNoBlocking);
187
188 RTCritSectEnter(&pDevExt->CritSect);
189
190 /*
191 * Reference the object.
192 */
193 ASMAtomicIncU32(&pObj->cUsage);
194
195 /*
196 * Look for the session record.
197 */
198 for (pUsage = pSession->pUsage; pUsage; pUsage = pUsage->pNext)
199 {
200 if (pUsage->pObj == pObj)
201 break;
202 }
203
204 if (pUsage)
205 pUsage->cUsage++;
206 else
207 {
208 /* create a new session record. */
209 pUsage = (PSUPDRVUSAGE)RTMemAlloc(sizeof(*pUsage));
210 if (RT_LIKELY(pUsage))
211 {
212 pUsage->cUsage = 1;
213 pUsage->pObj = pObj;
214 pUsage->pNext = pSession->pUsage;
215 pSession->pUsage = pUsage;
216 }
217 else
218 {
219 ASMAtomicDecU32(&pObj->cUsage);
220 rc = VERR_TRY_AGAIN;
221 }
222 }
223
224 RTCritSectLeave(&pDevExt->CritSect);
225 return rc;
226}
227
228
229INTNETR3DECL(int) SUPR0ObjAddRef(void *pvObj, PSUPDRVSESSION pSession)
230{
231 return SUPR0ObjAddRefEx(pvObj, pSession, false);
232}
233
234
235INTNETR3DECL(int) SUPR0ObjRelease(void *pvObj, PSUPDRVSESSION pSession)
236{
237 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
238 PSUPDRVOBJ pObj = (PSUPDRVOBJ)pvObj;
239 int rc = VERR_INVALID_PARAMETER;
240 PSUPDRVUSAGE pUsage;
241 PSUPDRVUSAGE pUsagePrev;
242
243 /*
244 * Acquire the spinlock and look for the usage record.
245 */
246 RTCritSectEnter(&pDevExt->CritSect);
247
248 for (pUsagePrev = NULL, pUsage = pSession->pUsage;
249 pUsage;
250 pUsagePrev = pUsage, pUsage = pUsage->pNext)
251 {
252 if (pUsage->pObj == pObj)
253 {
254 rc = VINF_SUCCESS;
255 AssertMsg(pUsage->cUsage >= 1 && pObj->cUsage >= pUsage->cUsage, ("glob %d; sess %d\n", pObj->cUsage, pUsage->cUsage));
256 if (pUsage->cUsage > 1)
257 {
258 pObj->cUsage--;
259 pUsage->cUsage--;
260 }
261 else
262 {
263 /*
264 * Free the session record.
265 */
266 if (pUsagePrev)
267 pUsagePrev->pNext = pUsage->pNext;
268 else
269 pSession->pUsage = pUsage->pNext;
270 RTMemFree(pUsage);
271
272 /* What about the object? */
273 if (pObj->cUsage > 1)
274 pObj->cUsage--;
275 else
276 {
277 /*
278 * Object is to be destroyed, unlink it.
279 */
280 rc = VINF_OBJECT_DESTROYED;
281 if (pDevExt->pObjs == pObj)
282 pDevExt->pObjs = pObj->pNext;
283 else
284 {
285 PSUPDRVOBJ pObjPrev;
286 for (pObjPrev = pDevExt->pObjs; pObjPrev; pObjPrev = pObjPrev->pNext)
287 if (pObjPrev->pNext == pObj)
288 {
289 pObjPrev->pNext = pObj->pNext;
290 break;
291 }
292 Assert(pObjPrev);
293 }
294 }
295 }
296 break;
297 }
298 }
299
300 RTCritSectLeave(&pDevExt->CritSect);
301
302 /*
303 * Call the destructor and free the object if required.
304 */
305 if (rc == VINF_OBJECT_DESTROYED)
306 {
307 if (pObj->pfnDestructor)
308 pObj->pfnDestructor(pObj, pObj->pvUser1, pObj->pvUser2);
309 RTMemFree(pObj);
310 }
311
312 return rc;
313}
314
315
316INTNETR3DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, const char *pszObjName)
317{
318 RT_NOREF(pvObj, pSession, pszObjName);
319 return VINF_SUCCESS;
320}
321
322
323INTNETR3DECL(int) SUPR0MemAlloc(PSUPDRVSESSION pSession, uint32_t cb, PRTR0PTR ppvR0, PRTR3PTR ppvR3)
324{
325 RT_NOREF(pSession);
326
327 /*
328 * This is used to allocate and map the send/receive buffers into the callers process space, meaning
329 * we have to mmap it with the shareable attribute.
330 */
331 void *pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
332 if (pv == MAP_FAILED)
333 return VERR_NO_MEMORY;
334
335 *ppvR0 = (RTR0PTR)pv;
336 if (ppvR3)
337 *ppvR3 = pv;
338 return VINF_SUCCESS;
339}
340
341
342INTNETR3DECL(int) SUPR0MemFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr)
343{
344 RT_NOREF(pSession);
345
346 PINTNETBUF pBuf = (PINTNETBUF)uPtr; /// @todo Hack hack hack!
347 munmap((void *)uPtr, pBuf->cbBuf);
348 return VINF_SUCCESS;
349}
350
351
352/**
353 * Destroys the given internal network XPC connection session freeing all allocated resources.
354 *
355 * @returns Reference count of the device extension..
356 * @param pSession The ession to destroy.
357 */
358static uint32_t intnetR3SessionDestroy(PSUPDRVSESSION pSession)
359{
360 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
361 uint32_t cRefs = ASMAtomicDecU32(&pDevExt->cRefs);
362 xpc_transaction_end();
363 xpc_connection_set_context(pSession->hXpcCon, NULL);
364 xpc_connection_cancel(pSession->hXpcCon);
365 pSession->hXpcCon = NULL;
366
367 ASMAtomicXchgBool(&pSession->fRecvAvail, true);
368
369 if (pSession->pUsage)
370 {
371 PSUPDRVUSAGE pUsage;
372 RTCritSectEnter(&pDevExt->CritSect);
373
374 while ((pUsage = pSession->pUsage) != NULL)
375 {
376 PSUPDRVOBJ pObj = pUsage->pObj;
377 pSession->pUsage = pUsage->pNext;
378
379 AssertMsg(pUsage->cUsage >= 1 && pObj->cUsage >= pUsage->cUsage, ("glob %d; sess %d\n", pObj->cUsage, pUsage->cUsage));
380 if (pUsage->cUsage < pObj->cUsage)
381 {
382 pObj->cUsage -= pUsage->cUsage;
383 }
384 else
385 {
386 /* Destroy the object and free the record. */
387 if (pDevExt->pObjs == pObj)
388 pDevExt->pObjs = pObj->pNext;
389 else
390 {
391 PSUPDRVOBJ pObjPrev;
392 for (pObjPrev = pDevExt->pObjs; pObjPrev; pObjPrev = pObjPrev->pNext)
393 if (pObjPrev->pNext == pObj)
394 {
395 pObjPrev->pNext = pObj->pNext;
396 break;
397 }
398 Assert(pObjPrev);
399 }
400
401 RTCritSectLeave(&pDevExt->CritSect);
402
403 if (pObj->pfnDestructor)
404 pObj->pfnDestructor(pObj, pObj->pvUser1, pObj->pvUser2);
405 RTMemFree(pObj);
406
407 RTCritSectEnter(&pDevExt->CritSect);
408 }
409
410 /* free it and continue. */
411 RTMemFree(pUsage);
412 }
413
414 RTCritSectLeave(&pDevExt->CritSect);
415 AssertMsg(!pSession->pUsage, ("Some buster reregistered an object during destruction!\n"));
416 }
417
418 RTMemFree(pSession);
419 return cRefs;
420}
421
422
423/**
424 * Data available in th receive buffer callback.
425 */
426static DECLCALLBACK(void) intnetR3RecvAvail(INTNETIFHANDLE hIf, void *pvUser)
427{
428 RT_NOREF(hIf);
429 PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser;
430
431 if (ASMAtomicXchgBool(&pSession->fRecvWait, false))
432 {
433 /* Send an empty message. */
434 xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0);
435 xpc_connection_send_message(pSession->hXpcCon, hObjPoke);
436 xpc_release(hObjPoke);
437 }
438 else
439 ASMAtomicXchgBool(&pSession->fRecvAvail, true);
440}
441
442
443static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSUPDRVSESSION pSession)
444{
445 int rc = VINF_SUCCESS;
446 uint64_t iReq = xpc_dictionary_get_uint64(hObj, "req-id");
447 size_t cbReq = 0;
448 const void *pvReq = xpc_dictionary_get_data(hObj, "req", &cbReq);
449 union
450 {
451 INTNETOPENREQ OpenReq;
452 INTNETIFCLOSEREQ IfCloseReq;
453 INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq;
454 INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq;
455 INTNETIFSETMACADDRESSREQ IfSetMacAddressReq;
456 INTNETIFSETACTIVEREQ IfSetActiveReq;
457 INTNETIFSENDREQ IfSendReq;
458 INTNETIFWAITREQ IfWaitReq;
459 INTNETIFABORTWAITREQ IfAbortWaitReq;
460 } ReqReply;
461
462 memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq));
463 size_t cbReply = 0;
464
465 if (pvReq)
466 {
467 switch (iReq)
468 {
469 case VMMR0_DO_INTNET_OPEN:
470 {
471 if (cbReq == sizeof(INTNETOPENREQ))
472 {
473 rc = IntNetR3Open(pSession, &ReqReply.OpenReq.szNetwork[0], ReqReply.OpenReq.enmTrunkType, ReqReply.OpenReq.szTrunk,
474 ReqReply.OpenReq.fFlags, ReqReply.OpenReq.cbSend, ReqReply.OpenReq.cbRecv,
475 intnetR3RecvAvail, pSession, &ReqReply.OpenReq.hIf);
476 cbReply = sizeof(INTNETOPENREQ);
477 }
478 else
479 rc = VERR_INVALID_PARAMETER;
480 break;
481 }
482 case VMMR0_DO_INTNET_IF_CLOSE:
483 {
484 if (cbReq == sizeof(INTNETIFCLOSEREQ))
485 {
486 rc = IntNetR0IfCloseReq(pSession, &ReqReply.IfCloseReq);
487 cbReply = sizeof(INTNETIFCLOSEREQ);
488 }
489 else
490 rc = VERR_INVALID_PARAMETER;
491 break;
492 }
493 case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS:
494 {
495 if (cbReq == sizeof(INTNETIFGETBUFFERPTRSREQ))
496 {
497 rc = IntNetR0IfGetBufferPtrsReq(pSession, &ReqReply.IfGetBufferPtrsReq);
498 /* This is special as we need to return a shared memory segment. */
499 xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj);
500 xpc_object_t hObjShMem = xpc_shmem_create(ReqReply.IfGetBufferPtrsReq.pRing3Buf, ReqReply.IfGetBufferPtrsReq.pRing3Buf->cbBuf);
501 if (hObjShMem)
502 {
503 xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem);
504 xpc_release(hObjShMem);
505 }
506 else
507 rc = VERR_NO_MEMORY;
508
509 xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc));
510 xpc_connection_send_message(hCon, hObjReply);
511 xpc_release(hObjReply);
512 return;
513 }
514 else
515 rc = VERR_INVALID_PARAMETER;
516 break;
517 }
518 case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE:
519 {
520 if (cbReq == sizeof(INTNETIFSETPROMISCUOUSMODEREQ))
521 {
522 rc = IntNetR0IfSetPromiscuousModeReq(pSession, &ReqReply.IfSetPromiscuousModeReq);
523 cbReply = sizeof(INTNETIFSETPROMISCUOUSMODEREQ);
524 }
525 else
526 rc = VERR_INVALID_PARAMETER;
527 break;
528 }
529 case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS:
530 {
531 if (cbReq == sizeof(INTNETIFSETMACADDRESSREQ))
532 {
533 rc = IntNetR0IfSetMacAddressReq(pSession, &ReqReply.IfSetMacAddressReq);
534 cbReply = sizeof(INTNETIFSETMACADDRESSREQ);
535 }
536 else
537 rc = VERR_INVALID_PARAMETER;
538 break;
539 }
540 case VMMR0_DO_INTNET_IF_SET_ACTIVE:
541 {
542 if (cbReq == sizeof(INTNETIFSETACTIVEREQ))
543 {
544 rc = IntNetR0IfSetActiveReq(pSession, &ReqReply.IfSetActiveReq);
545 cbReply = sizeof(INTNETIFSETACTIVEREQ);
546 }
547 else
548 rc = VERR_INVALID_PARAMETER;
549 break;
550 }
551 case VMMR0_DO_INTNET_IF_SEND:
552 {
553 if (cbReq == sizeof(INTNETIFSENDREQ))
554 {
555 rc = IntNetR0IfSendReq(pSession, &ReqReply.IfSendReq);
556 cbReply = sizeof(INTNETIFSENDREQ);
557 }
558 else
559 rc = VERR_INVALID_PARAMETER;
560 break;
561 }
562 case VMMR0_DO_INTNET_IF_WAIT:
563 {
564 if (cbReq == sizeof(INTNETIFWAITREQ))
565 {
566 ASMAtomicXchgBool(&pSession->fRecvWait, true);
567 if (ASMAtomicXchgBool(&pSession->fRecvAvail, false))
568 {
569 ASMAtomicXchgBool(&pSession->fRecvWait, false);
570
571 /* Send an empty message. */
572 xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0);
573 xpc_connection_send_message(pSession->hXpcCon, hObjPoke);
574 xpc_release(hObjPoke);
575 }
576 return;
577 }
578 else
579 rc = VERR_INVALID_PARAMETER;
580 break;
581 }
582 case VMMR0_DO_INTNET_IF_ABORT_WAIT:
583 {
584 if (cbReq == sizeof(INTNETIFABORTWAITREQ))
585 {
586 ASMAtomicXchgBool(&pSession->fRecvWait, false);
587 if (ASMAtomicXchgBool(&pSession->fRecvAvail, false))
588 {
589 /* Send an empty message. */
590 xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0);
591 xpc_connection_send_message(pSession->hXpcCon, hObjPoke);
592 xpc_release(hObjPoke);
593 }
594 cbReply = sizeof(INTNETIFABORTWAITREQ);
595 }
596 else
597 rc = VERR_INVALID_PARAMETER;
598 break;
599 }
600 default:
601 rc = VERR_INVALID_PARAMETER;
602 }
603 }
604
605 xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj);
606 xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc));
607 xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbReply);
608 xpc_connection_send_message(hCon, hObjReply);
609 xpc_release(hObjReply);
610}
611
612
613DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon)
614{
615 xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) {
616 PSUPDRVSESSION pSession = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon);
617
618 if (xpc_get_type(hObj) == XPC_TYPE_ERROR)
619 {
620 if (hObj == XPC_ERROR_CONNECTION_INVALID)
621 intnetR3SessionDestroy(pSession);
622 else if (hObj == XPC_ERROR_TERMINATION_IMMINENT)
623 {
624 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
625
626 uint32_t cRefs = intnetR3SessionDestroy(pSession);
627 if (!cRefs)
628 {
629 /* Last one cleans up the global data. */
630 RTCritSectDelete(&pDevExt->CritSect);
631 }
632 }
633 }
634 else
635 intnetR3RequestProcess(hXpcCon, hObj, pSession);
636 });
637
638 PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession));
639 if (pSession)
640 {
641 pSession->pDevExt = &g_DevExt;
642 pSession->hXpcCon = hXpcCon;
643
644 xpc_connection_set_context(hXpcCon, pSession);
645 xpc_connection_resume(hXpcCon);
646 xpc_transaction_begin();
647 ASMAtomicIncU32(&g_DevExt.cRefs);
648 }
649}
650
651
652int main(int argc, char **argv)
653{
654 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
655 if (RT_SUCCESS(rc))
656 {
657 IntNetR0Init();
658
659 g_DevExt.pObjs = NULL;
660 rc = RTCritSectInit(&g_DevExt.CritSect);
661 if (RT_SUCCESS(rc))
662 xpc_main(xpcConnHandler); /* Never returns. */
663
664 exit(EXIT_FAILURE);
665 }
666
667 return RTMsgInitFailure(rc);
668}
669
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