/* $Id: vfsmemory.cpp 36555 2011-04-05 12:34:09Z vboxsync $ */ /** @file * IPRT - Virtual File System, Memory Backed VFS. */ /* * Copyright (C) 2010 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE 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. */ /******************************************************************************* * Header Files * *******************************************************************************/ #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include /******************************************************************************* * Header Files * *******************************************************************************/ #include "internal/iprt.h" #include #include #include /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** The max extent size. */ #define RTVFSMEM_MAX_EXTENT_SIZE _2M /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** * Memory base object info. */ typedef struct RTVFSMEMBASE { /** The basic object info. */ RTFSOBJINFO ObjInfo; } RTVFSMEMBASE; /** * Memory file extent. * * This stores part of the file content. */ typedef struct RTVFSMEMEXTENT { /** Extent list entry. */ RTLISTNODE Entry; /** The offset of this extent within the file. */ uint64_t off; /** The size of the this extent. */ uint32_t cb; /** The data. */ uint8_t abData[1]; } RTVFSMEMEXTENT; /** Pointer to a memory file extent. */ typedef RTVFSMEMEXTENT *PRTVFSMEMEXTENT; /** * Memory file. */ typedef struct RTVFSMEMFILE { /** The base info. */ RTVFSMEMBASE Base; /** The current file position. */ uint64_t offCurPos; /** Pointer to the current file extent. */ PRTVFSMEMEXTENT pCurExt; /** Linked list of file extents - RTVFSMEMEXTENT. */ RTLISTNODE ExtentHead; /** The current extent size. * This is slowly grown to RTVFSMEM_MAX_EXTENT_SIZE as the file grows. */ uint32_t cbExtent; } RTVFSMEMFILE; /** Pointer to a memory file. */ typedef RTVFSMEMFILE *PRTVFSMEMFILE; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtVfsMemFile_Close(void *pvThis) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; /* * Free the extent list. */ PRTVFSMEMEXTENT pCur, pNext; RTListForEachSafe(&pThis->ExtentHead, pCur, pNext, RTVFSMEMEXTENT, Entry) { pCur->off = RTFOFF_MAX; pCur->cb = UINT32_MAX; RTListNodeRemove(&pCur->Entry); RTMemFree(pCur); } pThis->pCurExt = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtVfsMemFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; switch (enmAddAttr) { case RTFSOBJATTRADD_NOTHING: case RTFSOBJATTRADD_UNIX: *pObjInfo = pThis->Base.ObjInfo; return VINF_SUCCESS; default: return VERR_NOT_SUPPORTED; } } /** * The slow paths of rtVfsMemFile_LocateExtent. * * @copydoc rtVfsMemFile_LocateExtent */ static PRTVFSMEMEXTENT rtVfsMemFile_LocateExtentSlow(PRTVFSMEMFILE pThis, uint64_t off, bool *pfHit) { /* * Search from the start or the previously used extent. The heuristics * are very very simple, but whatever. */ PRTVFSMEMEXTENT pExtent = pThis->pCurExt; if (!pExtent || pExtent->off < off) { pExtent = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry); if (!pExtent) { *pfHit = false; return NULL; } } while (off - pExtent->off >= pExtent->cb) { Assert(pExtent->off <= off); PRTVFSMEMEXTENT pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry); if ( !pNext || pNext->off > off) { *pfHit = false; return pExtent; } pExtent = pNext; } *pfHit = true; pThis->pCurExt = pExtent; return pExtent; } /** * Locates the extent covering the specified offset, or then one before it. * * @returns The closest extent. NULL if off is 0 and there are no extent * covering byte 0 yet. * @param pThis The memory file. * @param off The offset (0-positive). * @param pfHit Where to indicate whether the extent is a * direct hit (@c true) or just a closest match * (@c false). */ DECLINLINE(PRTVFSMEMEXTENT) rtVfsMemFile_LocateExtent(PRTVFSMEMFILE pThis, uint64_t off, bool *pfHit) { /* * The most likely case is that we're hitting the extent we used in the * previous access or the one immediately following it. */ PRTVFSMEMEXTENT pExtent = pThis->pCurExt; if (!pExtent) return rtVfsMemFile_LocateExtentSlow(pThis, off, pfHit); if (off - pExtent->off >= pExtent->cb) { pExtent = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry); if ( !pExtent || off - pExtent->off >= pExtent->cb) return rtVfsMemFile_LocateExtentSlow(pThis, off, pfHit); pThis->pCurExt = pExtent; } *pfHit = true; return pExtent; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtVfsMemFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; Assert(pSgBuf->cSegs == 1); Assert(off < 0); NOREF(fBlocking); /* * Find the current position and check if it's within the file. */ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off; if (offUnsigned >= (uint64_t)pThis->Base.ObjInfo.cbObject) { if (pcbRead) { *pcbRead = 0; pThis->offCurPos = offUnsigned; return VINF_EOF; } return VERR_EOF; } size_t cbLeftToRead; if (offUnsigned + pSgBuf->paSegs[0].cbSeg > (uint64_t)pThis->Base.ObjInfo.cbObject) { if (!pcbRead) return VERR_EOF; *pcbRead = cbLeftToRead = (size_t)((uint64_t)pThis->Base.ObjInfo.cbObject - offUnsigned); } else { cbLeftToRead = pSgBuf->paSegs[0].cbSeg; if (pcbRead) *pcbRead = cbLeftToRead; } /* * Ok, we've got a valid stretch within the file. Do the reading. */ if (cbLeftToRead > 0) { uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg; bool fHit; PRTVFSMEMEXTENT pExtent = rtVfsMemFile_LocateExtent(pThis, offUnsigned, &fHit); for (;;) { PRTVFSMEMEXTENT pNext; size_t cbThisRead; Assert(!pExtent || pExtent->off <= offUnsigned); /* * Do we hit an extent covering the the current file surface? */ if (fHit) { size_t const offExtent = (size_t)(offUnsigned - pExtent->off); cbThisRead = pExtent->cb - offExtent; if (cbThisRead >= cbLeftToRead) cbThisRead = cbLeftToRead; memcpy(pbDst, &pExtent->abData[offUnsigned - pExtent->off], cbThisRead); offUnsigned += cbThisRead; cbLeftToRead -= cbThisRead; if (!cbLeftToRead) break; pbDst += cbThisRead; pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry); if ( pNext && pNext->off == pExtent->off + pExtent->cb) { pExtent = pNext; continue; } fHit = false; } /* * No extent of this portion (sparse file). */ else if (pExtent) pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry); else pNext = NULL; Assert(!pNext || pNext->off > pExtent->off); if ( !pNext || offUnsigned + cbLeftToRead <= pNext->off) cbThisRead = cbLeftToRead; else cbThisRead = (size_t)(pNext->off - offUnsigned); RT_BZERO(pbDst, cbThisRead); offUnsigned += cbThisRead; cbLeftToRead -= cbThisRead; if (!cbLeftToRead) break; pbDst += cbThisRead; /* Go on and read content from the next extent. */ fHit = true; pExtent = pNext; } } pThis->offCurPos = offUnsigned; return VINF_SUCCESS; } /** * Allocates a new extent covering the ground at @a offUnsigned. * * @returns Pointer to the new extent on success, NULL if we're out of memory. * @param pThis The memory file. * @param offUnsigned The location to allocate the extent at. * @param cbToWrite The number of bytes we're interested in writing * starting at @a offUnsigned. * @param pPrev The extention before @a offUnsigned. NULL if * none. */ static PRTVFSMEMEXTENT rtVfsMemFile_AllocExtent(PRTVFSMEMFILE pThis, uint64_t offUnsigned, size_t cbToWrite, PRTVFSMEMEXTENT pPrev) { /* * Adjust the extent size if we haven't reached the max size yet. */ if (pThis->cbExtent != RTVFSMEM_MAX_EXTENT_SIZE) { if (cbToWrite >= RTVFSMEM_MAX_EXTENT_SIZE) pThis->cbExtent = RTVFSMEM_MAX_EXTENT_SIZE; else if (!RTListIsEmpty(&pThis->ExtentHead)) { uint32_t cbNextExtent = pThis->cbExtent; if (RT_IS_POWER_OF_TWO(cbNextExtent)) cbNextExtent *= 2; else { /* Make it a power of two (seeRTVfsMemorizeIoStreamAsFile). */ cbNextExtent = _4K; while (cbNextExtent < pThis->cbExtent) cbNextExtent *= 2; } if (((pThis->Base.ObjInfo.cbAllocated + cbNextExtent) & (cbNextExtent - 1)) == 0) pThis->cbExtent = cbNextExtent; } } /* * Figure out the size and position of the extent we're adding. */ uint64_t offExtent = offUnsigned & ~(uint64_t)(pThis->cbExtent - 1); uint32_t cbExtent = pThis->cbExtent; uint64_t const offPrev = pPrev ? pPrev->off + pPrev->cb : 0; if (offExtent < offPrev) offExtent = offPrev; PRTVFSMEMEXTENT pNext = pPrev ? RTListGetNext(&pThis->ExtentHead, pPrev, RTVFSMEMEXTENT, Entry) : RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry); if (pNext) { uint64_t cbMaxExtent = pNext->off - offExtent; if (cbMaxExtent < cbExtent) cbExtent = (uint32_t)cbMaxExtent; } /* * Allocate, initialize and insert the new extent. */ PRTVFSMEMEXTENT pNew = (PRTVFSMEMEXTENT)RTMemAllocZ(RT_OFFSETOF(RTVFSMEMEXTENT, abData[cbExtent])); if (pNew) { pNew->off = offExtent; pNew->cb = cbExtent; if (pPrev) RTListNodeInsertAfter(&pPrev->Entry, &pNew->Entry); else RTListPrepend(&pThis->ExtentHead, &pNew->Entry); pThis->Base.ObjInfo.cbAllocated += cbExtent; } /** @todo retry with minimum size. */ return pNew; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} */ static DECLCALLBACK(int) rtVfsMemFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; Assert(pSgBuf->cSegs == 1); Assert(off < 0); NOREF(fBlocking); /* * Validate the write and set up the write loop. */ size_t cbLeftToWrite = pSgBuf->paSegs[0].cbSeg; if (!cbLeftToWrite) return VINF_SUCCESS; /* pcbWritten is already 0. */ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off; if (offUnsigned + cbLeftToWrite >= (uint64_t)RTFOFF_MAX) return VERR_OUT_OF_RANGE; int rc = VINF_SUCCESS; uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg; bool fHit; PRTVFSMEMEXTENT pExtent = rtVfsMemFile_LocateExtent(pThis, offUnsigned, &fHit); for (;;) { /* * If we didn't hit an extent, allocate one (unless it's all zeros). */ if (!fHit) { Assert(!pExtent || (pExtent->off < offUnsigned && pExtent->off + pExtent->cb <= offUnsigned)); /* Skip leading zeros if there is a whole bunch of them. */ uint8_t const *pbSrcNZ = (uint8_t const *)ASMMemIsAll8(pbSrc, cbLeftToWrite, 0); if (!pbSrcNZ) { offUnsigned += cbLeftToWrite; cbLeftToWrite = 0; break; } size_t const cbZeros = pbSrcNZ - pbSrc; if (cbZeros >= RT_MIN(pThis->cbExtent, _64K)) { offUnsigned += cbZeros; cbLeftToWrite -= cbZeros; pbSrc = pbSrcNZ; pExtent = rtVfsMemFile_LocateExtent(pThis, offUnsigned, &fHit); break; } fHit = true; pExtent = rtVfsMemFile_AllocExtent(pThis, offUnsigned, cbLeftToWrite, pExtent); if (!pExtent) { rc = VERR_NO_MEMORY; break; } } /* * Copy the source data into the current extent. */ uint32_t const offDst = (uint32_t)(offUnsigned - pExtent->off); uint32_t cbThisWrite = pExtent->cb - offDst; if (cbThisWrite > cbLeftToWrite) cbThisWrite = (uint32_t)cbLeftToWrite; memcpy(&pExtent->abData[offDst], pbSrc, cbThisWrite); offUnsigned += cbLeftToWrite; cbLeftToWrite -= cbThisWrite; if (!cbLeftToWrite) break; pbSrc += cbThisWrite; Assert(offUnsigned == pExtent->off + pExtent->cb); /* * Advance to the next extent. */ PRTVFSMEMEXTENT pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry); Assert(!pNext || pNext->off >= offUnsigned); if (pNext && pNext->off == offUnsigned) pExtent = pNext; else fHit = false; } /* * Update the state, set return value and return. * Note! There must be no alternative exit path from the loop above. */ pThis->offCurPos = offUnsigned; if ((uint64_t)pThis->Base.ObjInfo.cbObject < offUnsigned) pThis->Base.ObjInfo.cbObject = offUnsigned; if (pcbWritten) *pcbWritten = pSgBuf->paSegs[0].cbSeg - cbLeftToWrite; return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtVfsMemFile_Flush(void *pvThis) { NOREF(pvThis); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} */ static DECLCALLBACK(int) rtVfsMemFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; int rc; if (fEvents != RTPOLL_EVT_ERROR) { *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR; rc = VINF_SUCCESS; } else rc = RTVfsUtilDummyPollOne(fEvents, cMillies, fIntr, pfRetEvents); return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtVfsMemFile_Tell(void *pvThis, PRTFOFF poffActual) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; *poffActual = pThis->offCurPos; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtVfsMemFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; pThis->Base.ObjInfo.Attr.fMode = (pThis->Base.ObjInfo.Attr.fMode & ~fMask) | fMode; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtVfsMemFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; if (pAccessTime) pThis->Base.ObjInfo.AccessTime = *pAccessTime; if (pModificationTime) pThis->Base.ObjInfo.ModificationTime = *pModificationTime; if (pChangeTime) pThis->Base.ObjInfo.ChangeTime = *pChangeTime; if (pBirthTime) pThis->Base.ObjInfo.BirthTime = *pBirthTime; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtVfsMemFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; if (uid != NIL_RTUID) pThis->Base.ObjInfo.Attr.u.Unix.uid = uid; if (gid != NIL_RTUID) pThis->Base.ObjInfo.Attr.u.Unix.gid = gid; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSFILEOPS,pfnSeek} */ static DECLCALLBACK(int) rtVfsMemFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; /* * Seek relative to which position. */ uint64_t offWrt; switch (uMethod) { case RTFILE_SEEK_BEGIN: offWrt = 0; break; case RTFILE_SEEK_CURRENT: offWrt = pThis->offCurPos; break; case RTFILE_SEEK_END: offWrt = pThis->Base.ObjInfo.cbObject; break; default: return VERR_INTERNAL_ERROR_5; } /* * Calc new position, take care to stay without bounds. */ uint64_t offNew; if (offSeek == 0) offNew = offWrt; else if (offSeek > 0) { offNew = offWrt + offSeek; if ( offNew < offWrt || offNew > RTFOFF_MAX) offNew = RTFOFF_MAX; } else if ((uint64_t)-offSeek < offWrt) offNew = offWrt + offSeek; else offNew = 0; /* * Update the state and set return value. */ if ( pThis->pCurExt && pThis->pCurExt->off - offNew >= pThis->pCurExt->cb) pThis->pCurExt = NULL; pThis->offCurPos = offNew; *poffActual = offNew; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} */ static DECLCALLBACK(int) rtVfsMemFile_QuerySize(void *pvThis, uint64_t *pcbFile) { PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis; *pcbFile = pThis->Base.ObjInfo.cbObject; return VINF_SUCCESS; } /** * Standard file operations. */ DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsStdFileOps = { { /* Stream */ { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FILE, "MemFile", rtVfsMemFile_Close, rtVfsMemFile_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, RTVFSIOSTREAMOPS_FEAT_NO_SG, rtVfsMemFile_Read, rtVfsMemFile_Write, rtVfsMemFile_Flush, rtVfsMemFile_PollOne, rtVfsMemFile_Tell, NULL /*Skip*/, NULL /*ZeroFill*/, RTVFSIOSTREAMOPS_VERSION, }, RTVFSFILEOPS_VERSION, /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_OFFSETOF(RTVFSFILEOPS, Stream.Obj) - RT_OFFSETOF(RTVFSFILEOPS, ObjSet), rtVfsMemFile_SetMode, rtVfsMemFile_SetTimes, rtVfsMemFile_SetOwner, RTVFSOBJSETOPS_VERSION }, rtVfsMemFile_Seek, rtVfsMemFile_QuerySize, RTVFSFILEOPS_VERSION }; RTDECL(int) RTVfsMemorizeIoStreamAsFile(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, PRTVFSFILE phVfsFile) { /* * Create a memory file instance and try set the extension size to match * the length of the I/O stream. */ RTFSOBJINFO ObjInfo; int rc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_UNIX); if (RT_SUCCESS(rc)) { RTVFSFILE hVfsFile; PRTVFSMEMFILE pThis; rc = RTVfsNewFile(&g_rtVfsStdFileOps, sizeof(*pThis), fFlags | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pThis); if (RT_SUCCESS(rc)) { pThis->Base.ObjInfo = ObjInfo; pThis->offCurPos = 0; pThis->pCurExt = NULL; RTListInit(&pThis->ExtentHead); if (ObjInfo.cbObject <= 0) pThis->cbExtent = _4K; else if (ObjInfo.cbObject < RTVFSMEM_MAX_EXTENT_SIZE) pThis->cbExtent = _4K /* ObjInfo.cbObject */; else pThis->cbExtent = RTVFSMEM_MAX_EXTENT_SIZE; /* * Copy the stream. */ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFile); rc = RTVfsUtilPumpIoStreams(hVfsIos, hVfsIosDst, pThis->cbExtent); RTVfsIoStrmRelease(hVfsIosDst); if (RT_SUCCESS(rc)) { pThis->pCurExt = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry); pThis->offCurPos = 0; if (!(fFlags & RTFILE_O_WRITE)) { /** @todo clear RTFILE_O_WRITE from the resulting. */ } *phVfsFile = hVfsFile; return VINF_SUCCESS; } RTVfsFileRelease(hVfsFile); } } return rc; }