VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c@ 4811

Last change on this file since 4811 was 4811, checked in by vboxsync, 18 years ago

Split VMMR0Entry into VMMR0EntryInt, VMMR0EntryFast and VMMr0EntryEx. This will prevent the SUPCallVMMR0Ex path from causing harm and messing up the paths that has to be optimized.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 19.8 KB
Line 
1/** @file
2 * VBox host drivers - Ring-0 support drivers - Solaris host:
3 * Solaris driver C code
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include <sys/types.h>
23#include <sys/param.h>
24#include <sys/errno.h>
25#include <sys/uio.h>
26#include <sys/buf.h>
27#include <sys/modctl.h>
28#include <sys/open.h>
29#include <sys/conf.h>
30#include <sys/cmn_err.h>
31#include <sys/stat.h>
32#include <sys/ddi.h>
33#include <sys/sunddi.h>
34#include <sys/file.h>
35#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
36
37#include "SUPDRV.h"
38#include <iprt/spinlock.h>
39#include <iprt/process.h>
40#include <iprt/initterm.h>
41#include <iprt/alloc.h>
42
43
44/*******************************************************************************
45* Defined Constants And Macros *
46*******************************************************************************/
47/** The module name. */
48#define DEVICE_NAME "vboxdrv"
49/** The module description as seen in 'modinfo'. */
50#define DEVICE_DESC "VirtualBox Driver"
51/** Maximum number of driver instances. */
52#define DEVICE_MAXINSTANCES 16
53
54
55/*******************************************************************************
56* Internal Functions *
57*******************************************************************************/
58static int VBoxDrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred);
59static int VBoxDrvSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred);
60static int VBoxDrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred);
61static int VBoxDrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred);
62static int VBoxDrvSolarisIOCtl (dev_t Dev, int Cmd, intptr_t pArgs, int mode, cred_t *pCred, int *pVal);
63
64static int VBoxDrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd);
65static int VBoxDrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd);
66
67static int VBoxSupDrvErr2SolarisErr(int rc);
68static int VBoxDrvSolarisIOCtlSlow(PSUPDRVSESSION pSession, int Cmd, int Mode, intptr_t pArgs);
69
70
71/*******************************************************************************
72* Global Variables *
73*******************************************************************************/
74/**
75 * cb_ops: for drivers that support char/block entry points
76 */
77static struct cb_ops g_VBoxDrvSolarisCbOps =
78{
79 VBoxDrvSolarisOpen,
80 VBoxDrvSolarisClose,
81 nodev, /* b strategy */
82 nodev, /* b dump */
83 nodev, /* b print */
84 VBoxDrvSolarisRead,
85 VBoxDrvSolarisWrite,
86 VBoxDrvSolarisIOCtl,
87 nodev, /* c devmap */
88 nodev, /* c mmap */
89 nodev, /* c segmap */
90 nochpoll, /* c poll */
91 ddi_prop_op, /* property ops */
92 NULL, /* streamtab */
93 D_NEW | D_MP, /* compat. flag */
94 CB_REV /* revision */
95};
96
97/**
98 * dev_ops: for driver device operations
99 */
100static struct dev_ops g_VBoxDrvSolarisDevOps =
101{
102 DEVO_REV, /* driver build revision */
103 0, /* ref count */
104 nulldev, /* get info */
105 nulldev, /* identify */
106 nulldev, /* probe */
107 VBoxDrvSolarisAttach,
108 VBoxDrvSolarisDetach,
109 nodev, /* reset */
110 &g_VBoxDrvSolarisCbOps,
111 (struct bus_ops *)0,
112 nodev /* power */
113};
114
115/**
116 * modldrv: export driver specifics to the kernel
117 */
118static struct modldrv g_VBoxDrvSolarisModule =
119{
120 &mod_driverops, /* extern from kernel */
121 DEVICE_DESC,
122 &g_VBoxDrvSolarisDevOps
123};
124
125/**
126 * modlinkage: export install/remove/info to the kernel
127 */
128static struct modlinkage g_VBoxDrvSolarisModLinkage =
129{
130 MODREV_1, /* loadable module system revision */
131 &g_VBoxDrvSolarisModule,
132 NULL /* terminate array of linkage structures */
133};
134
135/** State info. for each driver instance. */
136typedef struct
137{
138 dev_info_t *pDip; /* Device handle */
139} vbox_devstate_t;
140
141/** Opaque pointer to state */
142static void *g_pVBoxDrvSolarisState;
143
144/** Device extention & session data association structure */
145static SUPDRVDEVEXT g_DevExt;
146
147/* GCC C++ hack. */
148unsigned __gxx_personality_v0 = 0xcccccccc;
149
150/** Hash table */
151static PSUPDRVSESSION g_apSessionHashTab[19];
152/** Spinlock protecting g_apSessionHashTab. */
153static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK;
154/** Calculates bucket index into g_apSessionHashTab.*/
155#define SESSION_HASH(sfn) ((sfn) % RT_ELEMENTS(g_apSessionHashTab))
156
157/**
158 * Kernel entry points
159 */
160int _init (void)
161{
162 cmn_err(CE_CONT, "VBoxDrvSolaris _init");
163
164 int e = ddi_soft_state_init(&g_pVBoxDrvSolarisState, sizeof (vbox_devstate_t), 1);
165 if (e != 0)
166 return e;
167
168 e = mod_install(&g_VBoxDrvSolarisModLinkage);
169 if (e != 0)
170 ddi_soft_state_fini(&g_pVBoxDrvSolarisState);
171
172 return e;
173}
174
175int _fini (void)
176{
177 cmn_err(CE_CONT, "VBoxDrvSolaris _fini");
178
179 int e = mod_remove(&g_VBoxDrvSolarisModLinkage);
180 if (e != 0)
181 return e;
182
183 ddi_soft_state_fini(&g_pVBoxDrvSolarisState);
184 return e;
185}
186
187int _info (struct modinfo *pModInfo)
188{
189 cmn_err(CE_CONT, "VBoxDrvSolaris _info");
190 return mod_info (&g_VBoxDrvSolarisModLinkage, pModInfo);
191}
192
193/**
194 * User context entry points
195 */
196static int VBoxDrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred)
197{
198 cmn_err(CE_CONT, "VBoxDrvSolarisOpen");
199
200 int rc;
201 PSUPDRVSESSION pSession;
202
203 /*
204 * Create a new session.
205 * Sessions in Solaris driver are mostly useless. It's however needed
206 * in VBoxDrvSolarisIOCtlSlow() while calling supdrvIOCtl()
207 */
208 rc = supdrvCreateSession(&g_DevExt, &pSession);
209 if (RT_SUCCESS(rc))
210 {
211 RTSPINLOCKTMP Tmp = RTSPINLOCKTMP_INITIALIZER;
212 unsigned iHash;
213
214 pSession->Uid = crgetuid(pCred);
215 pSession->Gid = crgetgid(pCred);
216 pSession->Process = RTProcSelf();
217 pSession->R0Process = RTR0ProcHandleSelf();
218
219 /*
220 * Insert it into the hash table.
221 */
222 iHash = SESSION_HASH(pSession->Process);
223 RTSpinlockAcquireNoInts(g_Spinlock, &Tmp);
224 pSession->pNextHash = g_apSessionHashTab[iHash];
225 g_apSessionHashTab[iHash] = pSession;
226 RTSpinlockReleaseNoInts(g_Spinlock, &Tmp);
227 cmn_err(CE_NOTE,"VBoxDrvSolarisOpen success");
228 }
229
230 int instance;
231 for (instance = 0; instance < DEVICE_MAXINSTANCES; instance++)
232 {
233 vbox_devstate_t *pState = ddi_get_soft_state (g_pVBoxDrvSolarisState, instance);
234 if (pState)
235 break;
236 }
237
238 if (instance >= DEVICE_MAXINSTANCES)
239 {
240 cmn_err(CE_NOTE, "VBoxDrvSolarisOpen: All instances exhausted\n");
241 return ENXIO;
242 }
243
244 *pDev = makedevice(getmajor(*pDev), instance);
245
246 return VBoxSupDrvErr2SolarisErr(rc);
247}
248
249static int VBoxDrvSolarisClose(dev_t pDev, int flag, int otyp, cred_t *cred)
250{
251 cmn_err(CE_CONT, "VBoxDrvSolarisClose");
252
253 RTSPINLOCKTMP Tmp = RTSPINLOCKTMP_INITIALIZER;
254 const RTPROCESS Process = RTProcSelf();
255 const unsigned iHash = SESSION_HASH(Process);
256 PSUPDRVSESSION pSession;
257
258 /*
259 * Remove from the hash table.
260 */
261 RTSpinlockAcquireNoInts(g_Spinlock, &Tmp);
262 pSession = g_apSessionHashTab[iHash];
263 if (pSession)
264 {
265 if (pSession->Process == Process)
266 {
267 g_apSessionHashTab[iHash] = pSession->pNextHash;
268 pSession->pNextHash = NULL;
269 }
270 else
271 {
272 PSUPDRVSESSION pPrev = pSession;
273 pSession = pSession->pNextHash;
274 while (pSession)
275 {
276 if (pSession->Process == Process)
277 {
278 pPrev->pNextHash = pSession->pNextHash;
279 pSession->pNextHash = NULL;
280 break;
281 }
282
283 /* next */
284 pPrev = pSession;
285 pSession = pSession->pNextHash;
286 }
287 }
288 }
289 RTSpinlockReleaseNoInts(g_Spinlock, &Tmp);
290 if (!pSession)
291 {
292 OSDBGPRINT(("VBoxDrvSolarisClose: WHAT?!? pSession == NULL! This must be a mistake... pid=%d (close)\n",
293 (int)Process));
294 return DDI_FAILURE;
295 }
296
297 /*
298 * Close the session.
299 */
300 supdrvCloseSession(&g_DevExt, pSession);
301 return DDI_SUCCESS;
302}
303
304static int VBoxDrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred)
305{
306 cmn_err(CE_CONT, "VBoxDrvSolarisRead");
307 return DDI_SUCCESS;
308}
309
310static int VBoxDrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred)
311{
312 cmn_err(CE_CONT, "VBoxDrvSolarisWrite");
313 return DDI_SUCCESS;
314}
315
316/**
317 * Attach entry point, to attach a device to the system or resume it.
318 *
319 * @param pDip The module structure instance.
320 * @param enmCmd Attach type (ddi_attach_cmd_t)
321 *
322 * @return corresponding solaris error code.
323 */
324static int VBoxDrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd)
325{
326 cmn_err(CE_CONT, "VBoxDrvSolarisAttach");
327 int rc = VINF_SUCCESS;
328 int instance = 0;
329 vbox_devstate_t *pState;
330
331 switch (enmCmd)
332 {
333 case DDI_ATTACH:
334 {
335 instance = ddi_get_instance (pDip);
336
337 if (ddi_soft_state_zalloc(g_pVBoxDrvSolarisState, instance) != DDI_SUCCESS)
338 {
339 cmn_err(CE_NOTE, "VBoxDrvSolarisAttach: state alloc failed");
340 return DDI_FAILURE;
341 }
342
343 pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, instance);
344
345 /*
346 * Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
347 */
348 rc = RTR0Init(0);
349 if (RT_SUCCESS(rc))
350 {
351 /*
352 * Initialize the device extension
353 */
354 rc = supdrvInitDevExt(&g_DevExt);
355 if (RT_SUCCESS(rc))
356 {
357 /*
358 * Initialize the session hash table.
359 */
360 memset(g_apSessionHashTab, 0, sizeof(g_apSessionHashTab));
361 rc = RTSpinlockCreate(&g_Spinlock);
362 if (RT_SUCCESS(rc))
363 {
364 /*
365 * Register ourselves as a character device, pseudo-driver
366 */
367 if (ddi_create_minor_node(pDip, "0", S_IFCHR,
368 instance, DDI_PSEUDO, 0) == DDI_SUCCESS)
369 {
370 pState->pDip = pDip;
371 ddi_report_dev(pDip);
372 return DDI_SUCCESS;
373 }
374
375 /* Is this really necessary? */
376 ddi_remove_minor_node(pDip, NULL);
377 cmn_err(CE_NOTE,"VBoxDrvSolarisAttach: ddi_create_minor_node failed.");
378
379 RTSpinlockDestroy(g_Spinlock);
380 g_Spinlock = NIL_RTSPINLOCK;
381 }
382 else
383 cmn_err(CE_NOTE, "VBoxDrvSolarisAttach: RTSpinlockCreate failed");
384 supdrvDeleteDevExt(&g_DevExt);
385 }
386 else
387 cmn_err(CE_NOTE, "VBoxDrvSolarisAttach: supdrvInitDevExt failed");
388 RTR0Term ();
389 }
390 else
391 cmn_err(CE_NOTE, "VBoxDrvSolarisAttach: failed to init R0Drv");
392 memset(&g_DevExt, 0, sizeof(g_DevExt));
393 break;
394 }
395
396 default:
397 return DDI_FAILURE;
398 }
399
400 return DDI_FAILURE;
401}
402
403/**
404 * Detach entry point, to detach a device to the system or suspend it.
405 *
406 * @param pDip The module structure instance.
407 * @param enmCmd Attach type (ddi_attach_cmd_t)
408 *
409 * @return corresponding solaris error code.
410 */
411static int VBoxDrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd)
412{
413 int rc = VINF_SUCCESS;
414 int instance;
415 register vbox_devstate_t *pState;
416
417 cmn_err(CE_CONT, "VBoxDrvSolarisDetach");
418 switch (enmCmd)
419 {
420 case DDI_DETACH:
421 {
422 instance = ddi_get_instance(pDip);
423 pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, instance);
424
425 ddi_remove_minor_node(pDip, NULL);
426 ddi_soft_state_free(g_pVBoxDrvSolarisState, instance);
427
428 supdrvDeleteDevExt(&g_DevExt);
429
430 rc = RTSpinlockDestroy(g_Spinlock);
431 AssertRC(rc);
432 g_Spinlock = NIL_RTSPINLOCK;
433
434 RTR0Term();
435
436 memset(&g_DevExt, 0, sizeof(g_DevExt));
437 cmn_err(CE_CONT, "VBoxDrvSolarisDetach: Clean Up Done.");
438 return DDI_SUCCESS;
439 }
440
441 default:
442 return DDI_FAILURE;
443 }
444}
445
446/**
447 * Driver ioctl, an alternate entry point for this character driver.
448 *
449 * @param Dev Device number
450 * @param Cmd Operation identifier
451 * @param pArg Arguments from user to driver
452 * @param Mode Information bitfield (read/write, address space etc.)
453 * @param pCred User credentials
454 * @param pVal Return value for calling process.
455 *
456 * @return corresponding solaris error code.
457 */
458static int VBoxDrvSolarisIOCtl (dev_t Dev, int Cmd, intptr_t pArgs, int Mode, cred_t* pCred, int* pVal)
459{
460 RTSPINLOCKTMP Tmp = RTSPINLOCKTMP_INITIALIZER;
461 const RTPROCESS Process = RTProcSelf();
462 const unsigned iHash = SESSION_HASH(Process);
463 PSUPDRVSESSION pSession;
464
465 cmn_err(CE_CONT, "VBoxDrvSolarisIOCtl\n");
466 /*
467 * Find the session.
468 */
469 RTSpinlockAcquireNoInts(g_Spinlock, &Tmp);
470 pSession = g_apSessionHashTab[iHash];
471 if (pSession && pSession->Process != Process)
472 {
473 do pSession = pSession->pNextHash;
474 while (pSession && pSession->Process != Process);
475 }
476 RTSpinlockReleaseNoInts(g_Spinlock, &Tmp);
477 if (!pSession)
478 {
479 OSDBGPRINT(("VBoxSupDrvIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#x\n",
480 (int)Process, Cmd));
481 return EINVAL;
482 }
483
484 /*
485 * Deal with the two high-speed IOCtl that takes it's arguments from
486 * the session and iCmd, and only returns a VBox status code.
487 */
488 if ( Cmd == SUP_IOCTL_FAST_DO_RAW_RUN
489 || Cmd == SUP_IOCTL_FAST_DO_HWACC_RUN
490 || Cmd == SUP_IOCTL_FAST_DO_NOP)
491 return supdrvIOCtlFast(Cmd, &g_DevExt, pSession);
492
493 return VBoxDrvSolarisIOCtlSlow(pSession, Cmd, Mode, pArgs);
494}
495
496/**
497 * Worker for VBoxSupDrvIOCtl that takes the slow IOCtl functions.
498 *
499 * @returns Solaris errno.
500 *
501 * @param pSession The session.
502 * @param Cmd The IOCtl command.
503 * @param Mode Information bitfield (for specifying ownership of data)
504 * @param iArg User space address of the request buffer.
505 */
506static int VBoxDrvSolarisIOCtlSlow(PSUPDRVSESSION pSession, int iCmd, int Mode, intptr_t iArg)
507{
508 int rc;
509 uint32_t cbBuf = 0;
510 SUPREQHDR Hdr;
511 PSUPREQHDR pHdr;
512
513
514 /*
515 * Read the header.
516 */
517 rc = ddi_copyin(&Hdr, (void *)iArg, sizeof(Hdr), Mode);
518 if (RT_UNLIKELY(rc))
519 {
520 dprintf(("VBoxDrvSolarisIOCtlSlow: ddi_copyin(,%#lx,) failed; iCmd=%#x. rc=%d\n", iArg, iCmd, rc));
521 return EFAULT;
522 }
523 if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC))
524 {
525 dprintf(("VBoxDrvSolarisIOCtlSlow: bad header magic %#x; iCmd=%#x\n", Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK, iCmd));
526 return EINVAL;
527 }
528
529 /*
530 * Buffer the request.
531 */
532 cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut);
533 if (RT_UNLIKELY(cbBuf > _1M*16))
534 {
535 dprintf(("VBoxDrvSolarisIOCtlSlow: too big cbBuf=%#x; iCmd=%#x\n", cbBuf, iCmd));
536 return E2BIG;
537 }
538 if (RT_UNLIKELY(cbBuf < sizeof(Hdr)))
539 {
540 dprintf(("VBoxDrvSolarisIOCtlSlow: bad ioctl cbBuf=%#x; iCmd=%#x.\n", cbBuf, iCmd));
541 return EINVAL;
542 }
543 pHdr = RTMemAlloc(cbBuf);
544 if (RT_UNLIKELY(!pHdr))
545 {
546 OSDBGPRINT(("VBoxDrvSolarisIOCtlSlow: failed to allocate buffer of %d bytes for iCmd=%#x.\n", cbBuf, iCmd));
547 return ENOMEM;
548 }
549 rc = ddi_copyin(pHdr, (void *)iArg, cbBuf, Mode);
550 if (RT_UNLIKELY(rc))
551 {
552 dprintf(("VBoxDrvSolarisIOCtlSlow: copy_from_user(,%#lx, %#x) failed; iCmd=%#x. rc=%d\n", iArg, Hdr.cbIn, iCmd, rc));
553 RTMemFree(pHdr);
554 return EFAULT;
555 }
556
557 /*
558 * Process the IOCtl.
559 */
560 rc = supdrvIOCtl(Cmd, &g_DevExt, pSession, pHdr);
561
562 /*
563 * Copy ioctl data and output buffer back to user space.
564 */
565 if (RT_LIKELY(!rc))
566 {
567 uint32_t cbOut = pHdr->cbOut;
568 if (RT_UNLIKELY(cbOut > cbBuf))
569 {
570 OSDBGPRINT(("VBoxDrvSolarisIOCtlSlow: too much output! %#x > %#x; iCmd=%#x!\n", cbOut, cbBuf, iCmd));
571 cbOut = cbBuf;
572 }
573 rc = ddi_copyout(pHdr, (void *)iArg, cbOut, Mode);
574 if (RT_UNLIKELY(rc != 0))
575 {
576 /* this is really bad */
577 OSDBGPRINT(("VBoxDrvSolarisIOCtlSlow: ddi_copyout(,%p,%d) failed. rc=%d\n", (void *)iArg, cbBuf, rc));
578 rc = EFAULT;
579 }
580 }
581 else
582 rc = EINVAL;
583
584 RTMemTmpFree(pHdr);
585 return rc;
586}
587
588
589/**
590 * Converts an supdrv error code to a solaris error code.
591 *
592 * @returns corresponding solaris error code.
593 * @param rc supdrv error code (SUPDRV_ERR_* defines).
594 */
595static int VBoxSupDrvErr2SolarisErr(int rc)
596{
597 switch (rc)
598 {
599 case 0: return 0;
600 case SUPDRV_ERR_GENERAL_FAILURE: return EACCES;
601 case SUPDRV_ERR_INVALID_PARAM: return EINVAL;
602 case SUPDRV_ERR_INVALID_MAGIC: return EILSEQ;
603 case SUPDRV_ERR_INVALID_HANDLE: return ENXIO;
604 case SUPDRV_ERR_INVALID_POINTER: return EFAULT;
605 case SUPDRV_ERR_LOCK_FAILED: return ENOLCK;
606 case SUPDRV_ERR_ALREADY_LOADED: return EEXIST;
607 case SUPDRV_ERR_PERMISSION_DENIED: return EPERM;
608 case SUPDRV_ERR_VERSION_MISMATCH: return ENOSYS;
609 }
610
611 return EPERM;
612}
613
614/**
615 * Initializes any OS specific object creator fields.
616 */
617void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession)
618{
619 NOREF(pObj);
620 NOREF(pSession);
621}
622
623
624/**
625 * Checks if the session can access the object.
626 *
627 * @returns true if a decision has been made.
628 * @returns false if the default access policy should be applied.
629 *
630 * @param pObj The object in question.
631 * @param pSession The session wanting to access the object.
632 * @param pszObjName The object name, can be NULL.
633 * @param prc Where to store the result when returning true.
634 */
635bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc)
636{
637 NOREF(pObj);
638 NOREF(pSession);
639 NOREF(pszObjName);
640 NOREF(prc);
641 return false;
642}
643
644
645RTDECL(int) SUPR0Printf(const char *pszFormat, ...)
646{
647 va_list args;
648 char szMsg[512];
649
650 va_start(args, pszFormat);
651 vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, args);
652 va_end(args);
653
654 szMsg[sizeof(szMsg) - 1] = '\0';
655 uprintf("%s", szMsg);
656 return 0;
657}
Note: See TracBrowser for help on using the repository browser.

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