VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/fileio.cpp@ 77754

Last change on this file since 77754 was 77209, checked in by vboxsync, 6 years ago

IPRT: Split RTFileWriteAt & RTFileReadAt out of fileio.cpp and into fileio-at-generic.cpp, and RTFileSgWriteAt & RTFileSgReadAt into fileio-sg-generic.cpp. bugref:9172

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 19.5 KB
Line 
1/* $Id: fileio.cpp 77209 2019-02-07 23:46:50Z vboxsync $ */
2/** @file
3 * IPRT - File I/O.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/file.h>
33
34#include <iprt/mem.h>
35#include <iprt/assert.h>
36#include <iprt/alloca.h>
37#include <iprt/string.h>
38#include <iprt/err.h>
39#include "internal/file.h"
40
41
42/*********************************************************************************************************************************
43* Global Variables *
44*********************************************************************************************************************************/
45/** Set of forced set open flags for files opened read-only. */
46static unsigned g_fOpenReadSet = 0;
47
48/** Set of forced cleared open flags for files opened read-only. */
49static unsigned g_fOpenReadMask = 0;
50
51/** Set of forced set open flags for files opened write-only. */
52static unsigned g_fOpenWriteSet = 0;
53
54/** Set of forced cleared open flags for files opened write-only. */
55static unsigned g_fOpenWriteMask = 0;
56
57/** Set of forced set open flags for files opened read-write. */
58static unsigned g_fOpenReadWriteSet = 0;
59
60/** Set of forced cleared open flags for files opened read-write. */
61static unsigned g_fOpenReadWriteMask = 0;
62
63
64/**
65 * Force the use of open flags for all files opened after the setting is
66 * changed. The caller is responsible for not causing races with RTFileOpen().
67 *
68 * @returns iprt status code.
69 * @param fOpenForAccess Access mode to which the set/mask settings apply.
70 * @param fSet Open flags to be forced set.
71 * @param fMask Open flags to be masked out.
72 */
73RTR3DECL(int) RTFileSetForceFlags(unsigned fOpenForAccess, unsigned fSet, unsigned fMask)
74{
75 /*
76 * For now allow only RTFILE_O_WRITE_THROUGH. The other flags either
77 * make no sense in this context or are not useful to apply to all files.
78 */
79 if ((fSet | fMask) & ~RTFILE_O_WRITE_THROUGH)
80 return VERR_INVALID_PARAMETER;
81 switch (fOpenForAccess)
82 {
83 case RTFILE_O_READ:
84 g_fOpenReadSet = fSet;
85 g_fOpenReadMask = fMask;
86 break;
87 case RTFILE_O_WRITE:
88 g_fOpenWriteSet = fSet;
89 g_fOpenWriteMask = fMask;
90 break;
91 case RTFILE_O_READWRITE:
92 g_fOpenReadWriteSet = fSet;
93 g_fOpenReadWriteMask = fMask;
94 break;
95 default:
96 AssertMsgFailed(("Invalid access mode %d\n", fOpenForAccess));
97 return VERR_INVALID_PARAMETER;
98 }
99 return VINF_SUCCESS;
100}
101
102
103/**
104 * Adjusts and validates the flags.
105 *
106 * The adjustments are made according to the wishes specified using the RTFileSetForceFlags API.
107 *
108 * @returns IPRT status code.
109 * @param pfOpen Pointer to the user specified flags on input.
110 * Updated on successful return.
111 * @internal
112 */
113int rtFileRecalcAndValidateFlags(uint64_t *pfOpen)
114{
115 /*
116 * Recalc.
117 */
118 uint32_t fOpen = *pfOpen;
119 switch (fOpen & RTFILE_O_ACCESS_MASK)
120 {
121 case RTFILE_O_READ:
122 fOpen |= g_fOpenReadSet;
123 fOpen &= ~g_fOpenReadMask;
124 break;
125 case RTFILE_O_WRITE:
126 fOpen |= g_fOpenWriteSet;
127 fOpen &= ~g_fOpenWriteMask;
128 break;
129 case RTFILE_O_READWRITE:
130 fOpen |= g_fOpenReadWriteSet;
131 fOpen &= ~g_fOpenReadWriteMask;
132 break;
133#ifdef RT_OS_WINDOWS
134 case RTFILE_O_ATTR_ONLY:
135 if (fOpen & RTFILE_O_ACCESS_ATTR_MASK)
136 break;
137#endif
138 default:
139 AssertMsgFailed(("Invalid access mode value, fOpen=%#llx\n", fOpen));
140 return VERR_INVALID_PARAMETER;
141 }
142
143 /*
144 * Validate .
145 */
146#ifdef RT_OS_WINDOWS
147 AssertMsgReturn((fOpen & RTFILE_O_ACCESS_MASK) || (fOpen & RTFILE_O_ACCESS_ATTR_MASK),
148 ("Missing RTFILE_O_READ/WRITE/ACCESS_ATTR: fOpen=%#llx\n", fOpen), VERR_INVALID_PARAMETER);
149#else
150 AssertMsgReturn(fOpen & RTFILE_O_ACCESS_MASK, ("Missing RTFILE_O_READ/WRITE: fOpen=%#llx\n", fOpen), VERR_INVALID_PARAMETER);
151#endif
152#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
153 AssertMsgReturn(!(fOpen & (~(uint64_t)RTFILE_O_VALID_MASK | RTFILE_O_NON_BLOCK)), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER);
154#else
155 AssertMsgReturn(!(fOpen & ~(uint64_t)RTFILE_O_VALID_MASK), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER);
156#endif
157 AssertMsgReturn((fOpen & (RTFILE_O_TRUNCATE | RTFILE_O_WRITE)) != RTFILE_O_TRUNCATE, ("%#llx\n", fOpen), VERR_INVALID_PARAMETER);
158
159 switch (fOpen & RTFILE_O_ACTION_MASK)
160 {
161 case 0: /* temporarily */
162 AssertMsgFailed(("Missing RTFILE_O_OPEN/CREATE*! (continuable assertion)\n"));
163 fOpen |= RTFILE_O_OPEN;
164 break;
165 case RTFILE_O_OPEN:
166 AssertMsgReturn(!(RTFILE_O_NOT_CONTENT_INDEXED & fOpen), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER);
167 case RTFILE_O_OPEN_CREATE:
168 case RTFILE_O_CREATE:
169 case RTFILE_O_CREATE_REPLACE:
170 break;
171 default:
172 AssertMsgFailed(("Invalid action value: fOpen=%#llx\n", fOpen));
173 return VERR_INVALID_PARAMETER;
174 }
175
176 switch (fOpen & RTFILE_O_DENY_MASK)
177 {
178 case 0: /* temporarily */
179 AssertMsgFailed(("Missing RTFILE_O_DENY_*! (continuable assertion)\n"));
180 fOpen |= RTFILE_O_DENY_NONE;
181 break;
182 case RTFILE_O_DENY_NONE:
183 case RTFILE_O_DENY_READ:
184 case RTFILE_O_DENY_WRITE:
185 case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ:
186 case RTFILE_O_DENY_NOT_DELETE:
187 case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_READ:
188 case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_WRITE:
189 case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ:
190 break;
191 default:
192 AssertMsgFailed(("Invalid deny value: fOpen=%#llx\n", fOpen));
193 return VERR_INVALID_PARAMETER;
194 }
195
196 /* done */
197 *pfOpen = fOpen;
198 return VINF_SUCCESS;
199}
200
201
202/**
203 * Gets the current file position.
204 *
205 * @returns File offset.
206 * @returns ~0UUL on failure.
207 * @param File File handle.
208 */
209RTR3DECL(uint64_t) RTFileTell(RTFILE File)
210{
211 /*
212 * Call the seek api to query the stuff.
213 */
214 uint64_t off = 0;
215 int rc = RTFileSeek(File, 0, RTFILE_SEEK_CURRENT, &off);
216 if (RT_SUCCESS(rc))
217 return off;
218 AssertMsgFailed(("RTFileSeek(%d) -> %d\n", File, rc));
219 return ~0ULL;
220}
221
222
223/**
224 * Determine the maximum file size.
225 *
226 * @returns The max size of the file.
227 * -1 on failure, the file position is undefined.
228 * @param File Handle to the file.
229 * @see RTFileGetMaxSizeEx.
230 */
231RTR3DECL(RTFOFF) RTFileGetMaxSize(RTFILE File)
232{
233 RTFOFF cbMax;
234 int rc = RTFileGetMaxSizeEx(File, &cbMax);
235 return RT_SUCCESS(rc) ? cbMax : -1;
236}
237
238
239RTDECL(int) RTFileCopyByHandles(RTFILE FileSrc, RTFILE FileDst)
240{
241 return RTFileCopyByHandlesEx(FileSrc, FileDst, NULL, NULL);
242}
243
244
245RTDECL(int) RTFileCopyEx(const char *pszSrc, const char *pszDst, uint32_t fFlags, PFNRTPROGRESS pfnProgress, void *pvUser)
246{
247 /*
248 * Validate input.
249 */
250 AssertMsgReturn(VALID_PTR(pszSrc), ("pszSrc=%p\n", pszSrc), VERR_INVALID_PARAMETER);
251 AssertMsgReturn(*pszSrc, ("pszSrc=%p\n", pszSrc), VERR_INVALID_PARAMETER);
252 AssertMsgReturn(VALID_PTR(pszDst), ("pszDst=%p\n", pszDst), VERR_INVALID_PARAMETER);
253 AssertMsgReturn(*pszDst, ("pszDst=%p\n", pszDst), VERR_INVALID_PARAMETER);
254 AssertMsgReturn(!pfnProgress || VALID_PTR(pfnProgress), ("pfnProgress=%p\n", pfnProgress), VERR_INVALID_PARAMETER);
255 AssertMsgReturn(!(fFlags & ~RTFILECOPY_FLAGS_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
256
257 /*
258 * Open the files.
259 */
260 RTFILE FileSrc;
261 int rc = RTFileOpen(&FileSrc, pszSrc,
262 RTFILE_O_READ | RTFILE_O_OPEN
263 | (fFlags & RTFILECOPY_FLAGS_NO_SRC_DENY_WRITE ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE));
264 if (RT_SUCCESS(rc))
265 {
266 RTFILE FileDst;
267 rc = RTFileOpen(&FileDst, pszDst,
268 RTFILE_O_WRITE | RTFILE_O_CREATE
269 | (fFlags & RTFILECOPY_FLAGS_NO_DST_DENY_WRITE ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE));
270 if (RT_SUCCESS(rc))
271 {
272 /*
273 * Call the ByHandles version and let it do the job.
274 */
275 rc = RTFileCopyByHandlesEx(FileSrc, FileDst, pfnProgress, pvUser);
276
277 /*
278 * Close the files regardless of the result.
279 * Don't bother cleaning up or anything like that.
280 */
281 int rc2 = RTFileClose(FileDst);
282 AssertRC(rc2);
283 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
284 rc = rc2;
285 }
286
287 int rc2 = RTFileClose(FileSrc);
288 AssertRC(rc2);
289 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
290 rc = rc2;
291 }
292 return rc;
293}
294
295
296RTDECL(int) RTFileCopyByHandlesEx(RTFILE FileSrc, RTFILE FileDst, PFNRTPROGRESS pfnProgress, void *pvUser)
297{
298 /*
299 * Validate input.
300 */
301 AssertMsgReturn(RTFileIsValid(FileSrc), ("FileSrc=%RTfile\n", FileSrc), VERR_INVALID_PARAMETER);
302 AssertMsgReturn(RTFileIsValid(FileDst), ("FileDst=%RTfile\n", FileDst), VERR_INVALID_PARAMETER);
303 AssertMsgReturn(!pfnProgress || VALID_PTR(pfnProgress), ("pfnProgress=%p\n", pfnProgress), VERR_INVALID_PARAMETER);
304
305 /*
306 * Save file offset.
307 */
308 RTFOFF offSrcSaved;
309 int rc = RTFileSeek(FileSrc, 0, RTFILE_SEEK_CURRENT, (uint64_t *)&offSrcSaved);
310 if (RT_FAILURE(rc))
311 return rc;
312
313 /*
314 * Get the file size.
315 */
316 RTFOFF cbSrc;
317 rc = RTFileSeek(FileSrc, 0, RTFILE_SEEK_END, (uint64_t *)&cbSrc);
318 if (RT_FAILURE(rc))
319 return rc;
320
321 /*
322 * Allocate buffer.
323 */
324 size_t cbBuf;
325 uint8_t *pbBufFree = NULL;
326 uint8_t *pbBuf;
327 if (cbSrc < _512K)
328 {
329 cbBuf = 8*_1K;
330 pbBuf = (uint8_t *)alloca(cbBuf);
331 }
332 else
333 {
334 cbBuf = _128K;
335 pbBuf = pbBufFree = (uint8_t *)RTMemTmpAlloc(cbBuf);
336 }
337 if (pbBuf)
338 {
339 /*
340 * Seek to the start of each file
341 * and set the size of the destination file.
342 */
343 rc = RTFileSeek(FileSrc, 0, RTFILE_SEEK_BEGIN, NULL);
344 if (RT_SUCCESS(rc))
345 {
346 rc = RTFileSeek(FileDst, 0, RTFILE_SEEK_BEGIN, NULL);
347 if (RT_SUCCESS(rc))
348 rc = RTFileSetSize(FileDst, cbSrc);
349 if (RT_SUCCESS(rc) && pfnProgress)
350 rc = pfnProgress(0, pvUser);
351 if (RT_SUCCESS(rc))
352 {
353 /*
354 * Copy loop.
355 */
356 unsigned uPercentage = 0;
357 RTFOFF off = 0;
358 RTFOFF cbPercent = cbSrc / 100;
359 RTFOFF offNextPercent = cbPercent;
360 while (off < cbSrc)
361 {
362 /* copy block */
363 RTFOFF cbLeft = cbSrc - off;
364 size_t cbBlock = cbLeft >= (RTFOFF)cbBuf ? cbBuf : (size_t)cbLeft;
365 rc = RTFileRead(FileSrc, pbBuf, cbBlock, NULL);
366 if (RT_FAILURE(rc))
367 break;
368 rc = RTFileWrite(FileDst, pbBuf, cbBlock, NULL);
369 if (RT_FAILURE(rc))
370 break;
371
372 /* advance */
373 off += cbBlock;
374 if (pfnProgress && offNextPercent < off && uPercentage < 100)
375 {
376 do
377 {
378 uPercentage++;
379 offNextPercent += cbPercent;
380 } while (offNextPercent < off && uPercentage < 100);
381 rc = pfnProgress(uPercentage, pvUser);
382 if (RT_FAILURE(rc))
383 break;
384 }
385 }
386
387#if 0
388 /*
389 * Copy OS specific data (EAs and stuff).
390 */
391 rtFileCopyOSStuff(FileSrc, FileDst);
392#endif
393
394 /* 100% */
395 if (pfnProgress && uPercentage < 100 && RT_SUCCESS(rc))
396 rc = pfnProgress(100, pvUser);
397 }
398 }
399 RTMemTmpFree(pbBufFree);
400 }
401 else
402 rc = VERR_NO_MEMORY;
403
404 /*
405 * Restore source position.
406 */
407 RTFileSeek(FileSrc, offSrcSaved, RTFILE_SEEK_BEGIN, NULL);
408
409 return rc;
410}
411
412
413RTDECL(int) RTFileCompare(const char *pszFile1, const char *pszFile2)
414{
415 return RTFileCompareEx(pszFile1, pszFile2, 0 /*fFlags*/, NULL, NULL);
416}
417
418
419RTDECL(int) RTFileCompareByHandles(RTFILE hFile1, RTFILE hFile2)
420{
421 return RTFileCompareByHandlesEx(hFile1, hFile2, 0 /*fFlags*/, NULL, NULL);
422}
423
424
425RTDECL(int) RTFileCompareEx(const char *pszFile1, const char *pszFile2, uint32_t fFlags, PFNRTPROGRESS pfnProgress, void *pvUser)
426{
427 /*
428 * Validate input.
429 */
430 AssertPtrReturn(pszFile1, VERR_INVALID_POINTER);
431 AssertReturn(*pszFile1, VERR_INVALID_PARAMETER);
432 AssertPtrReturn(pszFile2, VERR_INVALID_POINTER);
433 AssertReturn(*pszFile2, VERR_INVALID_PARAMETER);
434 AssertMsgReturn(!pfnProgress || VALID_PTR(pfnProgress), ("pfnProgress=%p\n", pfnProgress), VERR_INVALID_PARAMETER);
435 AssertMsgReturn(!(fFlags & ~RTFILECOMP_FLAGS_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
436
437 /*
438 * Open the files.
439 */
440 RTFILE hFile1;
441 int rc = RTFileOpen(&hFile1, pszFile1,
442 RTFILE_O_READ | RTFILE_O_OPEN
443 | (fFlags & RTFILECOMP_FLAGS_NO_DENY_WRITE_FILE1 ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE));
444 if (RT_SUCCESS(rc))
445 {
446 RTFILE hFile2;
447 rc = RTFileOpen(&hFile2, pszFile2,
448 RTFILE_O_READ | RTFILE_O_OPEN
449 | (fFlags & RTFILECOMP_FLAGS_NO_DENY_WRITE_FILE2 ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE));
450 if (RT_SUCCESS(rc))
451 {
452 /*
453 * Call the ByHandles version and let it do the job.
454 */
455 rc = RTFileCompareByHandlesEx(hFile1, hFile2, fFlags, pfnProgress, pvUser);
456
457 /* Clean up */
458 int rc2 = RTFileClose(hFile2);
459 AssertRC(rc2);
460 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
461 rc = rc2;
462 }
463
464 int rc2 = RTFileClose(hFile1);
465 AssertRC(rc2);
466 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
467 rc = rc2;
468 }
469 return rc;
470}
471
472
473RTDECL(int) RTFileCompareByHandlesEx(RTFILE hFile1, RTFILE hFile2, uint32_t fFlags, PFNRTPROGRESS pfnProgress, void *pvUser)
474{
475 /*
476 * Validate input.
477 */
478 AssertReturn(RTFileIsValid(hFile1), VERR_INVALID_HANDLE);
479 AssertReturn(RTFileIsValid(hFile1), VERR_INVALID_HANDLE);
480 AssertMsgReturn(!pfnProgress || VALID_PTR(pfnProgress), ("pfnProgress=%p\n", pfnProgress), VERR_INVALID_PARAMETER);
481 AssertMsgReturn(!(fFlags & ~RTFILECOMP_FLAGS_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
482
483 /*
484 * Compare the file sizes first.
485 */
486 uint64_t cbFile1;
487 int rc = RTFileGetSize(hFile1, &cbFile1);
488 if (RT_FAILURE(rc))
489 return rc;
490
491 uint64_t cbFile2;
492 rc = RTFileGetSize(hFile1, &cbFile2);
493 if (RT_FAILURE(rc))
494 return rc;
495
496 if (cbFile1 != cbFile2)
497 return VERR_NOT_EQUAL;
498
499
500 /*
501 * Allocate buffer.
502 */
503 size_t cbBuf;
504 uint8_t *pbBuf1Free = NULL;
505 uint8_t *pbBuf1;
506 uint8_t *pbBuf2Free = NULL;
507 uint8_t *pbBuf2;
508 if (cbFile1 < _512K)
509 {
510 cbBuf = 8*_1K;
511 pbBuf1 = (uint8_t *)alloca(cbBuf);
512 pbBuf2 = (uint8_t *)alloca(cbBuf);
513 }
514 else
515 {
516 cbBuf = _128K;
517 pbBuf1 = pbBuf1Free = (uint8_t *)RTMemTmpAlloc(cbBuf);
518 pbBuf2 = pbBuf2Free = (uint8_t *)RTMemTmpAlloc(cbBuf);
519 }
520 if (pbBuf1 && pbBuf2)
521 {
522 /*
523 * Seek to the start of each file
524 * and set the size of the destination file.
525 */
526 rc = RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL);
527 if (RT_SUCCESS(rc))
528 {
529 rc = RTFileSeek(hFile2, 0, RTFILE_SEEK_BEGIN, NULL);
530 if (RT_SUCCESS(rc) && pfnProgress)
531 rc = pfnProgress(0, pvUser);
532 if (RT_SUCCESS(rc))
533 {
534 /*
535 * Compare loop.
536 */
537 unsigned uPercentage = 0;
538 RTFOFF off = 0;
539 RTFOFF cbPercent = cbFile1 / 100;
540 RTFOFF offNextPercent = cbPercent;
541 while (off < (RTFOFF)cbFile1)
542 {
543 /* read the blocks */
544 RTFOFF cbLeft = cbFile1 - off;
545 size_t cbBlock = cbLeft >= (RTFOFF)cbBuf ? cbBuf : (size_t)cbLeft;
546 rc = RTFileRead(hFile1, pbBuf1, cbBlock, NULL);
547 if (RT_FAILURE(rc))
548 break;
549 rc = RTFileRead(hFile2, pbBuf2, cbBlock, NULL);
550 if (RT_FAILURE(rc))
551 break;
552
553 /* compare */
554 if (memcmp(pbBuf1, pbBuf2, cbBlock))
555 {
556 rc = VERR_NOT_EQUAL;
557 break;
558 }
559
560 /* advance */
561 off += cbBlock;
562 if (pfnProgress && offNextPercent < off)
563 {
564 while (offNextPercent < off)
565 {
566 uPercentage++;
567 offNextPercent += cbPercent;
568 }
569 rc = pfnProgress(uPercentage, pvUser);
570 if (RT_FAILURE(rc))
571 break;
572 }
573 }
574
575#if 0
576 /*
577 * Compare OS specific data (EAs and stuff).
578 */
579 if (RT_SUCCESS(rc))
580 rc = rtFileCompareOSStuff(hFile1, hFile2);
581#endif
582
583 /* 100% */
584 if (pfnProgress && uPercentage < 100 && RT_SUCCESS(rc))
585 rc = pfnProgress(100, pvUser);
586 }
587 }
588 }
589 else
590 rc = VERR_NO_MEMORY;
591 RTMemTmpFree(pbBuf2Free);
592 RTMemTmpFree(pbBuf1Free);
593
594 return rc;
595}
596
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