VirtualBox

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

Last change on this file since 98570 was 98103, checked in by vboxsync, 22 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.6 KB
Line 
1/* $Id: VBoxIntNetSwitch.cpp 98103 2023-01-17 14:15:46Z 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 }
437 else
438 ASMAtomicXchgBool(&pSession->fRecvAvail, true);
439}
440
441
442static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSUPDRVSESSION pSession)
443{
444 int rc = VINF_SUCCESS;
445 uint64_t iReq = xpc_dictionary_get_uint64(hObj, "req-id");
446 size_t cbReq = 0;
447 const void *pvReq = xpc_dictionary_get_data(hObj, "req", &cbReq);
448 union
449 {
450 INTNETOPENREQ OpenReq;
451 INTNETIFCLOSEREQ IfCloseReq;
452 INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq;
453 INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq;
454 INTNETIFSETMACADDRESSREQ IfSetMacAddressReq;
455 INTNETIFSETACTIVEREQ IfSetActiveReq;
456 INTNETIFSENDREQ IfSendReq;
457 INTNETIFWAITREQ IfWaitReq;
458 INTNETIFABORTWAITREQ IfAbortWaitReq;
459 } ReqReply;
460
461 memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq));
462 size_t cbReply = 0;
463
464 if (pvReq)
465 {
466 switch (iReq)
467 {
468 case VMMR0_DO_INTNET_OPEN:
469 {
470 if (cbReq == sizeof(INTNETOPENREQ))
471 {
472 rc = IntNetR3Open(pSession, &ReqReply.OpenReq.szNetwork[0], ReqReply.OpenReq.enmTrunkType, ReqReply.OpenReq.szTrunk,
473 ReqReply.OpenReq.fFlags, ReqReply.OpenReq.cbSend, ReqReply.OpenReq.cbRecv,
474 intnetR3RecvAvail, pSession, &ReqReply.OpenReq.hIf);
475 cbReply = sizeof(INTNETOPENREQ);
476 }
477 else
478 rc = VERR_INVALID_PARAMETER;
479 break;
480 }
481 case VMMR0_DO_INTNET_IF_CLOSE:
482 {
483 if (cbReq == sizeof(INTNETIFCLOSEREQ))
484 {
485 rc = IntNetR0IfCloseReq(pSession, &ReqReply.IfCloseReq);
486 cbReply = sizeof(INTNETIFCLOSEREQ);
487 }
488 else
489 rc = VERR_INVALID_PARAMETER;
490 break;
491 }
492 case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS:
493 {
494 if (cbReq == sizeof(INTNETIFGETBUFFERPTRSREQ))
495 {
496 rc = IntNetR0IfGetBufferPtrsReq(pSession, &ReqReply.IfGetBufferPtrsReq);
497 /* This is special as we need to return a shared memory segment. */
498 xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj);
499 xpc_object_t hObjShMem = xpc_shmem_create(ReqReply.IfGetBufferPtrsReq.pRing3Buf, ReqReply.IfGetBufferPtrsReq.pRing3Buf->cbBuf);
500 if (hObjShMem)
501 {
502 xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem);
503 xpc_release(hObjShMem);
504 }
505 else
506 rc = VERR_NO_MEMORY;
507
508 xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc));
509 xpc_connection_send_message(hCon, hObjReply);
510 return;
511 }
512 else
513 rc = VERR_INVALID_PARAMETER;
514 break;
515 }
516 case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE:
517 {
518 if (cbReq == sizeof(INTNETIFSETPROMISCUOUSMODEREQ))
519 {
520 rc = IntNetR0IfSetPromiscuousModeReq(pSession, &ReqReply.IfSetPromiscuousModeReq);
521 cbReply = sizeof(INTNETIFSETPROMISCUOUSMODEREQ);
522 }
523 else
524 rc = VERR_INVALID_PARAMETER;
525 break;
526 }
527 case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS:
528 {
529 if (cbReq == sizeof(INTNETIFSETMACADDRESSREQ))
530 {
531 rc = IntNetR0IfSetMacAddressReq(pSession, &ReqReply.IfSetMacAddressReq);
532 cbReply = sizeof(INTNETIFSETMACADDRESSREQ);
533 }
534 else
535 rc = VERR_INVALID_PARAMETER;
536 break;
537 }
538 case VMMR0_DO_INTNET_IF_SET_ACTIVE:
539 {
540 if (cbReq == sizeof(INTNETIFSETACTIVEREQ))
541 {
542 rc = IntNetR0IfSetActiveReq(pSession, &ReqReply.IfSetActiveReq);
543 cbReply = sizeof(INTNETIFSETACTIVEREQ);
544 }
545 else
546 rc = VERR_INVALID_PARAMETER;
547 break;
548 }
549 case VMMR0_DO_INTNET_IF_SEND:
550 {
551 if (cbReq == sizeof(INTNETIFSENDREQ))
552 {
553 rc = IntNetR0IfSendReq(pSession, &ReqReply.IfSendReq);
554 cbReply = sizeof(INTNETIFSENDREQ);
555 }
556 else
557 rc = VERR_INVALID_PARAMETER;
558 break;
559 }
560 case VMMR0_DO_INTNET_IF_WAIT:
561 {
562 if (cbReq == sizeof(INTNETIFWAITREQ))
563 {
564 ASMAtomicXchgBool(&pSession->fRecvWait, true);
565 if (ASMAtomicXchgBool(&pSession->fRecvAvail, false))
566 {
567 ASMAtomicXchgBool(&pSession->fRecvWait, false);
568
569 /* Send an empty message. */
570 xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0);
571 xpc_connection_send_message(pSession->hXpcCon, hObjPoke);
572 }
573 return;
574 }
575 else
576 rc = VERR_INVALID_PARAMETER;
577 break;
578 }
579 case VMMR0_DO_INTNET_IF_ABORT_WAIT:
580 {
581 if (cbReq == sizeof(INTNETIFABORTWAITREQ))
582 {
583 ASMAtomicXchgBool(&pSession->fRecvWait, false);
584 if (ASMAtomicXchgBool(&pSession->fRecvAvail, false))
585 {
586 /* Send an empty message. */
587 xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0);
588 xpc_connection_send_message(pSession->hXpcCon, hObjPoke);
589 }
590 cbReply = sizeof(INTNETIFABORTWAITREQ);
591 }
592 else
593 rc = VERR_INVALID_PARAMETER;
594 break;
595 }
596 default:
597 rc = VERR_INVALID_PARAMETER;
598 }
599 }
600
601 xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj);
602 xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc));
603 xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbReply);
604 xpc_connection_send_message(hCon, hObjReply);
605}
606
607
608DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon)
609{
610 xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) {
611 PSUPDRVSESSION pSession = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon);
612
613 if (xpc_get_type(hObj) == XPC_TYPE_ERROR)
614 {
615 if (hObj == XPC_ERROR_CONNECTION_INVALID)
616 intnetR3SessionDestroy(pSession);
617 else if (hObj == XPC_ERROR_TERMINATION_IMMINENT)
618 {
619 PSUPDRVDEVEXT pDevExt = pSession->pDevExt;
620
621 uint32_t cRefs = intnetR3SessionDestroy(pSession);
622 if (!cRefs)
623 {
624 /* Last one cleans up the global data. */
625 RTCritSectDelete(&pDevExt->CritSect);
626 }
627 }
628 }
629 else
630 intnetR3RequestProcess(hXpcCon, hObj, pSession);
631 });
632
633 PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession));
634 if (pSession)
635 {
636 pSession->pDevExt = &g_DevExt;
637 pSession->hXpcCon = hXpcCon;
638
639 xpc_connection_set_context(hXpcCon, pSession);
640 xpc_connection_resume(hXpcCon);
641 xpc_transaction_begin();
642 ASMAtomicIncU32(&g_DevExt.cRefs);
643 }
644}
645
646
647int main(int argc, char **argv)
648{
649 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
650 if (RT_SUCCESS(rc))
651 {
652 IntNetR0Init();
653
654 g_DevExt.pObjs = NULL;
655 rc = RTCritSectInit(&g_DevExt.CritSect);
656 if (RT_SUCCESS(rc))
657 xpc_main(xpcConnHandler); /* Never returns. */
658
659 exit(EXIT_FAILURE);
660 }
661
662 return RTMsgInitFailure(rc);
663}
664
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