/* $Id: vfsmemory.cpp $ */
/** @file
 * IPRT - Virtual File System, Memory Backed VFS.
 */

/*
 * Copyright (C) 2010-2019 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 <iprt/vfs.h>

#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/err.h>
#include <iprt/file.h>
#include <iprt/list.h>
#include <iprt/poll.h>
#include <iprt/string.h>
#include <iprt/vfslowlevel.h>



/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include "internal/iprt.h"
#include <iprt/vfs.h>

#include <iprt/err.h>
#include <iprt/mem.h>


/*********************************************************************************************************************************
*   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. */
    RTLISTANCHOR        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 || off < pExtent->off)
    {
        /* Consider the last entry first (for writes). */
        pExtent = RTListGetLast(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
        if (!pExtent)
        {
            *pfHit = false;
            return NULL;
        }
        if (off - pExtent->off < pExtent->cb)
        {
            *pfHit = true;
            pThis->pCurExt = pExtent;
            return pExtent;
        }

        /* Otherwise, start from the head. */
        pExtent = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
    }

    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 pNext;
        }

        pExtent = pNext;
    }

    *pfHit = true;
    pThis->pCurExt = pExtent;
    return pExtent;
}


/**
 * Locates the extent covering the specified offset, or the one after 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);
    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 (;;)
        {
            size_t      cbThisRead;

            /*
             * Do we hit an extent covering the current file surface?
             */
            if (fHit)
            {
                /* Yes, copy the data. */
                Assert(offUnsigned - pExtent->off < pExtent->cb);
                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;

                /* Advance, looping immediately if not sparse. */
                PRTVFSMEMEXTENT pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
                if (   pNext
                    && pNext->off == pExtent->off + pExtent->cb)
                {
                    pExtent = pNext;
                    continue;
                }

                Assert(!pNext || pNext->off > pExtent->off);
                pExtent = pNext;
                fHit = false;
            }
            else
                Assert(!pExtent || pExtent->off > offUnsigned);

            /*
             * No extent of this portion (sparse file) - Read zeros.
             */
            if (   !pExtent
                || offUnsigned + cbLeftToRead <= pExtent->off)
                cbThisRead = cbLeftToRead;
            else
                cbThisRead = (size_t)(pExtent->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;
        }
    }

    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   pNext               The extention after @a offUnsigned.  NULL if
 *                              none, i.e. we're allocating space at the end of
 *                              the file.
 */
static PRTVFSMEMEXTENT rtVfsMemFile_AllocExtent(PRTVFSMEMFILE pThis, uint64_t offUnsigned, size_t cbToWrite,
                                                PRTVFSMEMEXTENT pNext)
{
    /*
     * 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;

    PRTVFSMEMEXTENT pPrev     = pNext
                              ? RTListGetPrev(&pThis->ExtentHead, pNext, RTVFSMEMEXTENT, Entry)
                              : RTListGetLast(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
    uint64_t const  offPrev   = pPrev ? pPrev->off + pPrev->cb : 0;
    if (offExtent < offPrev)
        offExtent = offPrev;

    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_UOFFSETOF_DYN(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);
    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);

            /* Skip leading zeros if there is a whole bunch of them. */
            uint8_t const *pbSrcNZ = (uint8_t const *)ASMMemFirstNonZero(pbSrc, cbLeftToWrite);
            size_t         cbZeros = pbSrcNZ ? pbSrcNZ - pbSrc            : cbLeftToWrite;
            if (cbZeros)
            {
                uint64_t const cbToNext = pExtent ? pExtent->off - offUnsigned : UINT64_MAX;
                if (cbZeros > cbToNext)
                    cbZeros = (size_t)cbToNext;
                offUnsigned   += cbZeros;
                cbLeftToWrite -= cbZeros;
                if (!cbLeftToWrite)
                    break;

                Assert(!pExtent || offUnsigned <= pExtent->off);
                if (pExtent && pExtent->off == offUnsigned)
                {
                    fHit = true;
                    continue;
                }
            }

            fHit    = true;
            pExtent = rtVfsMemFile_AllocExtent(pThis, offUnsigned, cbLeftToWrite, pExtent);
            if (!pExtent)
            {
                rc = VERR_NO_MEMORY;
                break;
            }
        }
        Assert(offUnsigned - pExtent->off < pExtent->cb);

        /*
         * 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   += cbThisWrite;
        cbLeftToWrite -= cbThisWrite;
        if (!cbLeftToWrite)
            break;
        pbSrc += cbThisWrite;
        Assert(offUnsigned == pExtent->off + pExtent->cb);

        /*
         * Advance to the next extent (emulate the lookup).
         */
        pExtent = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
        fHit = pExtent && (offUnsigned - pExtent->off < pExtent->cb);
    }

    /*
     * 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,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 within RTFOFF type 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;
}


/**
 * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
 */
