VirtualBox

source: vbox/trunk/src/VBox/Additions/solaris/SharedFolders/vboxvfs_vfsops.c@ 11444

Last change on this file since 11444 was 10456, checked in by vboxsync, 16 years ago

Solaris vboxvfs: older bits commiting now.

  • Property svn:eol-style set to native
  • Property svn:keyword set to Id
  • Property svn:keywords set to Id
File size: 18.6 KB
Line 
1/* $Id: vboxvfs_vfsops.c 10456 2008-07-10 07:04:39Z vboxsync $ */
2/** @file
3 * VirtualBox File System Driver for Solaris Guests. VFS operations.
4 */
5
6/*
7 * Copyright (C) 2008 Sun Microsystems, Inc.
8 *
9 * Sun Microsystems, Inc. confidential
10 * All rights reserved
11 */
12
13
14/*******************************************************************************
15* Header Files *
16*******************************************************************************/
17#include <sys/types.h>
18#include <sys/mntent.h>
19#include <sys/param.h>
20#include <sys/modctl.h>
21#include <sys/mount.h>
22#include <sys/policy.h>
23#include <sys/atomic.h>
24#include <sys/sysmacros.h>
25#include <sys/ddi.h>
26#include <sys/sunddi.h>
27#include "vboxvfs.h"
28
29#if defined(DEBUG_ramshankar) && !defined(LOG_ENABLED)
30# define LOG_ENABLED
31# define LOG_TO_BACKDOOR
32#endif
33#include <VBox/log.h>
34#include <iprt/string.h>
35#include <iprt/mem.h>
36#include <iprt/err.h>
37#if defined(DEBUG_ramshankar)
38# undef LogFlow
39# define LogFlow LogRel
40# undef Log
41# define Log LogRel
42#endif
43
44/*******************************************************************************
45* Defined Constants And Macros *
46*******************************************************************************/
47/** Mount Options */
48#define MNTOPT_VBOXVFS_UID "uid"
49#define MNTOPT_VBOXVFS_GID "gid"
50
51
52/*******************************************************************************
53* Internal Functions *
54*******************************************************************************/
55static int VBoxVFS_Init(int fType, char *pszName);
56static int VBoxVFS_Mount(vfs_t *pVFS, vnode_t *pVNode, struct mounta *pMount, cred_t *pCred);
57static int VBoxVFS_Unmount(vfs_t *pVFS, int fFlags, cred_t *pCred);
58static int VBoxVFS_Root(vfs_t *pVFS, vnode_t **ppVNode);
59static int VBoxVFS_Statfs(register vfs_t *pVFS, struct statvfs64 *pStat);
60static int VBoxVFS_VGet(vfs_t *pVFS, vnode_t **ppVNode, struct fid *pFid);
61static void VBoxVFS_FreeVFS(vfs_t *pVFS);
62
63static int vboxvfs_CheckMountPerm(vfs_t *pVFS, struct mounta *pMount, vnode_t *pVNodeSpec, cred_t *pCred);
64static int vboxvfs_GetIntOpt(vfs_t *pVFS, char *pszOpt, int *pValue);
65
66
67/*******************************************************************************
68* Structures and Typedefs *
69*******************************************************************************/
70/**
71 * mntopts_t: mount options table array
72 */
73static mntopt_t g_VBoxVFSMountOptions[] =
74{
75 /* Option Name Cancel Opt. Default Arg Flags Data */
76 { MNTOPT_VBOXVFS_UID, NULL, NULL, MO_HASVALUE, NULL },
77 { MNTOPT_VBOXVFS_GID, NULL, NULL, MO_HASVALUE, NULL }
78};
79
80/**
81 * mntopts_t: mount options table prototype
82 */
83static mntopts_t g_VBoxVFSMountTableProt =
84{
85 sizeof(g_VBoxVFSMountOptions) / sizeof(mntopt_t),
86 g_VBoxVFSMountOptions
87};
88
89/**
90 * vfsdef_t: driver specific mount options
91 */
92static vfsdef_t g_VBoxVFSDef =
93{
94 VFSDEF_VERSION,
95 DEVICE_NAME,
96 VBoxVFS_Init,
97 VSW_HASPROTO,
98 &g_VBoxVFSMountTableProt
99};
100
101/**
102 * modlfs: loadable file system
103 */
104static struct modlfs g_VBoxVFSLoadMod =
105{
106 &mod_fsops, /* extern from kernel */
107 DEVICE_DESC,
108 &g_VBoxVFSDef
109};
110
111/**
112 * modlinkage: export install/remove/info to the kernel
113 */
114static struct modlinkage g_VBoxVFSModLinkage =
115{
116 MODREV_1, /* loadable module system revision */
117 &g_VBoxVFSLoadMod,
118 NULL /* terminate array of linkage structures */
119};
120
121
122/*******************************************************************************
123* Global Variables *
124*******************************************************************************/
125/** GCC C++ hack. */
126unsigned __gxx_personality_v0 = 0xdecea5ed;
127/** Global connection to the client. */
128VBSFCLIENT g_VBoxVFSClient;
129/** Global VFS Operations pointer. */
130vfsops_t *g_pVBoxVFS_vfsops;
131/** The file system type identifier. */
132static int g_VBoxVFSType;
133/** Major number of this module */
134static major_t g_VBoxVFSMajor;
135/** Minor instance number */
136static minor_t g_VBoxVFSMinor;
137/** Minor lock mutex protection */
138static kmutex_t g_VBoxVFSMinorMtx;
139
140
141/**
142 * Kernel entry points
143 */
144int _init(void)
145{
146 LogFlow((DEVICE_NAME ":_init\n"));
147
148 return mod_install(&g_VBoxVFSModLinkage);;
149}
150
151
152int _fini(void)
153{
154 LogFlow((DEVICE_NAME ":_fini\n"));
155
156 int rc = mod_remove(&g_VBoxVFSModLinkage);
157 if (rc)
158 return rc;
159
160 /* Blow away the operation vectors*/
161 vfs_freevfsops_by_type(g_VBoxVFSType);
162 vn_freevnodeops(g_pVBoxVFS_vnodeops);
163}
164
165
166int _info(struct modinfo *pModInfo)
167{
168 LogFlow((DEVICE_NAME ":_info\n"));
169
170 return mod_info(&g_VBoxVFSModLinkage, pModInfo);
171}
172
173
174static int VBoxVFS_Init(int fType, char *pszName)
175{
176 int rc;
177
178 LogFlow((DEVICE_NAME ":VBoxVFS_Init\n"));
179
180 g_VBoxVFSType = fType;
181
182 /* Initialize the R0 guest library. */
183 rc = vboxInit();
184 if (VBOX_SUCCESS(rc))
185 {
186 /* Connect to the host service. */
187 rc = vboxConnect(&g_VBoxVFSClient);
188 if (VBOX_SUCCESS(rc))
189 {
190 /* Use UTF-8 encoding. */
191 rc = vboxCallSetUtf8 (&g_VBoxVFSClient);
192 if (VBOX_SUCCESS(rc))
193 {
194 /* Fill up VFS user entry points. */
195 static const fs_operation_def_t s_VBoxVFS_vfsops_template[] =
196 {
197 VFSNAME_MOUNT, { .vfs_mount = VBoxVFS_Mount },
198 VFSNAME_UNMOUNT, { .vfs_unmount = VBoxVFS_Unmount },
199 VFSNAME_ROOT, { .vfs_root = VBoxVFS_Root },
200 VFSNAME_STATVFS, { .vfs_statvfs = VBoxVFS_Statfs },
201 VFSNAME_VGET, { .vfs_vget = VBoxVFS_VGet },
202 VFSNAME_FREEVFS, { .vfs_freevfs = VBoxVFS_FreeVFS },
203 NULL, NULL
204 };
205
206 rc = vfs_setfsops(fType, s_VBoxVFS_vfsops_template, &g_pVBoxVFS_vfsops);
207 if (!rc)
208 {
209 /* Set VNode operations. */
210 rc = vn_make_ops(pszName, g_VBoxVFS_vnodeops_template, &g_pVBoxVFS_vnodeops);
211 if (!rc)
212 {
213 /* Get a free major number. */
214 g_VBoxVFSMajor = getudev();
215 if (g_VBoxVFSMajor != (major_t)-1)
216 {
217 /* Initialize minor mutex here. */
218 mutex_init(&g_VBoxVFSMinorMtx, "VBoxVFSMinorMtx", MUTEX_DEFAULT, NULL);
219 LogFlow((DEVICE_NAME ":Successfully loaded vboxvfs.\n"));
220 return 0;
221 }
222 else
223 {
224 LogRel((DEVICE_NAME ":getudev failed.\n"));
225 rc = EMFILE;
226 }
227 }
228 else
229 LogRel((DEVICE_NAME ":vn_make_ops failed. rc=%d\n", rc));
230 }
231 else
232 LogRel((DEVICE_NAME ":vfs_setfsops failed. rc=%d\n", rc));
233 }
234 else
235 {
236 LogRel((DEVICE_NAME ":vboxCallSetUtf8 failed. rc=%d\n", rc));
237 rc = EPROTO;
238 }
239 vboxDisconnect(&g_VBoxVFSClient);
240 }
241 else
242 {
243 LogRel((DEVICE_NAME ":Failed to connect to host! rc=%d\n", rc));
244 rc = ENXIO;
245 }
246 vboxUninit();
247 }
248 else
249 {
250 LogRel((DEVICE_NAME ":Failed to initialize R0 lib. rc=%d\n", rc));
251 rc = ENXIO;
252 }
253 return rc;
254}
255
256static int VBoxVFS_Mount(vfs_t *pVFS, vnode_t *pVNode, struct mounta *pMount, cred_t *pCred)
257{
258 int rc = 0;
259 int Uid = 0;
260 int Gid = 0;
261 char *pszShare = NULL;
262 size_t cbShare = NULL;
263 pathname_t PathName;
264 vboxvfs_vnode_t *pVNodeRoot = NULL;
265 vnode_t *pVNodeSpec = NULL;
266 vnode_t *pVNodeDev = NULL;
267 dev_t Dev = 0;
268 SHFLSTRING *pShflShareName = NULL;
269 RTFSOBJINFO FSInfo;
270 size_t cbShflShareName = 0;
271 vboxvfs_globinfo_t *pVBoxVFSGlobalInfo = NULL;
272 int AddrSpace = (pMount->flags & MS_SYSSPACE) ? UIO_SYSSPACE : UIO_USERSPACE;
273
274 LogFlow((DEVICE_NAME ":VBoxVFS_Mount\n"));
275
276 /* Check user credentials for mounting in the specified target. */
277 rc = secpolicy_fs_mount(pCred, pVNode, pVFS);
278 if (rc)
279 {
280 LogRel((DEVICE_NAME ":VBoxVFS_Mount: secpolicy_fs_mount failed! invalid credentials.rc=%d\n", rc));
281 return EPERM;
282 }
283
284 /* We can mount to only directories. */
285 if (pVNode->v_type != VDIR)
286 return ENOTDIR;
287
288 /* We don't support remounting. */
289 if (pMount->flags & MS_REMOUNT)
290 return ENOTSUP;
291
292 mutex_enter(&pVNode->v_lock);
293 if ( !(pMount->flags & MS_OVERLAY)
294 && (pVNode->v_count > 1 || (pVNode->v_flag & VROOT)))
295 {
296 LogRel((DEVICE_NAME ":VBoxVFS_Mount: device busy.\n"));
297 mutex_exit(&pVNode->v_lock);
298 return EBUSY;
299 }
300 mutex_exit(&pVNode->v_lock);
301
302 /* From what I understood the options are already parsed at a higher level */
303 if ( (pMount->flags & MS_DATA)
304 && pMount->datalen > 0)
305 {
306 LogRel((DEVICE_NAME ":VBoxVFS_Mount: unparsed options not supported.\n"));
307 return EINVAL;
308 }
309
310 /* Get UID argument (optional). */
311 rc = vboxvfs_GetIntOpt(pVFS, MNTOPT_VBOXVFS_UID, &Uid);
312 if (rc < 0)
313 {
314 LogRel((DEVICE_NAME ":VBoxVFS_Mount: invalid uid value.\n"));
315 return EINVAL;
316 }
317
318 /* Get GID argument (optional). */
319 rc = vboxvfs_GetIntOpt(pVFS, MNTOPT_VBOXVFS_GID, &Gid);
320 if (rc < 0)
321 {
322 LogRel((DEVICE_NAME ":VBoxVFS_Mount: invalid gid value.\n"));
323 return EINVAL;
324 }
325
326 /* Get special (sharename). */
327 rc = pn_get(pMount->spec, AddrSpace, &PathName);
328 if (!rc)
329 {
330 /* Get an available minor instance number for this mount. */
331 mutex_enter(&g_VBoxVFSMinorMtx);
332 do
333 {
334 atomic_add_32_nv(&g_VBoxVFSMinor, 1) & L_MAXMIN32;
335 Dev = makedevice(g_VBoxVFSMajor, g_VBoxVFSMinor);
336 } while (vfs_devismounted(Dev));
337 mutex_exit(&g_VBoxVFSMinorMtx);
338
339 cbShare = strlen(PathName.pn_path);
340 pszShare = RTMemAlloc(cbShare);
341 if (pszShare)
342 memcpy(pszShare, PathName.pn_path, cbShare);
343 else
344 {
345 LogRel((DEVICE_NAME ":VBoxVFS_Mount: failed to alloc %d bytes for sharename.\n", cbShare));
346 rc = ENOMEM;
347 }
348 }
349 else
350 {
351 LogRel((DEVICE_NAME ":VBoxVFS_Mount: failed to get sharename.rc=%d\n", rc));
352 rc = EINVAL;
353 }
354 pn_free(&PathName);
355
356 if (rc)
357 {
358 if (pszShare)
359 RTMemFree(pszShare);
360 return rc;
361 }
362
363 /* Allocate the global info. structure. */
364 pVBoxVFSGlobalInfo = RTMemAlloc(sizeof(*pVBoxVFSGlobalInfo));
365 if (pVBoxVFSGlobalInfo)
366 {
367 cbShflShareName = offsetof(SHFLSTRING, String.utf8) + cbShare + 1;
368 pShflShareName = RTMemAllocZ(cbShflShareName);
369 if (pShflShareName)
370 {
371 pShflShareName->u16Length = cbShflShareName;
372 pShflShareName->u16Size = cbShflShareName + 1;
373 memcpy (pShflShareName->String.utf8, pszShare, cbShare + 1);
374
375 rc = vboxCallMapFolder(&g_VBoxVFSClient, pShflShareName, &pVBoxVFSGlobalInfo->Map);
376 RTMemFree(pShflShareName);
377 if (VBOX_SUCCESS(rc))
378 rc = 0;
379 else
380 {
381 LogRel((DEVICE_NAME ":VBoxVFS_Mount: vboxCallMapFolder failed rc=%d\n", rc));
382 rc = EPROTO;
383 }
384 }
385 else
386 {
387 LogRel((DEVICE_NAME ":VBoxVFS_Mount: RTMemAllocZ failed to alloc %d bytes for ShFlShareName.\n", cbShflShareName));
388 rc = ENOMEM;
389 }
390 RTMemFree(pVBoxVFSGlobalInfo);
391 }
392 else
393 {
394 LogRel((DEVICE_NAME ":VBoxVFS_Mount: RTMemAlloc failed to alloc %d bytes for global struct.\n", sizeof(*pVBoxVFSGlobalInfo)));
395 rc = ENOMEM;
396 }
397
398 /* Undo work on failure. */
399 if (rc)
400 goto mntError1;
401
402 /* Initialize the per-filesystem mutex */
403 mutex_init(&pVBoxVFSGlobalInfo->MtxFS, "VBoxVFS_FSMtx", MUTEX_DEFAULT, NULL);
404
405 pVBoxVFSGlobalInfo->Uid = Uid;
406 pVBoxVFSGlobalInfo->Gid = Gid;
407 pVBoxVFSGlobalInfo->pVFS = pVFS;
408 pVFS->vfs_data = pVBoxVFSGlobalInfo;
409 pVFS->vfs_fstype = g_VBoxVFSType;
410 pVFS->vfs_dev = Dev;
411 vfs_make_fsid(&pVFS->vfs_fsid, Dev, g_VBoxVFSType);
412
413 /* Allocate root vboxvfs_vnode_t object */
414 pVNodeRoot = RTMemAlloc(sizeof(*pVNodeRoot));
415 if (pVNodeRoot)
416 {
417 /* Initialize vnode protection mutex */
418 mutex_init(&pVNodeRoot->MtxContents, "VBoxVFS_VNodeMtx", MUTEX_DEFAULT, NULL);
419
420 pVBoxVFSGlobalInfo->pVNodeRoot = pVNodeRoot;
421
422 /* Allocate root path */
423 pVNodeRoot->pPath = RTMemAllocZ(sizeof(SHFLSTRING) + 1);
424 if (pVNodeRoot->pPath)
425 {
426 /* Initialize root path */
427 pVNodeRoot->pPath->u16Length = 1;
428 pVNodeRoot->pPath->u16Size = 2;
429 pVNodeRoot->pPath->String.utf8[0] = '/';
430 pVNodeRoot->pPath->String.utf8[1] = '\0';
431
432 /* Stat root node info from host */
433 rc = vboxvfs_Stat(__func__, pVBoxVFSGlobalInfo, pVNodeRoot->pPath, &FSInfo, B_FALSE);
434 if (!rc)
435 {
436 /* Initialize the root vboxvfs_node_t object */
437 vboxvfs_InitVNode(pVBoxVFSGlobalInfo, pVNodeRoot, &FSInfo);
438
439 /* Success! */
440 LogFlow((DEVICE_NAME ":VBoxVFS_Mount: success!\n", rc));
441 return 0;
442 }
443 else
444 LogRel((DEVICE_NAME ":VBoxVFS_Mount: vboxvfs_Stat failed rc(errno)=%d\n", rc));
445 RTMemFree(pVNodeRoot->pPath);
446 }
447 else
448 {
449 LogRel((DEVICE_NAME ":VBoxVFS_Mount: failed to alloc memory for root node path.\n"));
450 rc = ENOMEM;
451 }
452 RTMemFree(pVNodeRoot);
453 }
454 else
455 {
456 LogRel((DEVICE_NAME ":VBoxVFS_Mount: failed to alloc memory for root node.\n"));
457 rc = ENOMEM;
458 }
459
460 /* Undo work in reverse. */
461mntError1:
462 RTMemFree(pszShare);
463 return rc;
464}
465
466static int VBoxVFS_Unmount(vfs_t *pVFS, int fUnmount, cred_t *pCred)
467{
468 int rc;
469 vboxvfs_globinfo_t *pVBoxVFSGlobalInfo;
470
471 LogFlow((DEVICE_NAME ":VBoxVFS_Unmount.\n"));
472
473 /* Check if user can unmount. */
474 rc = secpolicy_fs_unmount(pCred, pVFS);
475 if (rc)
476 {
477 LogRel((DEVICE_NAME ":VBoxVFS_Unmount: insufficient privileges to unmount.rc=%d\n", rc));
478 return EPERM;
479 }
480
481 /* @todo -XXX - Not sure of supporting force unmounts. What this means is that a failed force mount could bring down
482 * the entire system as hanging about vnode releases would no longer be valid after unloading ourselves...
483 */
484 if (fUnmount & MS_FORCE)
485 pVFS->vfs_flag |= VFS_UNMOUNTED;
486
487 /* @todo implement ref-counting of active vnodes & check for busy state here. */
488 /* @todo mutex protection needed here */
489 pVBoxVFSGlobalInfo = VFS_TO_VBOXVFS(pVFS);
490
491 rc = vboxCallUnmapFolder(&g_VBoxVFSClient, &pVBoxVFSGlobalInfo->Map);
492 if (VBOX_FAILURE(rc))
493 LogRel((DEVICE_NAME ":VBoxVFS_Unmount: failed to unmap shared folder. rc=%d\n", rc));
494
495 VN_RELE(VBOXVN_TO_VN(pVBoxVFSGlobalInfo->pVNodeRoot));
496
497 RTMemFree(pVBoxVFSGlobalInfo->pVNodeRoot->pPath);
498 RTMemFree(pVBoxVFSGlobalInfo->pVNodeRoot);
499 RTMemFree(pVBoxVFSGlobalInfo);
500 pVBoxVFSGlobalInfo = NULL;
501 pVFS->vfs_data = NULL;
502
503 return 0;
504}
505
506static int VBoxVFS_Root(vfs_t *pVFS, vnode_t **ppVNode)
507{
508 vboxvfs_globinfo_t *pVBoxVFSGlobalInfo = VFS_TO_VBOXVFS(pVFS);
509 *ppVNode = VBOXVN_TO_VN(pVBoxVFSGlobalInfo->pVNodeRoot);
510 VN_HOLD(*ppVNode);
511
512 return 0;
513}
514
515static int VBoxVFS_Statfs(register vfs_t *pVFS, struct statvfs64 *pStat)
516{
517 SHFLVOLINFO VolumeInfo;
518 uint32_t cbBuffer;
519 vboxvfs_globinfo_t *pVBoxVFSGlobalInfo;
520 dev32_t Dev32;
521 int rc;
522
523 pVBoxVFSGlobalInfo = VFS_TO_VBOXVFS(pVFS);
524 cbBuffer = sizeof(VolumeInfo);
525 rc = vboxCallFSInfo(&g_VBoxVFSClient, &pVBoxVFSGlobalInfo->Map, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME, &cbBuffer,
526 (PSHFLDIRINFO)&VolumeInfo);
527 if (VBOX_FAILURE(rc))
528 return RTErrConvertToErrno(rc);
529
530 bzero(pStat, sizeof(*pStat));
531 cmpldev(&Dev32, pVFS->vfs_dev);
532 pStat->f_fsid = Dev32;
533 pStat->f_flag = vf_to_stf(pVFS->vfs_flag);
534 pStat->f_bsize = VolumeInfo.ulBytesPerAllocationUnit;
535 pStat->f_frsize = VolumeInfo.ulBytesPerAllocationUnit;
536 pStat->f_bfree = VolumeInfo.ullAvailableAllocationBytes / VolumeInfo.ulBytesPerAllocationUnit;
537 pStat->f_bavail = VolumeInfo.ullAvailableAllocationBytes / VolumeInfo.ulBytesPerAllocationUnit;
538 pStat->f_blocks = VolumeInfo.ullTotalAllocationBytes / VolumeInfo.ulBytesPerAllocationUnit;
539 pStat->f_files = 1000;
540 pStat->f_ffree = 1000; /* don't return 0 here since the guest may think that it is not possible to create any more files */
541 pStat->f_namemax = 255; /* @todo is this correct?? */
542
543 strlcpy(pStat->f_basetype, vfssw[pVFS->vfs_fstype].vsw_name, sizeof(pStat->f_basetype));
544 strlcpy(pStat->f_fstr, DEVICE_NAME, sizeof(pStat->f_fstr));
545
546 return 0;
547}
548
549static int VBoxVFS_VGet(vfs_t *pVFS, vnode_t **ppVNode, struct fid *pFid)
550{
551 /* -- TODO -- */
552 return 0;
553}
554
555static void VBoxVFS_FreeVFS(vfs_t *pVFS)
556{
557 vboxDisconnect(&g_VBoxVFSClient);
558 vboxUninit();
559}
560
561static int vboxvfs_CheckMountPerm(vfs_t *pVFS, struct mounta *pMount, vnode_t *pVNodeSpec, cred_t *pCred)
562{
563 /* Check if user has the rights to mount the special file. */
564 int fOpen = FREAD | FWRITE;
565 int fAccess = VREAD | VWRITE;
566 int rc;
567
568 if (pVNodeSpec->v_type != VBLK)
569 return ENOTBLK;
570
571 if ( (pVFS->vfs_flag & VFS_RDONLY)
572 || (pMount->flags & MS_RDONLY))
573 {
574 fOpen = FREAD;
575 fAccess = VREAD;
576 }
577
578 rc = VOP_ACCESS(pVNodeSpec, fAccess, 0, pCred, NULL /* caller_context */);
579 if (!rc)
580 rc = secpolicy_spec_open(pCred, pVNodeSpec, fOpen);
581
582 return rc;
583}
584
585static int vboxvfs_GetIntOpt(vfs_t *pVFS, char *pszOpt, int *pValue)
586{
587 int rc;
588 long Val;
589 char *pchOpt = NULL;
590 char *pchEnd = NULL;
591
592 rc = vfs_optionisset(pVFS, pszOpt, &pchOpt);
593 if (rc)
594 {
595 rc = ddi_strtol(pchOpt, &pchEnd, 10 /* base */, &Val);
596 if ( !rc
597 && Val > INT_MIN
598 && Val < INT_MAX
599 && pchEnd == pchOpt + strlen(pchOpt))
600 {
601 *pValue = (int)Val;
602 return 0;
603 }
604 return -1;
605 }
606 return 1;
607}
608
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