/* $Id: vfsprogress.cpp $ */ /** @file * IPRT - Virtual File System, progress filter for files. */ /* * Copyright (C) 2010-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Private data of a standard file. */ typedef struct RTVFSPROGRESSFILE { /** This is negative (RT_FAILURE) if canceled. */ int rcCanceled; /** RTVFSPROGRESS_F_XXX. */ uint32_t fFlags; /** Progress callback. */ PFNRTPROGRESS pfnProgress; /** User argument for the callback. */ void *pvUser; /** The I/O stream handle. */ RTVFSIOSTREAM hVfsIos; /** The file handle. NIL_RTFILE if a pure I/O stream. */ RTVFSFILE hVfsFile; /** Total number of bytes expected to be read and written. */ uint64_t cbExpected; /** The number of bytes expected to be read. */ uint64_t cbExpectedRead; /** The number of bytes expected to be written. */ uint64_t cbExpectedWritten; /** Number of bytes currently read. */ uint64_t cbCurrentlyRead; /** Number of bytes currently written. */ uint64_t cbCurrentlyWritten; /** Current precentage. */ unsigned uCurPct; } RTVFSPROGRESSFILE; /** Pointer to the private data of a standard file. */ typedef RTVFSPROGRESSFILE *PRTVFSPROGRESSFILE; /** * Update the progress and do the progress callback if necessary. * * @returns Callback return code. * @param pThis The file progress instance. */ static int rtVfsProgressFile_UpdateProgress(PRTVFSPROGRESSFILE pThis) { uint64_t cbDone = RT_MIN(pThis->cbCurrentlyRead, pThis->cbExpectedRead) + RT_MIN(pThis->cbCurrentlyWritten, pThis->cbExpectedWritten); unsigned uPct = cbDone * 100 / pThis->cbExpected; if (uPct == pThis->uCurPct) return pThis->rcCanceled; pThis->uCurPct = uPct; int rc = pThis->pfnProgress(uPct, pThis->pvUser); if (!(pThis->fFlags & RTVFSPROGRESS_F_CANCELABLE)) rc = VINF_SUCCESS; else if (RT_FAILURE(rc) && RT_SUCCESS(pThis->rcCanceled)) pThis->rcCanceled = rc; return rc; } /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtVfsProgressFile_Close(void *pvThis) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; if (pThis->hVfsFile != NIL_RTVFSFILE) { RTVfsFileRelease(pThis->hVfsFile); pThis->hVfsFile = NIL_RTVFSFILE; } RTVfsIoStrmRelease(pThis->hVfsIos); pThis->hVfsIos = NIL_RTVFSIOSTREAM; pThis->pfnProgress = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtVfsProgressFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr); return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtVfsProgressFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) { /* Simplify a little there if a seeks is implied and assume the read goes well. */ if ( off >= 0 && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ)) { uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile); if (offCurrent < (uint64_t)off) pThis->cbCurrentlyRead += off - offCurrent; } /* Calc the request before calling down the stack. */ size_t cbReq = 0; unsigned i = pSgBuf->cSegs; while (i-- > 0) cbReq += pSgBuf->paSegs[i].cbSeg; /* Do the read. */ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbRead); if (RT_SUCCESS(rc)) { /* Update the progress (we cannot cancel here, sorry). */ pThis->cbCurrentlyRead += pcbRead ? *pcbRead : cbReq; rtVfsProgressFile_UpdateProgress(pThis); } } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} */ static DECLCALLBACK(int) rtVfsProgressFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) { /* Simplify a little there if a seeks is implied and assume the write goes well. */ if ( off >= 0 && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE)) { uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile); if (offCurrent < (uint64_t)off) pThis->cbCurrentlyWritten += off - offCurrent; } /* Calc the request before calling down the stack. */ size_t cbReq = 0; unsigned i = pSgBuf->cSegs; while (i-- > 0) cbReq += pSgBuf->paSegs[i].cbSeg; /* Do the read. */ rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbWritten); if (RT_SUCCESS(rc)) { /* Update the progress (we cannot cancel here, sorry). */ pThis->cbCurrentlyWritten += pcbWritten ? *pcbWritten : cbReq; rtVfsProgressFile_UpdateProgress(pThis); } } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtVfsProgressFile_Flush(void *pvThis) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) rc = RTVfsIoStrmFlush(pThis->hVfsIos); return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} */ static DECLCALLBACK(int) rtVfsProgressFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents); else { *pfRetEvents |= RTPOLL_EVT_ERROR; rc = VINF_SUCCESS; } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtVfsProgressFile_Tell(void *pvThis, PRTFOFF poffActual) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; *poffActual = RTVfsIoStrmTell(pThis->hVfsIos); return *poffActual >= 0 ? VINF_SUCCESS : (int)*poffActual; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip} */ static DECLCALLBACK(int) rtVfsProgressFile_Skip(void *pvThis, RTFOFF cb) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) { rc = RTVfsIoStrmSkip(pThis->hVfsIos, cb); if (RT_SUCCESS(rc)) { pThis->cbCurrentlyRead += cb; rtVfsProgressFile_UpdateProgress(pThis); } } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnZeroFill} */ static DECLCALLBACK(int) rtVfsProgressFile_ZeroFill(void *pvThis, RTFOFF cb) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; int rc = pThis->rcCanceled; if (RT_SUCCESS(rc)) { rc = RTVfsIoStrmZeroFill(pThis->hVfsIos, cb); if (RT_SUCCESS(rc)) { pThis->cbCurrentlyWritten += cb; rtVfsProgressFile_UpdateProgress(pThis); } } return rc; } /** * I/O stream progress operations. */ DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtVfsProgressIosOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_IO_STREAM, "I/O Stream Progress", rtVfsProgressFile_Close, rtVfsProgressFile_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, 0, rtVfsProgressFile_Read, rtVfsProgressFile_Write, rtVfsProgressFile_Flush, rtVfsProgressFile_PollOne, rtVfsProgressFile_Tell, rtVfsProgressFile_Skip, rtVfsProgressFile_ZeroFill, RTVFSIOSTREAMOPS_VERSION, }; /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtVfsProgressFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; //return RTVfsFileSetMode(pThis->hVfsIos, fMode, fMask); - missing RT_NOREF(pThis, fMode, fMask); AssertFailedReturn(VERR_NOT_IMPLEMENTED); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtVfsProgressFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; //return RTVfsFileSetTimes(pThis->hVfsIos, pAccessTime, pModificationTime, pChangeTime, pBirthTime); - missing RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime); AssertFailedReturn(VERR_NOT_IMPLEMENTED); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtVfsProgressFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; //return RTVfsFileSetOwern(pThis->hVfsIos, uid, gid); - missing RT_NOREF(pThis, uid, gid); AssertFailedReturn(VERR_NOT_IMPLEMENTED); } /** * @interface_method_impl{RTVFSFILEOPS,pfnSeek} */ static DECLCALLBACK(int) rtVfsProgressFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; uint64_t offPrev = UINT64_MAX; if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE)) offPrev = RTVfsFileTell(pThis->hVfsFile); uint64_t offActual = 0; int rc = RTVfsFileSeek(pThis->hVfsFile, offSeek, uMethod, &offActual); if (RT_SUCCESS(rc)) { if (poffActual) *poffActual = offActual; /* Do progress updates as requested. */ if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE)) { if (offActual > offPrev) { if (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) pThis->cbCurrentlyRead += offActual - offPrev; else pThis->cbCurrentlyWritten += offActual - offPrev; rtVfsProgressFile_UpdateProgress(pThis); } } } return rc; } /** * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} */ static DECLCALLBACK(int) rtVfsProgressFile_QuerySize(void *pvThis, uint64_t *pcbFile) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; return RTVfsFileQuerySize(pThis->hVfsFile, pcbFile); } /** * @interface_method_impl{RTVFSFILEOPS,pfnSetSize} */ static DECLCALLBACK(int) rtVfsProgressFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; return RTVfsFileSetSize(pThis->hVfsFile, cbFile, fFlags); } /** * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize} */ static DECLCALLBACK(int) rtVfsProgressFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax) { PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis; return RTVfsFileQueryMaxSize(pThis->hVfsFile, pcbMax); } /** * File progress operations. */ DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsProgressFileOps = { { /* Stream */ { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FILE, "File Progress", rtVfsProgressFile_Close, rtVfsProgressFile_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, 0, rtVfsProgressFile_Read, rtVfsProgressFile_Write, rtVfsProgressFile_Flush, rtVfsProgressFile_PollOne, rtVfsProgressFile_Tell, rtVfsProgressFile_Skip, rtVfsProgressFile_ZeroFill, RTVFSIOSTREAMOPS_VERSION, }, RTVFSFILEOPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj), rtVfsProgressFile_SetMode, rtVfsProgressFile_SetTimes, rtVfsProgressFile_SetOwner, RTVFSOBJSETOPS_VERSION }, rtVfsProgressFile_Seek, rtVfsProgressFile_QuerySize, rtVfsProgressFile_SetSize, rtVfsProgressFile_QueryMaxSize, RTVFSFILEOPS_VERSION }; RTDECL(int) RTVfsCreateProgressForIoStream(RTVFSIOSTREAM hVfsIos, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags, uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSIOSTREAM phVfsIos) { AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER); AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS); AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE), VERR_INVALID_FLAGS); uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos); AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); PRTVFSPROGRESSFILE pThis; int rc = RTVfsNewIoStream(&g_rtVfsProgressIosOps, sizeof(*pThis), RTVfsIoStrmGetOpenFlags(hVfsIos), NIL_RTVFS, NIL_RTVFSLOCK, phVfsIos, (void **)&pThis); if (RT_SUCCESS(rc)) { pThis->rcCanceled = VINF_SUCCESS; pThis->fFlags = fFlags; pThis->pfnProgress = pfnProgress; pThis->pvUser = pvUser; pThis->hVfsIos = hVfsIos; pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIos); pThis->cbCurrentlyRead = 0; pThis->cbCurrentlyWritten = 0; pThis->cbExpectedRead = cbExpectedRead; pThis->cbExpectedWritten = cbExpectedWritten; pThis->cbExpected = cbExpectedRead + cbExpectedWritten; if (!pThis->cbExpected) pThis->cbExpected = 1; pThis->uCurPct = 0; } return rc; } RTDECL(int) RTVfsCreateProgressForFile(RTVFSFILE hVfsFile, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags, uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSFILE phVfsFile) { AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER); AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS); AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE), VERR_INVALID_FLAGS); uint32_t cRefs = RTVfsFileRetain(hVfsFile); AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile); AssertReturnStmt(hVfsIos != NIL_RTVFSIOSTREAM, RTVfsFileRelease(hVfsFile), VERR_INVALID_HANDLE); PRTVFSPROGRESSFILE pThis; int rc = RTVfsNewFile(&g_rtVfsProgressFileOps, sizeof(*pThis), RTVfsFileGetOpenFlags(hVfsFile), NIL_RTVFS, NIL_RTVFSLOCK, phVfsFile, (void **)&pThis); if (RT_SUCCESS(rc)) { pThis->fFlags = fFlags; pThis->pfnProgress = pfnProgress; pThis->pvUser = pvUser; pThis->hVfsIos = hVfsIos; pThis->hVfsFile = hVfsFile; pThis->cbCurrentlyRead = 0; pThis->cbCurrentlyWritten = 0; pThis->cbExpectedRead = cbExpectedRead; pThis->cbExpectedWritten = cbExpectedWritten; pThis->cbExpected = cbExpectedRead + cbExpectedWritten; if (!pThis->cbExpected) pThis->cbExpected = 1; pThis->uCurPct = 0; } return rc; }