static DECLCALLBACK(int) rtVfsMemFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
{
    NOREF(pvThis); NOREF(cbFile); NOREF(fFlags);
    AssertMsgFailed(("Lucky you! You get to implement this (or bug bird about it).\n"));
    return VERR_NOT_IMPLEMENTED;
}


/**
 * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
 */
static DECLCALLBACK(int) rtVfsMemFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
{
    RT_NOREF(pvThis);
    *pcbMax = ~(size_t)0 >> 1;
    return VINF_SUCCESS;
}


/**
 * Memory file operations.
 */
DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsMemFileOps =
{
    { /* 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,
        NULL /*PollOne*/,
        rtVfsMemFile_Tell,
        NULL /*Skip*/,
        NULL /*ZeroFill*/,
        RTVFSIOSTREAMOPS_VERSION,
    },
    RTVFSFILEOPS_VERSION,
    /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0,
    { /* ObjSet */
        RTVFSOBJSETOPS_VERSION,
        RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
        rtVfsMemFile_SetMode,
        rtVfsMemFile_SetTimes,
        rtVfsMemFile_SetOwner,
        RTVFSOBJSETOPS_VERSION
    },
    rtVfsMemFile_Seek,
    rtVfsMemFile_QuerySize,
    rtVfsMemFile_SetSize,
    rtVfsMemFile_QueryMaxSize,
    RTVFSFILEOPS_VERSION
};


/**
 * Initialize the RTVFSMEMFILE::Base.ObjInfo  specific members.
 *
 * @param   pObjInfo        The object info to init.
 * @param   cbObject        The object size set.
 */
static void rtVfsMemInitObjInfo(PRTFSOBJINFO pObjInfo, uint64_t cbObject)
{
    pObjInfo->cbObject                  = cbObject;
    pObjInfo->cbAllocated               = cbObject;
    pObjInfo->Attr.fMode                = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | RTFS_UNIX_IRWXU;
    pObjInfo->Attr.enmAdditional        = RTFSOBJATTRADD_UNIX;
    pObjInfo->Attr.u.Unix.uid           = NIL_RTUID;
    pObjInfo->Attr.u.Unix.gid           = NIL_RTGID;
    pObjInfo->Attr.u.Unix.cHardlinks    = 1;
    pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
    pObjInfo->Attr.u.Unix.INodeId       = 0;
    pObjInfo->Attr.u.Unix.fFlags        = 0;
    pObjInfo->Attr.u.Unix.GenerationId  = 0;
    pObjInfo->Attr.u.Unix.Device        = 0;
    RTTimeNow(&pObjInfo->AccessTime);
    pObjInfo->ModificationTime          = pObjInfo->AccessTime;
    pObjInfo->ChangeTime                = pObjInfo->AccessTime;
    pObjInfo->BirthTime                 = pObjInfo->AccessTime;
}


/**
 * Initialize the RTVFSMEMFILE specific members.
 *
 * @param   pThis           The memory file to initialize.
 * @param   cbObject        The object size for estimating extent size.
 * @param   fFlags          The user specified flags.
 */
static void rtVfsMemFileInit(PRTVFSMEMFILE pThis, RTFOFF cbObject, uint32_t fFlags)
{
    pThis->offCurPos    = 0;
    pThis->pCurExt      = NULL;
    RTListInit(&pThis->ExtentHead);
    if (cbObject <= 0)
        pThis->cbExtent = _4K;
    else if (cbObject < RTVFSMEM_MAX_EXTENT_SIZE)
        pThis->cbExtent = fFlags & RTFILE_O_WRITE ? _4K : cbObject;
    else
        pThis->cbExtent = RTVFSMEM_MAX_EXTENT_SIZE;
}


/**
 * Rewinds the file to position 0 and clears the WRITE flag if necessary.
 *
 * @param   pThis           The memory file instance.
 * @param   fFlags          The user specified flags.
 */
static void rtVfsMemFileResetAndFixWriteFlag(PRTVFSMEMFILE pThis, uint32_t fFlags)
{
    pThis->pCurExt   = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
    pThis->offCurPos = 0;

    if (!(fFlags & RTFILE_O_WRITE))
    {
        /** @todo clear RTFILE_O_WRITE from the resulting. */
    }
}


