VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/vfs/vfsprogress.cpp@ 94291

最後變更 在這個檔案從94291是 94291,由 vboxsync 提交於 3 年 前

IPRT,Storage: Adding RTVfsQueryLabel and internally a generic pfnQueryInfoEx method to the RTVFSOBJOPS function table. Untested implementation of the latter for iso/udf. bugref:9781

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 17.5 KB
 
1/* $Id: vfsprogress.cpp 94291 2022-03-17 13:29:52Z vboxsync $ */
2/** @file
3 * IPRT - Virtual File System, progress filter for files.
4 */
5
6/*
7 * Copyright (C) 2010-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.alldomusa.eu.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 <iprt/vfs.h>
32#include <iprt/vfslowlevel.h>
33
34#include <iprt/assert.h>
35#include <iprt/errcore.h>
36#include <iprt/file.h>
37#include <iprt/poll.h>
38#include <iprt/string.h>
39#include <iprt/thread.h>
40
41
42/*********************************************************************************************************************************
43* Structures and Typedefs *
44*********************************************************************************************************************************/
45/**
46 * Private data of a standard file.
47 */
48typedef struct RTVFSPROGRESSFILE
49{
50 /** This is negative (RT_FAILURE) if canceled. */
51 int rcCanceled;
52 /** RTVFSPROGRESS_F_XXX. */
53 uint32_t fFlags;
54 /** Progress callback. */
55 PFNRTPROGRESS pfnProgress;
56 /** User argument for the callback. */
57 void *pvUser;
58 /** The I/O stream handle. */
59 RTVFSIOSTREAM hVfsIos;
60 /** The file handle. NIL_RTFILE if a pure I/O stream. */
61 RTVFSFILE hVfsFile;
62 /** Total number of bytes expected to be read and written. */
63 uint64_t cbExpected;
64 /** The number of bytes expected to be read. */
65 uint64_t cbExpectedRead;
66 /** The number of bytes expected to be written. */
67 uint64_t cbExpectedWritten;
68 /** Number of bytes currently read. */
69 uint64_t cbCurrentlyRead;
70 /** Number of bytes currently written. */
71 uint64_t cbCurrentlyWritten;
72 /** Current precentage. */
73 unsigned uCurPct;
74} RTVFSPROGRESSFILE;
75/** Pointer to the private data of a standard file. */
76typedef RTVFSPROGRESSFILE *PRTVFSPROGRESSFILE;
77
78
79/**
80 * Update the progress and do the progress callback if necessary.
81 *
82 * @returns Callback return code.
83 * @param pThis The file progress instance.
84 */
85static int rtVfsProgressFile_UpdateProgress(PRTVFSPROGRESSFILE pThis)
86{
87 uint64_t cbDone = RT_MIN(pThis->cbCurrentlyRead, pThis->cbExpectedRead)
88 + RT_MIN(pThis->cbCurrentlyWritten, pThis->cbExpectedWritten);
89 unsigned uPct = cbDone * 100 / pThis->cbExpected;
90 if (uPct == pThis->uCurPct)
91 return pThis->rcCanceled;
92 pThis->uCurPct = uPct;
93
94 int rc = pThis->pfnProgress(uPct, pThis->pvUser);
95 if (!(pThis->fFlags & RTVFSPROGRESS_F_CANCELABLE))
96 rc = VINF_SUCCESS;
97 else if (RT_FAILURE(rc) && RT_SUCCESS(pThis->rcCanceled))
98 pThis->rcCanceled = rc;
99
100 return rc;
101}
102
103
104/**
105 * @interface_method_impl{RTVFSOBJOPS,pfnClose}
106 */
107static DECLCALLBACK(int) rtVfsProgressFile_Close(void *pvThis)
108{
109 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
110
111 if (pThis->hVfsFile != NIL_RTVFSFILE)
112 {
113 RTVfsFileRelease(pThis->hVfsFile);
114 pThis->hVfsFile = NIL_RTVFSFILE;
115 }
116 RTVfsIoStrmRelease(pThis->hVfsIos);
117 pThis->hVfsIos = NIL_RTVFSIOSTREAM;
118
119 pThis->pfnProgress = NULL;
120
121 return VINF_SUCCESS;
122}
123
124
125/**
126 * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
127 */
128static DECLCALLBACK(int) rtVfsProgressFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
129{
130 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
131 int rc = pThis->rcCanceled;
132 if (RT_SUCCESS(rc))
133 rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
134 return rc;
135}
136
137
138/**
139 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
140 */
141static DECLCALLBACK(int) rtVfsProgressFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
142{
143 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
144
145 int rc = pThis->rcCanceled;
146 if (RT_SUCCESS(rc))
147 {
148 /* Simplify a little there if a seeks is implied and assume the read goes well. */
149 if ( off >= 0
150 && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ))
151 {
152 uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile);
153 if (offCurrent < (uint64_t)off)
154 pThis->cbCurrentlyRead += off - offCurrent;
155 }
156
157 /* Calc the request before calling down the stack. */
158 size_t cbReq = 0;
159 unsigned i = pSgBuf->cSegs;
160 while (i-- > 0)
161 cbReq += pSgBuf->paSegs[i].cbSeg;
162
163 /* Do the read. */
164 rc = RTVfsIoStrmSgRead(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbRead);
165 if (RT_SUCCESS(rc))
166 {
167 /* Update the progress (we cannot cancel here, sorry). */
168 pThis->cbCurrentlyRead += pcbRead ? *pcbRead : cbReq;
169 rtVfsProgressFile_UpdateProgress(pThis);
170 }
171 }
172
173 return rc;
174}
175
176
177/**
178 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
179 */
180static DECLCALLBACK(int) rtVfsProgressFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
181{
182 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
183
184 int rc = pThis->rcCanceled;
185 if (RT_SUCCESS(rc))
186 {
187 /* Simplify a little there if a seeks is implied and assume the write goes well. */
188 if ( off >= 0
189 && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
190 {
191 uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile);
192 if (offCurrent < (uint64_t)off)
193 pThis->cbCurrentlyWritten += off - offCurrent;
194 }
195
196 /* Calc the request before calling down the stack. */
197 size_t cbReq = 0;
198 unsigned i = pSgBuf->cSegs;
199 while (i-- > 0)
200 cbReq += pSgBuf->paSegs[i].cbSeg;
201
202 /* Do the read. */
203 rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbWritten);
204 if (RT_SUCCESS(rc))
205 {
206 /* Update the progress (we cannot cancel here, sorry). */
207 pThis->cbCurrentlyWritten += pcbWritten ? *pcbWritten : cbReq;
208 rtVfsProgressFile_UpdateProgress(pThis);
209 }
210 }
211
212 return rc;
213}
214
215
216/**
217 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
218 */
219static DECLCALLBACK(int) rtVfsProgressFile_Flush(void *pvThis)
220{
221 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
222 int rc = pThis->rcCanceled;
223 if (RT_SUCCESS(rc))
224 rc = RTVfsIoStrmFlush(pThis->hVfsIos);
225 return rc;
226}
227
228
229/**
230 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
231 */
232static DECLCALLBACK(int)
233rtVfsProgressFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
234{
235 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
236 int rc = pThis->rcCanceled;
237 if (RT_SUCCESS(rc))
238 rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
239 else
240 {
241 *pfRetEvents |= RTPOLL_EVT_ERROR;
242 rc = VINF_SUCCESS;
243 }
244 return rc;
245}
246
247
248/**
249 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
250 */
251static DECLCALLBACK(int) rtVfsProgressFile_Tell(void *pvThis, PRTFOFF poffActual)
252{
253 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
254 *poffActual = RTVfsIoStrmTell(pThis->hVfsIos);
255 return *poffActual >= 0 ? VINF_SUCCESS : (int)*poffActual;
256}
257
258
259/**
260 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
261 */
262static DECLCALLBACK(int) rtVfsProgressFile_Skip(void *pvThis, RTFOFF cb)
263{
264 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
265 int rc = pThis->rcCanceled;
266 if (RT_SUCCESS(rc))
267 {
268 rc = RTVfsIoStrmSkip(pThis->hVfsIos, cb);
269 if (RT_SUCCESS(rc))
270 {
271 pThis->cbCurrentlyRead += cb;
272 rtVfsProgressFile_UpdateProgress(pThis);
273 }
274 }
275 return rc;
276}
277
278
279/**
280 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnZeroFill}
281 */
282static DECLCALLBACK(int) rtVfsProgressFile_ZeroFill(void *pvThis, RTFOFF cb)
283{
284 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
285 int rc = pThis->rcCanceled;
286 if (RT_SUCCESS(rc))
287 {
288 rc = RTVfsIoStrmZeroFill(pThis->hVfsIos, cb);
289 if (RT_SUCCESS(rc))
290 {
291 pThis->cbCurrentlyWritten += cb;
292 rtVfsProgressFile_UpdateProgress(pThis);
293 }
294 }
295 return rc;
296}
297
298
299/**
300 * I/O stream progress operations.
301 */
302DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtVfsProgressIosOps =
303{
304 { /* Obj */
305 RTVFSOBJOPS_VERSION,
306 RTVFSOBJTYPE_IO_STREAM,
307 "I/O Stream Progress",
308 rtVfsProgressFile_Close,
309 rtVfsProgressFile_QueryInfo,
310 NULL,
311 RTVFSOBJOPS_VERSION
312 },
313 RTVFSIOSTREAMOPS_VERSION,
314 0,
315 rtVfsProgressFile_Read,
316 rtVfsProgressFile_Write,
317 rtVfsProgressFile_Flush,
318 rtVfsProgressFile_PollOne,
319 rtVfsProgressFile_Tell,
320 rtVfsProgressFile_Skip,
321 rtVfsProgressFile_ZeroFill,
322 RTVFSIOSTREAMOPS_VERSION,
323};
324
325
326
327/**
328 * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
329 */
330static DECLCALLBACK(int) rtVfsProgressFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
331{
332 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
333 //return RTVfsFileSetMode(pThis->hVfsIos, fMode, fMask); - missing
334 RT_NOREF(pThis, fMode, fMask);
335 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
336}
337
338
339/**
340 * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
341 */
342static DECLCALLBACK(int) rtVfsProgressFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
343 PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
344{
345 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
346 //return RTVfsFileSetTimes(pThis->hVfsIos, pAccessTime, pModificationTime, pChangeTime, pBirthTime); - missing
347 RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
348 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
349}
350
351
352/**
353 * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
354 */
355static DECLCALLBACK(int) rtVfsProgressFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
356{
357 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
358 //return RTVfsFileSetOwern(pThis->hVfsIos, uid, gid); - missing
359 RT_NOREF(pThis, uid, gid);
360 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
361}
362
363
364/**
365 * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
366 */
367static DECLCALLBACK(int) rtVfsProgressFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
368{
369 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
370
371 uint64_t offPrev = UINT64_MAX;
372 if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
373 offPrev = RTVfsFileTell(pThis->hVfsFile);
374
375 uint64_t offActual = 0;
376 int rc = RTVfsFileSeek(pThis->hVfsFile, offSeek, uMethod, &offActual);
377 if (RT_SUCCESS(rc))
378 {
379 if (poffActual)
380 *poffActual = offActual;
381
382 /* Do progress updates as requested. */
383 if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
384 {
385 if (offActual > offPrev)
386 {
387 if (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ)
388 pThis->cbCurrentlyRead += offActual - offPrev;
389 else
390 pThis->cbCurrentlyWritten += offActual - offPrev;
391 rtVfsProgressFile_UpdateProgress(pThis);
392 }
393 }
394 }
395 return rc;
396}
397
398
399/**
400 * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
401 */
402static DECLCALLBACK(int) rtVfsProgressFile_QuerySize(void *pvThis, uint64_t *pcbFile)
403{
404 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
405 return RTVfsFileQuerySize(pThis->hVfsFile, pcbFile);
406}
407
408
409/**
410 * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
411 */
412static DECLCALLBACK(int) rtVfsProgressFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
413{
414 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
415 return RTVfsFileSetSize(pThis->hVfsFile, cbFile, fFlags);
416}
417
418
419/**
420 * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
421 */
422static DECLCALLBACK(int) rtVfsProgressFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
423{
424 PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
425 return RTVfsFileQueryMaxSize(pThis->hVfsFile, pcbMax);
426}
427
428
429
430/**
431 * File progress operations.
432 */
433DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsProgressFileOps =
434{
435 { /* Stream */
436 { /* Obj */
437 RTVFSOBJOPS_VERSION,
438 RTVFSOBJTYPE_FILE,
439 "File Progress",
440 rtVfsProgressFile_Close,
441 rtVfsProgressFile_QueryInfo,
442 NULL,
443 RTVFSOBJOPS_VERSION
444 },
445 RTVFSIOSTREAMOPS_VERSION,
446 0,
447 rtVfsProgressFile_Read,
448 rtVfsProgressFile_Write,
449 rtVfsProgressFile_Flush,
450 rtVfsProgressFile_PollOne,
451 rtVfsProgressFile_Tell,
452 rtVfsProgressFile_Skip,
453 rtVfsProgressFile_ZeroFill,
454 RTVFSIOSTREAMOPS_VERSION,
455 },
456 RTVFSFILEOPS_VERSION,
457 0,
458 { /* ObjSet */
459 RTVFSOBJSETOPS_VERSION,
460 RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
461 rtVfsProgressFile_SetMode,
462 rtVfsProgressFile_SetTimes,
463 rtVfsProgressFile_SetOwner,
464 RTVFSOBJSETOPS_VERSION
465 },
466 rtVfsProgressFile_Seek,
467 rtVfsProgressFile_QuerySize,
468 rtVfsProgressFile_SetSize,
469 rtVfsProgressFile_QueryMaxSize,
470 RTVFSFILEOPS_VERSION
471};
472
473
474RTDECL(int) RTVfsCreateProgressForIoStream(RTVFSIOSTREAM hVfsIos, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags,
475 uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSIOSTREAM phVfsIos)
476{
477 AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER);
478 AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS);
479 AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE),
480 VERR_INVALID_FLAGS);
481
482 uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos);
483 AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
484
485 PRTVFSPROGRESSFILE pThis;
486 int rc = RTVfsNewIoStream(&g_rtVfsProgressIosOps, sizeof(*pThis), RTVfsIoStrmGetOpenFlags(hVfsIos),
487 NIL_RTVFS, NIL_RTVFSLOCK, phVfsIos, (void **)&pThis);
488 if (RT_SUCCESS(rc))
489 {
490 pThis->rcCanceled = VINF_SUCCESS;
491 pThis->fFlags = fFlags;
492 pThis->pfnProgress = pfnProgress;
493 pThis->pvUser = pvUser;
494 pThis->hVfsIos = hVfsIos;
495 pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIos);
496 pThis->cbCurrentlyRead = 0;
497 pThis->cbCurrentlyWritten = 0;
498 pThis->cbExpectedRead = cbExpectedRead;
499 pThis->cbExpectedWritten = cbExpectedWritten;
500 pThis->cbExpected = cbExpectedRead + cbExpectedWritten;
501 if (!pThis->cbExpected)
502 pThis->cbExpected = 1;
503 pThis->uCurPct = 0;
504 }
505 return rc;
506}
507
508
509RTDECL(int) RTVfsCreateProgressForFile(RTVFSFILE hVfsFile, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags,
510 uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSFILE phVfsFile)
511{
512 AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER);
513 AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS);
514 AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE),
515 VERR_INVALID_FLAGS);
516
517 uint32_t cRefs = RTVfsFileRetain(hVfsFile);
518 AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
519
520 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
521 AssertReturnStmt(hVfsIos != NIL_RTVFSIOSTREAM, RTVfsFileRelease(hVfsFile), VERR_INVALID_HANDLE);
522
523 PRTVFSPROGRESSFILE pThis;
524 int rc = RTVfsNewFile(&g_rtVfsProgressFileOps, sizeof(*pThis), RTVfsFileGetOpenFlags(hVfsFile),
525 NIL_RTVFS, NIL_RTVFSLOCK, phVfsFile, (void **)&pThis);
526 if (RT_SUCCESS(rc))
527 {
528 pThis->fFlags = fFlags;
529 pThis->pfnProgress = pfnProgress;
530 pThis->pvUser = pvUser;
531 pThis->hVfsIos = hVfsIos;
532 pThis->hVfsFile = hVfsFile;
533 pThis->cbCurrentlyRead = 0;
534 pThis->cbCurrentlyWritten = 0;
535 pThis->cbExpectedRead = cbExpectedRead;
536 pThis->cbExpectedWritten = cbExpectedWritten;
537 pThis->cbExpected = cbExpectedRead + cbExpectedWritten;
538 if (!pThis->cbExpected)
539 pThis->cbExpected = 1;
540 pThis->uCurPct = 0;
541 }
542 return rc;
543}
544
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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