VirtualBox

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

Last change on this file was 100908, checked in by vboxsync, 9 months ago

IPRT,Storage,Puel: Changed the pfnRead and pfnWrite VFS methods and the RTVfsIoStrmSgRead, RTVfsIoStrmSgWrite, RTVfsFileSgRead and RTVfsFileSgWrite APIs to advance pSgBuf and respect the incoming position just like RTFileSgRead & RTFileSgWrite.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use