RTDECL(int) RTVfsMemFileCreate(RTVFSIOSTREAM hVfsIos, size_t cbEstimate, PRTVFSFILE phVfsFile)
{
    /*
     * Create a memory file instance and set the extension size according to the
     * buffer size.  Add the WRITE flag so we can use normal write APIs for
     * copying the buffer.
     */
    RTVFSFILE       hVfsFile;
    PRTVFSMEMFILE   pThis;
    int rc = RTVfsNewFile(&g_rtVfsMemFileOps, sizeof(*pThis), RTFILE_O_READ | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
                          &hVfsFile, (void **)&pThis);
    if (RT_SUCCESS(rc))
    {
        rtVfsMemInitObjInfo(&pThis->Base.ObjInfo, 0);
        rtVfsMemFileInit(pThis, cbEstimate, RTFILE_O_READ | RTFILE_O_WRITE);

        if (hVfsIos != NIL_RTVFSIOSTREAM)
        {
            RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFile);
            rc = RTVfsUtilPumpIoStreams(hVfsIos, hVfsIosDst, pThis->cbExtent);
            RTVfsIoStrmRelease(hVfsIosDst);
        }

        if (RT_SUCCESS(rc))
        {
            *phVfsFile = hVfsFile;
            return VINF_SUCCESS;
        }

        RTVfsFileRelease(hVfsFile);
    }
    return rc;
}


RTDECL(int) RTVfsMemIoStrmCreate(RTVFSIOSTREAM hVfsIos, size_t cbEstimate, PRTVFSIOSTREAM phVfsIos)
{
    RTVFSFILE hVfsFile;
    int rc = RTVfsMemFileCreate(hVfsIos, cbEstimate, &hVfsFile);
    if (RT_SUCCESS(rc))
    {
        *phVfsIos = RTVfsFileToIoStream(hVfsFile);
        AssertStmt(*phVfsIos != NIL_RTVFSIOSTREAM, rc = VERR_INTERNAL_ERROR_2);
        RTVfsFileRelease(hVfsFile);
    }
    return rc;
}


RTDECL(int) RTVfsFileFromBuffer(uint32_t fFlags, void const *pvBuf, size_t cbBuf, PRTVFSFILE phVfsFile)
{
    /*
     * Create a memory file instance and set the extension size according to the
     * buffer size.  Add the WRITE flag so we can use normal write APIs for
     * copying the buffer.
     */
    RTVFSFILE       hVfsFile;
    PRTVFSMEMFILE   pThis;
    int rc = RTVfsNewFile(&g_rtVfsMemFileOps, sizeof(*pThis), fFlags | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
                          &hVfsFile, (void **)&pThis);
    if (RT_SUCCESS(rc))
    {
        rtVfsMemInitObjInfo(&pThis->Base.ObjInfo, cbBuf);
        rtVfsMemFileInit(pThis, cbBuf, fFlags);

        /*
         * Copy the buffer and reposition the file pointer to the start.
         */
        rc = RTVfsFileWrite(hVfsFile, pvBuf, cbBuf, NULL);
        if (RT_SUCCESS(rc))
        {
            rtVfsMemFileResetAndFixWriteFlag(pThis, fFlags);
            *phVfsFile = hVfsFile;
            return VINF_SUCCESS;
        }
        RTVfsFileRelease(hVfsFile);
    }
    return rc;
}


RTDECL(int) RTVfsIoStrmFromBuffer(uint32_t fFlags, void const *pvBuf, size_t cbBuf, PRTVFSIOSTREAM phVfsIos)
{
    RTVFSFILE hVfsFile;
    int rc = RTVfsFileFromBuffer(fFlags, pvBuf, cbBuf, &hVfsFile);
    if (RT_SUCCESS(rc))
    {
        *phVfsIos = RTVfsFileToIoStream(hVfsFile);
        RTVfsFileRelease(hVfsFile);
    }
    return rc;
}


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_rtVfsMemFileOps, sizeof(*pThis), fFlags | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
                          &hVfsFile, (void **)&pThis);
        if (RT_SUCCESS(rc))
        {
            pThis->Base.ObjInfo = ObjInfo;
            rtVfsMemFileInit(pThis, ObjInfo.cbObject, fFlags);

            /*
             * Copy the stream.
             */
            RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFile);
            rc = RTVfsUtilPumpIoStreams(hVfsIos, hVfsIosDst, pThis->cbExtent);
            RTVfsIoStrmRelease(hVfsIosDst);
            if (RT_SUCCESS(rc))
            {
                rtVfsMemFileResetAndFixWriteFlag(pThis, fFlags);
                *phVfsFile = hVfsFile;
                return VINF_SUCCESS;
            }
            RTVfsFileRelease(hVfsFile);
        }
    }
    return rc;
}

