diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Runtime/common/vfs | |
parent | Initial commit. (diff) | |
download | virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.tar.xz virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/vfs')
-rw-r--r-- | src/VBox/Runtime/common/vfs/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsbase.cpp | 4294 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfschain.cpp | 1851 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsfss2dir.cpp | 434 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsiosmisc.cpp | 228 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsmemory.cpp | 943 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsmisc.cpp | 89 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsmount.cpp | 574 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsmsg.cpp | 68 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsprogress.cpp | 542 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsreadahead.cpp | 1001 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsstddir.cpp | 855 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsstdfile.cpp | 640 | ||||
-rw-r--r-- | src/VBox/Runtime/common/vfs/vfsstdpipe.cpp | 317 |
14 files changed, 11836 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/vfs/Makefile.kup b/src/VBox/Runtime/common/vfs/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/common/vfs/Makefile.kup diff --git a/src/VBox/Runtime/common/vfs/vfsbase.cpp b/src/VBox/Runtime/common/vfs/vfsbase.cpp new file mode 100644 index 00000000..a97ae7ad --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsbase.cpp @@ -0,0 +1,4294 @@ +/* $Id: vfsbase.cpp $ */ +/** @file + * IPRT - Virtual File System, Base. + */ + +/* + * Copyright (C) 2010-2020 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/poll.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/zero.h> + +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/magics.h" +#include "internal/path.h" +//#include "internal/vfs.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The instance data alignment. */ +#define RTVFS_INST_ALIGNMENT 16U + +/** The max number of symbolic links to resolve in a path. */ +#define RTVFS_MAX_LINKS 20U + + +/** Asserts that the VFS base object vtable is valid. */ +#define RTVFSOBJ_ASSERT_OPS(a_pObjOps, a_enmType) \ + do \ + { \ + Assert((a_pObjOps)->uVersion == RTVFSOBJOPS_VERSION); \ + Assert((a_pObjOps)->enmType == (a_enmType) || (a_enmType) == RTVFSOBJTYPE_INVALID); \ + AssertPtr((a_pObjOps)->pszName); \ + Assert(*(a_pObjOps)->pszName); \ + AssertPtr((a_pObjOps)->pfnClose); \ + AssertPtr((a_pObjOps)->pfnQueryInfo); \ + Assert((a_pObjOps)->uEndMarker == RTVFSOBJOPS_VERSION); \ + } while (0) + +/** Asserts that the VFS set object vtable is valid. */ +#define RTVFSOBJSET_ASSERT_OPS(a_pSetOps, a_offObjOps) \ + do \ + { \ + Assert((a_pSetOps)->uVersion == RTVFSOBJSETOPS_VERSION); \ + Assert((a_pSetOps)->offObjOps == (a_offObjOps)); \ + AssertPtrNull((a_pSetOps)->pfnSetMode); \ + AssertPtrNull((a_pSetOps)->pfnSetTimes); \ + AssertPtrNull((a_pSetOps)->pfnSetOwner); \ + Assert((a_pSetOps)->uEndMarker == RTVFSOBJSETOPS_VERSION); \ + } while (0) + +/** Asserts that the VFS directory vtable is valid. */ +#define RTVFSDIR_ASSERT_OPS(pDirOps, a_enmType) \ + do { \ + RTVFSOBJ_ASSERT_OPS(&(pDirOps)->Obj, a_enmType); \ + RTVFSOBJSET_ASSERT_OPS(&(pDirOps)->ObjSet, RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj)); \ + Assert((pDirOps)->uVersion == RTVFSDIROPS_VERSION); \ + Assert(!(pDirOps)->fReserved); \ + AssertPtr((pDirOps)->pfnOpen); \ + AssertPtrNull((pDirOps)->pfnOpenFile); \ + AssertPtrNull((pDirOps)->pfnOpenDir); \ + AssertPtrNull((pDirOps)->pfnCreateDir); \ + AssertPtrNull((pDirOps)->pfnOpenSymlink); \ + AssertPtr((pDirOps)->pfnCreateSymlink); \ + AssertPtr((pDirOps)->pfnUnlinkEntry); \ + AssertPtr((pDirOps)->pfnRewindDir); \ + AssertPtr((pDirOps)->pfnReadDir); \ + Assert((pDirOps)->uEndMarker == RTVFSDIROPS_VERSION); \ + } while (0) + +/** Asserts that the VFS I/O stream vtable is valid. */ +#define RTVFSIOSTREAM_ASSERT_OPS(pIoStreamOps, a_enmType) \ + do { \ + RTVFSOBJ_ASSERT_OPS(&(pIoStreamOps)->Obj, a_enmType); \ + Assert((pIoStreamOps)->uVersion == RTVFSIOSTREAMOPS_VERSION); \ + Assert(!((pIoStreamOps)->fFeatures & ~RTVFSIOSTREAMOPS_FEAT_VALID_MASK)); \ + AssertPtr((pIoStreamOps)->pfnRead); \ + AssertPtrNull((pIoStreamOps)->pfnWrite); \ + AssertPtr((pIoStreamOps)->pfnFlush); \ + AssertPtrNull((pIoStreamOps)->pfnPollOne); \ + AssertPtr((pIoStreamOps)->pfnTell); \ + AssertPtrNull((pIoStreamOps)->pfnSkip); \ + AssertPtrNull((pIoStreamOps)->pfnZeroFill); \ + Assert((pIoStreamOps)->uEndMarker == RTVFSIOSTREAMOPS_VERSION); \ + } while (0) + +/** Asserts that the VFS I/O stream vtable is valid. */ +#define RTVFSFILE_ASSERT_OPS(pFileOps, a_enmType) \ + do { \ + RTVFSIOSTREAM_ASSERT_OPS(&(pFileOps)->Stream, a_enmType); \ + Assert((pFileOps)->uVersion == RTVFSFILEOPS_VERSION); \ + Assert((pFileOps)->fReserved == 0); \ + AssertPtr((pFileOps)->pfnSeek); \ + AssertPtrNull((pFileOps)->pfnQuerySize); \ + AssertPtrNull((pFileOps)->pfnSetSize); \ + AssertPtrNull((pFileOps)->pfnQueryMaxSize); \ + Assert((pFileOps)->uEndMarker == RTVFSFILEOPS_VERSION); \ + } while (0) + +/** Asserts that the VFS symlink vtable is valid. */ +#define RTVFSSYMLINK_ASSERT_OPS(pSymlinkOps, a_enmType) \ + do { \ + RTVFSOBJ_ASSERT_OPS(&(pSymlinkOps)->Obj, a_enmType); \ + RTVFSOBJSET_ASSERT_OPS(&(pSymlinkOps)->ObjSet, RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj)); \ + Assert((pSymlinkOps)->uVersion == RTVFSSYMLINKOPS_VERSION); \ + Assert(!(pSymlinkOps)->fReserved); \ + AssertPtr((pSymlinkOps)->pfnRead); \ + Assert((pSymlinkOps)->uEndMarker == RTVFSSYMLINKOPS_VERSION); \ + } while (0) + + +/** Validates a VFS handle and returns @a rcRet if it's invalid. */ +#define RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, rcRet) \ + do { \ + if ((hVfs) != NIL_RTVFS) \ + { \ + AssertPtrReturn((hVfs), (rcRet)); \ + AssertReturn((hVfs)->uMagic == RTVFS_MAGIC, (rcRet)); \ + } \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** @todo Move all this stuff to internal/vfs.h */ + + +/** + * The VFS internal lock data. + */ +typedef struct RTVFSLOCKINTERNAL +{ + /** The number of references to the this lock. */ + uint32_t volatile cRefs; + /** The lock type. */ + RTVFSLOCKTYPE enmType; + /** Type specific data. */ + union + { + /** Read/Write semaphore handle. */ + RTSEMRW hSemRW; + /** Fast mutex semaphore handle. */ + RTSEMFASTMUTEX hFastMtx; + /** Regular mutex semaphore handle. */ + RTSEMMUTEX hMtx; + } u; +} RTVFSLOCKINTERNAL; + + +/** + * The VFS base object handle data. + * + * All other VFS handles are derived from this one. The final handle type is + * indicated by RTVFSOBJOPS::enmType via the RTVFSOBJINTERNAL::pOps member. + */ +typedef struct RTVFSOBJINTERNAL +{ + /** The VFS magic (RTVFSOBJ_MAGIC). */ + uint32_t uMagic : 31; + /** Set if we've got no VFS reference but still got a valid hVfs. + * This is hack for permanent root directory objects. */ + uint32_t fNoVfsRef : 1; + /** The number of references to this VFS object. */ + uint32_t volatile cRefs; + /** Pointer to the instance data. */ + void *pvThis; + /** The vtable. */ + PCRTVFSOBJOPS pOps; + /** The lock protecting all access to the VFS. + * Only valid if RTVFS_C_THREAD_SAFE is set, otherwise it is NIL_RTVFSLOCK. */ + RTVFSLOCK hLock; + /** Reference back to the VFS containing this object. */ + RTVFS hVfs; +} RTVFSOBJINTERNAL; + + +/** + * The VFS filesystem stream handle data. + * + * @extends RTVFSOBJINTERNAL + */ +typedef struct RTVFSFSSTREAMINTERNAL +{ + /** The VFS magic (RTVFSFSTREAM_MAGIC). */ + uint32_t uMagic; + /** File open flags, at a minimum the access mask. */ + uint32_t fFlags; + /** The vtable. */ + PCRTVFSFSSTREAMOPS pOps; + /** The base object handle data. */ + RTVFSOBJINTERNAL Base; +} RTVFSFSSTREAMINTERNAL; + + +/** + * The VFS handle data. + * + * @extends RTVFSOBJINTERNAL + */ +typedef struct RTVFSINTERNAL +{ + /** The VFS magic (RTVFS_MAGIC). */ + uint32_t uMagic; + /** Creation flags (RTVFS_C_XXX). */ + uint32_t fFlags; + /** The vtable. */ + PCRTVFSOPS pOps; + /** The base object handle data. */ + RTVFSOBJINTERNAL Base; +} RTVFSINTERNAL; + + +/** + * The VFS directory handle data. + * + * @extends RTVFSOBJINTERNAL + */ +typedef struct RTVFSDIRINTERNAL +{ + /** The VFS magic (RTVFSDIR_MAGIC). */ + uint32_t uMagic; + /** Reserved for flags or something. */ + uint32_t fReserved; + /** The vtable. */ + PCRTVFSDIROPS pOps; + /** The base object handle data. */ + RTVFSOBJINTERNAL Base; +} RTVFSDIRINTERNAL; + + +/** + * The VFS symbolic link handle data. + * + * @extends RTVFSOBJINTERNAL + */ +typedef struct RTVFSSYMLINKINTERNAL +{ + /** The VFS magic (RTVFSSYMLINK_MAGIC). */ + uint32_t uMagic; + /** Reserved for flags or something. */ + uint32_t fReserved; + /** The vtable. */ + PCRTVFSSYMLINKOPS pOps; + /** The base object handle data. */ + RTVFSOBJINTERNAL Base; +} RTVFSSYMLINKINTERNAL; + + +/** + * The VFS I/O stream handle data. + * + * This is often part of a type specific handle, like a file or pipe. + * + * @extends RTVFSOBJINTERNAL + */ +typedef struct RTVFSIOSTREAMINTERNAL +{ + /** The VFS magic (RTVFSIOSTREAM_MAGIC). */ + uint32_t uMagic; + /** File open flags, at a minimum the access mask. */ + uint32_t fFlags; + /** The vtable. */ + PCRTVFSIOSTREAMOPS pOps; + /** The base object handle data. */ + RTVFSOBJINTERNAL Base; +} RTVFSIOSTREAMINTERNAL; + + +/** + * The VFS file handle data. + * + * @extends RTVFSIOSTREAMINTERNAL + */ +typedef struct RTVFSFILEINTERNAL +{ + /** The VFS magic (RTVFSFILE_MAGIC). */ + uint32_t uMagic; + /** Reserved for flags or something. */ + uint32_t fReserved; + /** The vtable. */ + PCRTVFSFILEOPS pOps; + /** The stream handle data. */ + RTVFSIOSTREAMINTERNAL Stream; +} RTVFSFILEINTERNAL; + +#if 0 /* later */ + +/** + * The VFS pipe handle data. + * + * @extends RTVFSIOSTREAMINTERNAL + */ +typedef struct RTVFSPIPEINTERNAL +{ + /** The VFS magic (RTVFSPIPE_MAGIC). */ + uint32_t uMagic; + /** Reserved for flags or something. */ + uint32_t fReserved; + /** The vtable. */ + PCRTVFSPIPEOPS pOps; + /** The stream handle data. */ + RTVFSIOSTREAMINTERNAL Stream; +} RTVFSPIPEINTERNAL; + + +/** + * The VFS socket handle data. + * + * @extends RTVFSIOSTREAMINTERNAL + */ +typedef struct RTVFSSOCKETINTERNAL +{ + /** The VFS magic (RTVFSSOCKET_MAGIC). */ + uint32_t uMagic; + /** Reserved for flags or something. */ + uint32_t fReserved; + /** The vtable. */ + PCRTVFSSOCKETOPS pOps; + /** The stream handle data. */ + RTVFSIOSTREAMINTERNAL Stream; +} RTVFSSOCKETINTERNAL; + +#endif /* later */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLINLINE(uint32_t) rtVfsObjRelease(RTVFSOBJINTERNAL *pThis); +static int rtVfsTraverseToParent(RTVFSINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags, RTVFSDIRINTERNAL **ppVfsParentDir); +static int rtVfsDirFollowSymlinkObjToParent(RTVFSDIRINTERNAL **ppVfsParentDir, RTVFSOBJ hVfsObj, + PRTVFSPARSEDPATH pPath, uint32_t fFlags); + + + +/* + * + * V F S L o c k A b s t r a c t i o n + * V F S L o c k A b s t r a c t i o n + * V F S L o c k A b s t r a c t i o n + * + * + */ + + +RTDECL(uint32_t) RTVfsLockRetain(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType)); + return cRefs; +} + + +RTDECL(uint32_t) RTVfsLockRetainDebug(RTVFSLOCK hLock, RT_SRC_POS_DECL) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType)); + LogFlow(("RTVfsLockRetainDebug(%p) -> %d; caller: %s %s(%u)\n", hLock, cRefs, pszFunction, pszFile, iLine)); + RT_SRC_POS_NOREF(); + return cRefs; +} + + +/** + * Destroys a VFS lock handle. + * + * @param pThis The lock to destroy. + */ +static void rtVfsLockDestroy(RTVFSLOCKINTERNAL *pThis) +{ + switch (pThis->enmType) + { + case RTVFSLOCKTYPE_RW: + RTSemRWDestroy(pThis->u.hSemRW); + pThis->u.hSemRW = NIL_RTSEMRW; + break; + + case RTVFSLOCKTYPE_FASTMUTEX: + RTSemFastMutexDestroy(pThis->u.hFastMtx); + pThis->u.hFastMtx = NIL_RTSEMFASTMUTEX; + break; + + case RTVFSLOCKTYPE_MUTEX: + RTSemMutexDestroy(pThis->u.hMtx); + pThis->u.hFastMtx = NIL_RTSEMMUTEX; + break; + + default: + AssertMsgFailedReturnVoid(("%p %d\n", pThis, pThis->enmType)); + } + + pThis->enmType = RTVFSLOCKTYPE_INVALID; + RTMemFree(pThis); +} + + +RTDECL(uint32_t) RTVfsLockRelease(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + if (pThis == NIL_RTVFSLOCK) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType)); + if (cRefs == 0) + rtVfsLockDestroy(pThis); + return cRefs; +} + + +/** + * Creates a read/write lock. + * + * @returns IPRT status code + * @param phLock Where to return the lock handle. + */ +static int rtVfsLockCreateRW(PRTVFSLOCK phLock) +{ + RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->cRefs = 1; + pThis->enmType = RTVFSLOCKTYPE_RW; + + int rc = RTSemRWCreate(&pThis->u.hSemRW); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + *phLock = pThis; + return VINF_SUCCESS; +} + + +/** + * Creates a fast mutex lock. + * + * @returns IPRT status code + * @param phLock Where to return the lock handle. + */ +static int rtVfsLockCreateFastMutex(PRTVFSLOCK phLock) +{ + RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->cRefs = 1; + pThis->enmType = RTVFSLOCKTYPE_FASTMUTEX; + + int rc = RTSemFastMutexCreate(&pThis->u.hFastMtx); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + *phLock = pThis; + return VINF_SUCCESS; + +} + + +/** + * Creates a mutex lock. + * + * @returns IPRT status code + * @param phLock Where to return the lock handle. + */ +static int rtVfsLockCreateMutex(PRTVFSLOCK phLock) +{ + RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->cRefs = 1; + pThis->enmType = RTVFSLOCKTYPE_MUTEX; + + int rc = RTSemMutexCreate(&pThis->u.hMtx); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + *phLock = pThis; + return VINF_SUCCESS; +} + + +/** + * Acquires the lock for reading. + * + * @param hLock Non-nil lock handle. + * @internal + */ +RTDECL(void) RTVfsLockAcquireReadSlow(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + int rc; + + AssertPtr(pThis); + switch (pThis->enmType) + { + case RTVFSLOCKTYPE_RW: + rc = RTSemRWRequestRead(pThis->u.hSemRW, RT_INDEFINITE_WAIT); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_FASTMUTEX: + rc = RTSemFastMutexRequest(pThis->u.hFastMtx); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_MUTEX: + rc = RTSemMutexRequest(pThis->u.hMtx, RT_INDEFINITE_WAIT); + AssertRC(rc); + break; + default: + AssertFailed(); + } +} + + +/** + * Release a lock held for reading. + * + * @param hLock Non-nil lock handle. + * @internal + */ +RTDECL(void) RTVfsLockReleaseReadSlow(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + int rc; + + AssertPtr(pThis); + switch (pThis->enmType) + { + case RTVFSLOCKTYPE_RW: + rc = RTSemRWReleaseRead(pThis->u.hSemRW); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_FASTMUTEX: + rc = RTSemFastMutexRelease(pThis->u.hFastMtx); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_MUTEX: + rc = RTSemMutexRelease(pThis->u.hMtx); + AssertRC(rc); + break; + default: + AssertFailed(); + } +} + + +/** + * Acquires the lock for writing. + * + * @param hLock Non-nil lock handle. + * @internal + */ +RTDECL(void) RTVfsLockAcquireWriteSlow(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + int rc; + + AssertPtr(pThis); + switch (pThis->enmType) + { + case RTVFSLOCKTYPE_RW: + rc = RTSemRWRequestWrite(pThis->u.hSemRW, RT_INDEFINITE_WAIT); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_FASTMUTEX: + rc = RTSemFastMutexRequest(pThis->u.hFastMtx); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_MUTEX: + rc = RTSemMutexRequest(pThis->u.hMtx, RT_INDEFINITE_WAIT); + AssertRC(rc); + break; + default: + AssertFailed(); + } +} + + +/** + * Release a lock held for writing. + * + * @param hLock Non-nil lock handle. + * @internal + */ +RTDECL(void) RTVfsLockReleaseWriteSlow(RTVFSLOCK hLock) +{ + RTVFSLOCKINTERNAL *pThis = hLock; + int rc; + + AssertPtr(pThis); + switch (pThis->enmType) + { + case RTVFSLOCKTYPE_RW: + rc = RTSemRWReleaseWrite(pThis->u.hSemRW); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_FASTMUTEX: + rc = RTSemFastMutexRelease(pThis->u.hFastMtx); + AssertRC(rc); + break; + + case RTVFSLOCKTYPE_MUTEX: + rc = RTSemMutexRelease(pThis->u.hMtx); + AssertRC(rc); + break; + default: + AssertFailed(); + } +} + + + +/* + * + * B A S E O B J E C T + * B A S E O B J E C T + * B A S E O B J E C T + * + */ + +/** + * Internal object retainer that asserts sanity in strict builds. + * + * @param pThis The base object handle data. + * @param pszCaller Where we're called from. + */ +DECLINLINE(void) rtVfsObjRetainVoid(RTVFSOBJINTERNAL *pThis, const char *pszCaller) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); +LogFlow(("rtVfsObjRetainVoid(%p/%p) -> %d; caller=%s\n", pThis, pThis->pvThis, cRefs, pszCaller)); RT_NOREF(pszCaller); + AssertMsg(cRefs > 1 && cRefs < _1M, + ("%#x %p ops=%p %s (%d); caller=%s\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType, pszCaller)); + NOREF(cRefs); +} + + +/** + * Initializes the base object part of a new object. + * + * @returns IPRT status code. + * @param pThis Pointer to the base object part. + * @param pObjOps The base object vtable. + * @param hVfs The VFS handle to associate with. + * @param fNoVfsRef If set, do not retain an additional reference to + * @a hVfs. Permanent root dir hack. + * @param hLock The lock handle, pseudo handle or nil. + * @param pvThis Pointer to the private data. + */ +static int rtVfsObjInitNewObject(RTVFSOBJINTERNAL *pThis, PCRTVFSOBJOPS pObjOps, RTVFS hVfs, bool fNoVfsRef, + RTVFSLOCK hLock, void *pvThis) +{ + /* + * Deal with the lock first as that's the most complicated matter. + */ + if (hLock != NIL_RTVFSLOCK) + { + int rc; + if (hLock == RTVFSLOCK_CREATE_RW) + { + rc = rtVfsLockCreateRW(&hLock); + AssertRCReturn(rc, rc); + } + else if (hLock == RTVFSLOCK_CREATE_FASTMUTEX) + { + rc = rtVfsLockCreateFastMutex(&hLock); + AssertRCReturn(rc, rc); + } + else if (hLock == RTVFSLOCK_CREATE_MUTEX) + { + rc = rtVfsLockCreateMutex(&hLock); + AssertRCReturn(rc, rc); + } + else + { + /* + * The caller specified a lock, we consume the this reference. + */ + AssertPtrReturn(hLock, VERR_INVALID_HANDLE); + AssertReturn(hLock->enmType > RTVFSLOCKTYPE_INVALID && hLock->enmType < RTVFSLOCKTYPE_END, VERR_INVALID_HANDLE); + AssertReturn(hLock->cRefs > 0, VERR_INVALID_HANDLE); + } + } + else if (hVfs != NIL_RTVFS) + { + /* + * Retain a reference to the VFS lock, if there is one. + */ + hLock = hVfs->Base.hLock; + if (hLock != NIL_RTVFSLOCK) + { + uint32_t cRefs = RTVfsLockRetain(hLock); + if (RT_UNLIKELY(cRefs == UINT32_MAX)) + return VERR_INVALID_HANDLE; + } + } + + + /* + * Do the actual initializing. + */ + pThis->uMagic = RTVFSOBJ_MAGIC; + pThis->fNoVfsRef = fNoVfsRef; + pThis->pvThis = pvThis; + pThis->pOps = pObjOps; + pThis->cRefs = 1; + pThis->hVfs = hVfs; + pThis->hLock = hLock; + if (hVfs != NIL_RTVFS && !fNoVfsRef) + rtVfsObjRetainVoid(&hVfs->Base, "rtVfsObjInitNewObject"); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTVfsNewBaseObj(PCRTVFSOBJOPS pObjOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFSOBJ phVfsObj, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pObjOps); + AssertReturn(pObjOps->uVersion == RTVFSOBJOPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pObjOps->uEndMarker == RTVFSOBJOPS_VERSION, VERR_VERSION_MISMATCH); + RTVFSOBJ_ASSERT_OPS(pObjOps, RTVFSOBJTYPE_BASE); + Assert(cbInstance > 0); + AssertPtr(ppvInstance); + AssertPtr(phVfsObj); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSOBJINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSOBJINTERNAL *pThis = (RTVFSOBJINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(pThis, pObjOps, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + *phVfsObj = pThis; + *ppvInstance = pThis->pvThis; + return VINF_SUCCESS; +} + + +/** + * Internal object retainer that asserts sanity in strict builds. + * + * @returns The new reference count. + * @param pThis The base object handle data. + */ +DECLINLINE(uint32_t) rtVfsObjRetain(RTVFSOBJINTERNAL *pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); +LogFlow(("rtVfsObjRetain(%p/%p) -> %d\n", pThis, pThis->pvThis, cRefs)); + AssertMsg(cRefs > 1 && cRefs < _1M, + ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType)); + return cRefs; +} + +/** + * Internal object retainer that asserts sanity in strict builds. + * + * @returns The new reference count. + * @param pThis The base object handle data. + */ +DECLINLINE(uint32_t) rtVfsObjRetainDebug(RTVFSOBJINTERNAL *pThis, const char *pszApi, RT_SRC_POS_DECL) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, + ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType)); + LogFlow(("%s(%p/%p) -> %2d; caller: %s %s(%d) \n", pszApi, pThis, pThis->pvThis, cRefs, pszFunction, pszFile, iLine)); + RT_SRC_POS_NOREF(); RT_NOREF(pszApi); + return cRefs; +} + + +#ifdef DEBUG +# undef RTVfsObjRetain +#endif +RTDECL(uint32_t) RTVfsObjRetain(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX); + + return rtVfsObjRetain(pThis); +} +#ifdef DEBUG +# define RTVfsObjRetain(hVfsObj) RTVfsObjRetainDebug(hVfsObj, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsObjRetainDebug(RTVFSOBJ hVfsObj, RT_SRC_POS_DECL) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX); + + return rtVfsObjRetainDebug(pThis, "RTVfsObjRetainDebug", RT_SRC_POS_ARGS); +} + + +/** + * Does the actual object destruction for rtVfsObjRelease(). + * + * @param pThis The object to destroy. + */ +static void rtVfsObjDestroy(RTVFSOBJINTERNAL *pThis) +{ + RTVFSOBJTYPE const enmType = pThis->pOps->enmType; + + /* + * Invalidate the object. + */ + RTVfsLockAcquireWrite(pThis->hLock); /* paranoia */ + void *pvToFree = NULL; + switch (enmType) + { + case RTVFSOBJTYPE_BASE: + pvToFree = pThis; + break; + + case RTVFSOBJTYPE_VFS: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base)->uMagic, RTVFS_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_FS_STREAM: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base)->uMagic, RTVFSFSSTREAM_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_IO_STREAM: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base)->uMagic, RTVFSIOSTREAM_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_DIR: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base)->uMagic, RTVFSDIR_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_FILE: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base)->uMagic, RTVFSFILE_MAGIC_DEAD); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base)->uMagic, RTVFSIOSTREAM_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_SYMLINK: + pvToFree = RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base); + ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base)->uMagic, RTVFSSYMLINK_MAGIC_DEAD); + break; + + case RTVFSOBJTYPE_INVALID: + case RTVFSOBJTYPE_END: + case RTVFSOBJTYPE_32BIT_HACK: + AssertMsgFailed(("enmType=%d ops=%p %s\n", enmType, pThis->pOps, pThis->pOps->pszName)); + break; + /* no default as we want gcc warnings. */ + } + pThis->uMagic = RTVFSOBJ_MAGIC_DEAD; + RTVfsLockReleaseWrite(pThis->hLock); + + /* + * Close the object and free the handle. + */ + int rc = pThis->pOps->pfnClose(pThis->pvThis); + AssertRC(rc); + if (pThis->hVfs != NIL_RTVFS) + { + if (!pThis->fNoVfsRef) + rtVfsObjRelease(&pThis->hVfs->Base); + pThis->hVfs = NIL_RTVFS; + } + if (pThis->hLock != NIL_RTVFSLOCK) + { + RTVfsLockRelease(pThis->hLock); + pThis->hLock = NIL_RTVFSLOCK; + } + RTMemFree(pvToFree); +} + + +/** + * Internal object releaser that asserts sanity in strict builds. + * + * @returns The new reference count. + * @param pcRefs The reference counter. + */ +DECLINLINE(uint32_t) rtVfsObjRelease(RTVFSOBJINTERNAL *pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType)); + LogFlow(("rtVfsObjRelease(%p/%p) -> %d\n", pThis, pThis->pvThis, cRefs)); + if (cRefs == 0) + rtVfsObjDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTVfsObjRelease(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis == NIL_RTVFSOBJ) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX); + return rtVfsObjRelease(pThis); +} + + +RTDECL(RTVFSOBJTYPE) RTVfsObjGetType(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, RTVFSOBJTYPE_INVALID); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, RTVFSOBJTYPE_INVALID); + return pThis->pOps->enmType; + } + return RTVFSOBJTYPE_INVALID; +} + + +RTDECL(RTVFS) RTVfsObjToVfs(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFS); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFS); + + if (pThis->pOps->enmType == RTVFSOBJTYPE_VFS) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToVfs"); + LogFlow(("RTVfsObjToVfs(%p) -> %p\n", pThis, RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base))); + return RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base); + } + } + return NIL_RTVFS; +} + + +RTDECL(RTVFSFSSTREAM) RTVfsObjToFsStream(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFSFSSTREAM); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSFSSTREAM); + + if (pThis->pOps->enmType == RTVFSOBJTYPE_FS_STREAM) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToFsStream"); + return RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base); + } + } + return NIL_RTVFSFSSTREAM; +} + + +RTDECL(RTVFSDIR) RTVfsObjToDir(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFSDIR); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSDIR); + + if (pThis->pOps->enmType == RTVFSOBJTYPE_DIR) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToDir"); + return RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base); + } + } + return NIL_RTVFSDIR; +} + + +RTDECL(RTVFSIOSTREAM) RTVfsObjToIoStream(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFSIOSTREAM); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSIOSTREAM); + + if ( pThis->pOps->enmType == RTVFSOBJTYPE_IO_STREAM + || pThis->pOps->enmType == RTVFSOBJTYPE_FILE) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToIoStream"); + return RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base); + } + } + return NIL_RTVFSIOSTREAM; +} + + +RTDECL(RTVFSFILE) RTVfsObjToFile(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFSFILE); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSFILE); + + if (pThis->pOps->enmType == RTVFSOBJTYPE_FILE) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToFile"); + return RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base); + } + } + return NIL_RTVFSFILE; +} + + +RTDECL(RTVFSSYMLINK) RTVfsObjToSymlink(RTVFSOBJ hVfsObj) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + if (pThis != NIL_RTVFSOBJ) + { + AssertPtrReturn(pThis, NIL_RTVFSSYMLINK); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSSYMLINK); + + if (pThis->pOps->enmType == RTVFSOBJTYPE_SYMLINK) + { + rtVfsObjRetainVoid(pThis, "RTVfsObjToSymlink"); + return RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base); + } + } + return NIL_RTVFSSYMLINK; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromVfs(RTVFS hVfs) +{ + if (hVfs != NIL_RTVFS) + { + RTVFSOBJINTERNAL *pThis = &hVfs->Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromVfs"); + LogFlow(("RTVfsObjFromVfs(%p) -> %p\n", hVfs, pThis)); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromFsStream(RTVFSFSSTREAM hVfsFss) +{ + if (hVfsFss != NIL_RTVFSFSSTREAM) + { + RTVFSOBJINTERNAL *pThis = &hVfsFss->Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromFsStream"); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromDir(RTVFSDIR hVfsDir) +{ + if (hVfsDir != NIL_RTVFSDIR) + { + RTVFSOBJINTERNAL *pThis = &hVfsDir->Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromDir"); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromIoStream(RTVFSIOSTREAM hVfsIos) +{ + if (hVfsIos != NIL_RTVFSIOSTREAM) + { + RTVFSOBJINTERNAL *pThis = &hVfsIos->Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromIoStream"); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromFile(RTVFSFILE hVfsFile) +{ + if (hVfsFile != NIL_RTVFSFILE) + { + RTVFSOBJINTERNAL *pThis = &hVfsFile->Stream.Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromFile"); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(RTVFSOBJ) RTVfsObjFromSymlink(RTVFSSYMLINK hVfsSym) +{ + if (hVfsSym != NIL_RTVFSSYMLINK) + { + RTVFSOBJINTERNAL *pThis = &hVfsSym->Base; + AssertPtrReturn(pThis, NIL_RTVFSOBJ); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ); + + rtVfsObjRetainVoid(pThis, "RTVfsObjFromSymlink"); + return pThis; + } + return NIL_RTVFSOBJ; +} + + +RTDECL(int) RTVfsObjOpen(RTVFS hVfs, const char *pszPath, uint64_t fFileOpen, uint32_t fObjFlags, PRTVFSOBJ phVfsObj) +{ + /* + * Validate input. + */ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER); + + int rc = rtFileRecalcAndValidateFlags(&fFileOpen); + if (RT_FAILURE(rc)) + return rc; + AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK) + && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY, + ("fObjFlags=%#x\n", fObjFlags), + VERR_INVALID_FLAGS); + /* + * Parse the path, assume current directory is root since we've got no + * caller context here. + */ + PRTVFSPARSEDPATH pPath; + rc = RTVfsParsePathA(pszPath, "/", &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsTraverseToParent(pThis, pPath, (fObjFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + for (uint32_t cLoops = 1; ; cLoops++) + { + /* If we end with a directory slash, adjust open flags. */ + if (pPath->fDirSlash) + { + fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY; + if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY) + fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING; + } + if (fObjFlags & RTPATH_F_FOLLOW_LINK) + fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK; + + /* Open it. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fFileOpen, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* We're done if we don't follow links or this wasn't a link. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(*phVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsObj = hVfsObj; + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsObjQueryInfo(RTVFSOBJ hVfsObj, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE); + + RTVfsLockAcquireRead(pThis->hLock); + int rc = pThis->pOps->pfnQueryInfo(pThis->pvThis, pObjInfo, enmAddAttr); + RTVfsLockReleaseRead(pThis->hLock); + return rc; +} + + +/** + * Gets the RTVFSOBJSETOPS for the given base object. + * + * @returns Pointer to the vtable if supported by the type, otherwise NULL. + * @param pThis The base object. + */ +static PCRTVFSOBJSETOPS rtVfsObjGetSetOps(RTVFSOBJINTERNAL *pThis) +{ + switch (pThis->pOps->enmType) + { + case RTVFSOBJTYPE_DIR: + return &RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base)->pOps->ObjSet; + case RTVFSOBJTYPE_FILE: + return &RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base)->pOps->ObjSet; + case RTVFSOBJTYPE_SYMLINK: + return &RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base)->pOps->ObjSet; + default: + return NULL; + } +} + + +RTDECL(int) RTVfsObjSetMode(RTVFSOBJ hVfsObj, RTFMODE fMode, RTFMODE fMask) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE); + + fMode = rtFsModeNormalize(fMode, NULL, 0, 0); + if (!rtFsModeIsValid(fMode)) + return VERR_INVALID_PARAMETER; + + PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis); + AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION); + + int rc; + if (pObjSetOps->pfnSetMode) + { + RTVfsLockAcquireWrite(pThis->hLock); + rc = pObjSetOps->pfnSetMode(pThis->pvThis, fMode, fMask); + RTVfsLockReleaseWrite(pThis->hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(int) RTVfsObjSetTimes(RTVFSOBJ hVfsObj, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE); + + AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER); + + PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis); + AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION); + + int rc; + if (pObjSetOps->pfnSetTimes) + { + RTVfsLockAcquireWrite(pThis->hLock); + rc = pObjSetOps->pfnSetTimes(pThis->pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime); + RTVfsLockReleaseWrite(pThis->hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(int) RTVfsObjSetOwner(RTVFSOBJ hVfsObj, RTUID uid, RTGID gid) +{ + RTVFSOBJINTERNAL *pThis = hVfsObj; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE); + + PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis); + AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION); + + int rc; + if (pObjSetOps->pfnSetOwner) + { + RTVfsLockAcquireWrite(pThis->hLock); + rc = pObjSetOps->pfnSetOwner(pThis->pvThis, uid, gid); + RTVfsLockReleaseWrite(pThis->hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +/* + * + * U T I L U T I L U T I L + * U T I L U T I L U T I L + * U T I L U T I L U T I L + * + */ + + +RTDECL(int) RTVfsParsePathAppend(PRTVFSPARSEDPATH pPath, const char *pszPath, uint16_t *piRestartComp) +{ + AssertReturn(*pszPath != '/' && *pszPath != '\\', VERR_INTERNAL_ERROR_4); + + /* In case *piRestartComp was set higher than the number of components + before making the call to this function. */ + if (piRestartComp && *piRestartComp + 1 >= pPath->cComponents) + *piRestartComp = pPath->cComponents > 0 ? pPath->cComponents - 1 : 0; + +/** @todo The '..' handling doesn't really work wrt to symbolic links in the + * path. */ + + /* + * Append a slash to the destination path if necessary. + */ + char * const pszDst = pPath->szPath; + size_t offDst = pPath->cch; + if (pPath->cComponents > 0) + { + pszDst[offDst++] = '/'; + if (offDst >= RTVFSPARSEDPATH_MAX) + return VERR_FILENAME_TOO_LONG; + } + if (pPath->fAbsolute) + Assert(pszDst[offDst - 1] == '/' && pszDst[0] == '/'); + else + Assert(offDst == 0 || (pszDst[0] != '/' && pszDst[offDst - 1] == '/')); + + /* + * Parse and append the relative path. + */ + const char *pszSrc = pszPath; + pPath->fDirSlash = false; + for (;;) + { + /* Copy until we encounter the next slash. */ + pPath->aoffComponents[pPath->cComponents++] = (uint16_t)offDst; + for (;;) + { + char ch = *pszSrc++; + if ( ch != '/' + && ch != '\\' + && ch != '\0') + { + pszDst[offDst++] = ch; + if (offDst < RTVFSPARSEDPATH_MAX) + { /* likely */ } + else + return VERR_FILENAME_TOO_LONG; + } + else + { + /* Deal with dot components before we processes the slash/end. */ + if (pszDst[offDst - 1] == '.') + { + if ( offDst == 1 + || pszDst[offDst - 2] == '/') + { + pPath->cComponents--; + offDst = pPath->aoffComponents[pPath->cComponents]; + } + else if ( offDst > 3 + && pszDst[offDst - 2] == '.' + && pszDst[offDst - 3] == '/') + { + if ( pPath->fAbsolute + || offDst < 5 + || pszDst[offDst - 4] != '.' + || pszDst[offDst - 5] != '.' + || (offDst >= 6 && pszDst[offDst - 6] != '/') ) + { + pPath->cComponents -= pPath->cComponents > 1 ? 2 : 1; + offDst = pPath->aoffComponents[pPath->cComponents]; + if (piRestartComp && *piRestartComp + 1 >= pPath->cComponents) + *piRestartComp = pPath->cComponents > 0 ? pPath->cComponents - 1 : 0; + } + } + } + + if (ch != '\0') + { + /* Skip unnecessary slashes and check for end of path. */ + while ((ch = *pszSrc) == '/' || ch == '\\') + pszSrc++; + + if (ch == '\0') + pPath->fDirSlash = true; + } + + if (ch == '\0') + { + /* Drop trailing slash unless it's the root slash. */ + if ( offDst > 0 + && pszDst[offDst - 1] == '/' + && ( !pPath->fAbsolute + || offDst > 1)) + offDst--; + + /* Terminate the string and enter its length. */ + pszDst[offDst] = '\0'; + pszDst[offDst + 1] = '\0'; /* for aoffComponents[pPath->cComponents] */ + pPath->cch = (uint16_t)offDst; + pPath->aoffComponents[pPath->cComponents] = (uint16_t)(offDst + 1); + return VINF_SUCCESS; + } + + /* Append component separator before continuing with the next component. */ + if (offDst > 0 && pszDst[offDst - 1] != '/') + pszDst[offDst++] = '/'; + if (offDst >= RTVFSPARSEDPATH_MAX) + return VERR_FILENAME_TOO_LONG; + break; + } + } + } +} + + +/** @todo Replace RTVfsParsePath with RTPathParse and friends? */ +RTDECL(int) RTVfsParsePath(PRTVFSPARSEDPATH pPath, const char *pszPath, const char *pszCwd) +{ + if (*pszPath != '/' && *pszPath != '\\') + { + if (pszCwd) + { + /* + * Relative with a CWD. + */ + int rc = RTVfsParsePath(pPath, pszCwd, NULL /*crash if pszCwd is not absolute*/); + if (RT_FAILURE(rc)) + return rc; + } + else + { + /* + * Relative. + */ + pPath->cch = 0; + pPath->cComponents = 0; + pPath->fDirSlash = false; + pPath->fAbsolute = false; + pPath->aoffComponents[0] = 0; + pPath->aoffComponents[1] = 1; + pPath->szPath[0] = '\0'; + pPath->szPath[1] = '\0'; + } + } + else + { + /* + * Make pszPath relative, i.e. set up pPath for the root and skip + * leading slashes in pszPath before appending it. + */ + pPath->cch = 1; + pPath->cComponents = 0; + pPath->fDirSlash = false; + pPath->fAbsolute = true; + pPath->aoffComponents[0] = 1; + pPath->aoffComponents[1] = 2; + pPath->szPath[0] = '/'; + pPath->szPath[1] = '\0'; + pPath->szPath[2] = '\0'; + while (pszPath[0] == '/' || pszPath[0] == '\\') + pszPath++; + if (!pszPath[0]) + return VINF_SUCCESS; + } + return RTVfsParsePathAppend(pPath, pszPath, NULL); +} + + + +RTDECL(int) RTVfsParsePathA(const char *pszPath, const char *pszCwd, PRTVFSPARSEDPATH *ppPath) +{ + /* + * Allocate the output buffer and hand the problem to rtVfsParsePath. + */ + int rc; + PRTVFSPARSEDPATH pPath = (PRTVFSPARSEDPATH)RTMemTmpAlloc(sizeof(RTVFSPARSEDPATH)); + if (pPath) + { + rc = RTVfsParsePath(pPath, pszPath, pszCwd); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pPath); + pPath = NULL; + } + } + else + rc = VERR_NO_TMP_MEMORY; + *ppPath = pPath; /* always set it */ + return rc; +} + + +RTDECL(void) RTVfsParsePathFree(PRTVFSPARSEDPATH pPath) +{ + if (pPath) + { + pPath->cch = UINT16_MAX; + pPath->cComponents = UINT16_MAX; + pPath->aoffComponents[0] = UINT16_MAX; + pPath->aoffComponents[1] = UINT16_MAX; + RTMemTmpFree(pPath); + } +} + + +/** + * Handles a symbolic link, adding it to + * + * @returns IPRT status code. + * @param ppCurDir The current directory variable. We change it if + * the symbolic links is absolute. + * @param pPath The parsed path to update. + * @param iPathComponent The current path component. + * @param hSymlink The symbolic link to process. + */ +static int rtVfsTraverseHandleSymlink(RTVFSDIRINTERNAL **ppCurDir, PRTVFSPARSEDPATH pPath, + uint16_t iPathComponent, RTVFSSYMLINK hSymlink) +{ + /* + * Read the link and append the trailing path to it. + */ + char szPath[RTPATH_MAX]; + int rc = RTVfsSymlinkRead(hSymlink, szPath, sizeof(szPath) - 1); + if (RT_SUCCESS(rc)) + { + szPath[sizeof(szPath) - 1] = '\0'; + if (iPathComponent + 1 < pPath->cComponents) + rc = RTPathAppend(szPath, sizeof(szPath), &pPath->szPath[pPath->aoffComponents[iPathComponent + 1]]); + } + if (RT_SUCCESS(rc)) + { + /* + * Special hack help vfsstddir.cpp deal with symbolic links. + */ + RTVFSDIRINTERNAL *pCurDir = *ppCurDir; + char *pszPath = szPath; + if (pCurDir->pOps->pfnFollowAbsoluteSymlink) + { + size_t cchRoot = rtPathRootSpecLen(szPath); + if (cchRoot > 0) + { + pszPath = &szPath[cchRoot]; + char const chSaved = *pszPath; + *pszPath = '\0'; + RTVFSDIRINTERNAL *pVfsRootDir; + RTVfsLockAcquireWrite(pCurDir->Base.hLock); + rc = pCurDir->pOps->pfnFollowAbsoluteSymlink(pCurDir, szPath, &pVfsRootDir); + RTVfsLockAcquireWrite(pCurDir->Base.hLock); + *pszPath = chSaved; + if (RT_SUCCESS(rc)) + { + RTVfsDirRelease(pCurDir); + *ppCurDir = pCurDir = pVfsRootDir; + } + else if (rc == VERR_PATH_IS_RELATIVE) + pszPath = szPath; + else + return rc; + } + } + + rc = RTVfsParsePath(pPath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Deal with absolute references in a VFS setup. + * Note! The current approach only correctly handles this on root volumes. + */ + if ( pPath->fAbsolute + && pCurDir->Base.hVfs != NIL_RTVFS) /** @todo This needs fixing once we implement mount points. */ + { + RTVFSINTERNAL *pVfs = pCurDir->Base.hVfs; + RTVFSDIRINTERNAL *pVfsRootDir; + RTVfsLockAcquireRead(pVfs->Base.hLock); + rc = pVfs->pOps->pfnOpenRoot(pVfs->Base.pvThis, &pVfsRootDir); + RTVfsLockReleaseRead(pVfs->Base.hLock); + if (RT_SUCCESS(rc)) + { + RTVfsDirRelease(pCurDir); + *ppCurDir = pCurDir = pVfsRootDir; + } + else + return rc; + } + } + } + else if (rc == VERR_BUFFER_OVERFLOW) + rc = VERR_FILENAME_TOO_LONG; + return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc; +} + + +/** + * Internal worker for various open functions as well as RTVfsTraverseToParent. + * + * + * @returns IPRT status code. + * @param pThis The VFS. + * @param pPath The parsed path. This may be changed as symbolic + * links are processed during the path traversal. If + * it contains zero components, a dummy component is + * added to assist the caller. + * @param fFlags RTPATH_F_XXX. + * @param ppVfsParentDir Where to return the parent directory handle + * (referenced). + */ +static int rtVfsDirTraverseToParent(RTVFSDIRINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags, + RTVFSDIRINTERNAL **ppVfsParentDir) +{ + /* + * Assert sanity. + */ + AssertPtr(pThis); + Assert(pThis->uMagic == RTVFSDIR_MAGIC); + Assert(pThis->Base.cRefs > 0); + AssertPtr(pPath); + AssertPtr(ppVfsParentDir); + *ppVfsParentDir = NULL; + Assert(RTPATH_F_IS_VALID(fFlags, 0)); + + /* + * Start with the pThis directory. + */ + if (RTVfsDirRetain(pThis) == UINT32_MAX) + return VERR_INVALID_HANDLE; + RTVFSDIRINTERNAL *pCurDir = pThis; + + /* + * Special case for traversing zero components. + * We fake up a "./" in the pPath to help the caller along. + */ + if (pPath->cComponents == 0) + { + pPath->fDirSlash = true; + pPath->szPath[0] = '.'; + pPath->szPath[1] = '\0'; + pPath->szPath[2] = '\0'; + pPath->cch = 1; + pPath->cComponents = 1; + pPath->aoffComponents[0] = 0; + pPath->aoffComponents[1] = 1; + pPath->aoffComponents[2] = 1; + + *ppVfsParentDir = pCurDir; + return VINF_SUCCESS; + } + + + /* + * The traversal loop. + */ + int rc = VINF_SUCCESS; + unsigned cLinks = 0; + uint16_t iComponent = 0; + for (;;) + { + /* + * Are we done yet? + */ + bool fFinal = iComponent + 1 >= pPath->cComponents; + if (fFinal && (fFlags & RTPATH_F_ON_LINK)) + { + *ppVfsParentDir = pCurDir; + return VINF_SUCCESS; + } + + /* + * Try open the next entry. + */ + const char *pszEntry = &pPath->szPath[pPath->aoffComponents[iComponent]]; + char *pszEntryEnd = &pPath->szPath[pPath->aoffComponents[iComponent + 1] - 1]; + *pszEntryEnd = '\0'; + RTVFSDIR hDir = NIL_RTVFSDIR; + RTVFSSYMLINK hSymlink = NIL_RTVFSSYMLINK; + RTVFS hVfsMnt = NIL_RTVFS; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + if (fFinal) + { + RTVfsLockAcquireRead(pCurDir->Base.hLock); + rc = pCurDir->pOps->pfnOpen(pCurDir->Base.pvThis, pszEntry, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING + | RTVFSOBJ_F_TRAVERSAL | RTPATH_F_ON_LINK, + &hVfsObj); + RTVfsLockReleaseRead(pCurDir->Base.hLock); + *pszEntryEnd = '\0'; + if (RT_FAILURE(rc)) + { + if ( rc == VERR_PATH_NOT_FOUND + || rc == VERR_FILE_NOT_FOUND + || rc == VERR_IS_A_DIRECTORY + || rc == VERR_IS_A_FILE + || rc == VERR_IS_A_FIFO + || rc == VERR_IS_A_SOCKET + || rc == VERR_IS_A_CHAR_DEVICE + || rc == VERR_IS_A_BLOCK_DEVICE + || rc == VERR_NOT_SYMLINK) + { + *ppVfsParentDir = pCurDir; + return VINF_SUCCESS; + } + break; + } + hSymlink = RTVfsObjToSymlink(hVfsObj); + Assert(hSymlink != NIL_RTVFSSYMLINK); + } + else + { + RTVfsLockAcquireRead(pCurDir->Base.hLock); + rc = pCurDir->pOps->pfnOpen(pCurDir->Base.pvThis, pszEntry, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_OPEN_MOUNT + | RTVFSOBJ_F_CREATE_NOTHING | RTVFSOBJ_F_TRAVERSAL | RTPATH_F_ON_LINK, + &hVfsObj); + RTVfsLockReleaseRead(pCurDir->Base.hLock); + *pszEntryEnd = '/'; + if (RT_FAILURE(rc)) + { + if (rc == VERR_FILE_NOT_FOUND) + rc = VERR_PATH_NOT_FOUND; + break; + } + hDir = RTVfsObjToDir(hVfsObj); + hSymlink = RTVfsObjToSymlink(hVfsObj); + hVfsMnt = RTVfsObjToVfs(hVfsObj); + } + Assert( (hDir != NIL_RTVFSDIR && hSymlink == NIL_RTVFSSYMLINK && hVfsMnt == NIL_RTVFS) + || (hDir == NIL_RTVFSDIR && hSymlink != NIL_RTVFSSYMLINK && hVfsMnt == NIL_RTVFS) + || (hDir == NIL_RTVFSDIR && hSymlink == NIL_RTVFSSYMLINK && hVfsMnt != NIL_RTVFS)); + RTVfsObjRelease(hVfsObj); + + if (hDir != NIL_RTVFSDIR) + { + /* + * Directory - advance down the path. + */ + AssertPtr(hDir); + Assert(hDir->uMagic == RTVFSDIR_MAGIC); + RTVfsDirRelease(pCurDir); + pCurDir = hDir; + iComponent++; + } + else if (hSymlink != NIL_RTVFSSYMLINK) + { + /* + * Symbolic link - deal with it and retry the current component. + */ + AssertPtr(hSymlink); + Assert(hSymlink->uMagic == RTVFSSYMLINK_MAGIC); + if (fFlags & RTPATH_F_NO_SYMLINKS) + { + rc = VERR_SYMLINK_NOT_ALLOWED; + break; + } + cLinks++; + if (cLinks >= RTVFS_MAX_LINKS) + { + rc = VERR_TOO_MANY_SYMLINKS; + break; + } + rc = rtVfsTraverseHandleSymlink(&pCurDir, pPath, iComponent, hSymlink); + if (RT_FAILURE(rc)) + break; + iComponent = 0; + } + else + { + /* + * Mount point - deal with it and retry the current component. + */ + RTVfsDirRelease(pCurDir); + RTVfsLockAcquireRead(hVfsMnt->Base.hLock); + rc = hVfsMnt->pOps->pfnOpenRoot(hVfsMnt->Base.pvThis, &pCurDir); + RTVfsLockReleaseRead(hVfsMnt->Base.hLock); + if (RT_FAILURE(rc)) + { + pCurDir = NULL; + break; + } + iComponent = 0; + /** @todo union mounts. */ + } + } + + if (pCurDir) + RTVfsDirRelease(pCurDir); + + return rc; +} + + +/** + * Internal worker for various open functions as well as RTVfsTraverseToParent. + * + * @returns IPRT status code. + * @param pThis The VFS. + * @param pPath The parsed path. This may be changed as symbolic + * links are processed during the path traversal. + * @param fFlags RTPATH_F_XXX. + * @param ppVfsParentDir Where to return the parent directory handle + * (referenced). + */ +static int rtVfsTraverseToParent(RTVFSINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags, RTVFSDIRINTERNAL **ppVfsParentDir) +{ + /* + * Assert sanity. + */ + AssertPtr(pThis); + Assert(pThis->uMagic == RTVFS_MAGIC); + Assert(pThis->Base.cRefs > 0); + AssertPtr(pPath); + AssertPtr(ppVfsParentDir); + *ppVfsParentDir = NULL; + Assert(RTPATH_F_IS_VALID(fFlags, 0)); + + /* + * Open the root directory and join paths with the directory traversal. + */ + /** @todo Union mounts, traversal optimization methods, races, ++ */ + RTVFSDIRINTERNAL *pRootDir; + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnOpenRoot(pThis->Base.pvThis, &pRootDir); + RTVfsLockReleaseRead(pThis->Base.hLock); + if (RT_SUCCESS(rc)) + { + rc = rtVfsDirTraverseToParent(pRootDir, pPath, fFlags, ppVfsParentDir); + RTVfsDirRelease(pRootDir); + } + return rc; +} + + + +/** + * Follows a symbolic link object to the next parent directory. + * + * @returns IPRT status code + * @param ppVfsParentDir Pointer to the parent directory of @a hVfsObj on + * input, the parent directory of the link target on + * return. + * @param hVfsObj Symbolic link object handle. + * @param pPath Path buffer to use parse the symbolic link target. + * @param fFlags See rtVfsDirTraverseToParent. + */ +static int rtVfsDirFollowSymlinkObjToParent(RTVFSDIRINTERNAL **ppVfsParentDir, RTVFSOBJ hVfsObj, + PRTVFSPARSEDPATH pPath, uint32_t fFlags) +{ + RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj); + AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_INTERNAL_ERROR_3); + + int rc = rtVfsTraverseHandleSymlink(ppVfsParentDir, pPath, pPath->cComponents, hVfsSymlink); + if (RT_SUCCESS(rc)) + { + RTVFSDIRINTERNAL *pVfsStartDir = *ppVfsParentDir; + rc = rtVfsDirTraverseToParent(pVfsStartDir, pPath, fFlags, ppVfsParentDir); + RTVfsDirRelease(pVfsStartDir); + } + + RTVfsSymlinkRelease(hVfsSymlink); + return rc; +} + + + +RTDECL(int) RTVfsUtilDummyPollOne(uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) +{ + NOREF(fEvents); + int rc; + if (fIntr) + rc = RTThreadSleep(cMillies); + else + { + uint64_t uMsStart = RTTimeMilliTS(); + do + rc = RTThreadSleep(cMillies); + while ( rc == VERR_INTERRUPTED + && !fIntr + && RTTimeMilliTS() - uMsStart < cMillies); + if (rc == VERR_INTERRUPTED) + rc = VERR_TIMEOUT; + } + + *pfRetEvents = 0; + return rc; +} + + +RTDECL(int) RTVfsUtilPumpIoStreams(RTVFSIOSTREAM hVfsIosSrc, RTVFSIOSTREAM hVfsIosDst, size_t cbBufHint) +{ + /* + * Allocate a temporary buffer. + */ + size_t cbBuf = cbBufHint; + if (!cbBuf) + cbBuf = _64K; + else if (cbBuf < _4K) + cbBuf = _4K; + else if (cbBuf > _1M) + cbBuf = _1M; + + void *pvBuf = RTMemTmpAlloc(cbBuf); + if (!pvBuf) + { + cbBuf = _4K; + pvBuf = RTMemTmpAlloc(cbBuf); + if (!pvBuf) + return VERR_NO_TMP_MEMORY; + } + + /* + * Pump loop. + */ + int rc; + for (;;) + { + size_t cbRead; + rc = RTVfsIoStrmRead(hVfsIosSrc, pvBuf, cbBuf, true /*fBlocking*/, &cbRead); + if (RT_FAILURE(rc)) + break; + if (rc == VINF_EOF && cbRead == 0) + break; + + rc = RTVfsIoStrmWrite(hVfsIosDst, pvBuf, cbRead, true /*fBlocking*/, NULL /*cbWritten*/); + if (RT_FAILURE(rc)) + break; + } + + RTMemTmpFree(pvBuf); + + /* + * Flush the destination stream on success to make sure we've caught + * errors caused by buffering delays. + */ + if (RT_SUCCESS(rc)) + rc = RTVfsIoStrmFlush(hVfsIosDst); + + return rc; +} + + + + + +/* + * F I L E S Y S T E M R O O T + * F I L E S Y S T E M R O O T + * F I L E S Y S T E M R O O T + */ + + +RTDECL(int) RTVfsNew(PCRTVFSOPS pVfsOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFS phVfs, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pVfsOps); + AssertReturn(pVfsOps->uVersion == RTVFSOPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pVfsOps->uEndMarker == RTVFSOPS_VERSION, VERR_VERSION_MISMATCH); + RTVFSOBJ_ASSERT_OPS(&pVfsOps->Obj, RTVFSOBJTYPE_VFS); + Assert(cbInstance > 0); + AssertPtr(ppvInstance); + AssertPtr(phVfs); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSINTERNAL *pThis = (RTVFSINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Base, &pVfsOps->Obj, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFS_MAGIC; + pThis->pOps = pVfsOps; + + *phVfs = pThis; + *ppvInstance = pThis->Base.pvThis; + + LogFlow(("RTVfsNew -> VINF_SUCCESS; hVfs=%p pvThis=%p\n", pThis, pThis->Base.pvThis)); + return VINF_SUCCESS; +} + +#ifdef DEBUG +# undef RTVfsRetain +#endif +RTDECL(uint32_t) RTVfsRetain(RTVFS hVfs) +{ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX); + uint32_t cRefs = rtVfsObjRetain(&pThis->Base); + LogFlow(("RTVfsRetain(%p/%p) -> %d\n", pThis, pThis->Base.pvThis, cRefs)); + return cRefs; +} +#ifdef DEBUG +# define RTVfsRetain(hVfs) RTVfsRetainDebug(hVfs, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsRetainDebug(RTVFS hVfs, RT_SRC_POS_DECL) +{ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX); + RT_SRC_POS_NOREF(); + return rtVfsObjRetainDebug(&pThis->Base, "RTVfsRetainDebug", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsRelease(RTVFS hVfs) +{ + RTVFSINTERNAL *pThis = hVfs; + if (pThis == NIL_RTVFS) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX); +#ifdef LOG_ENABLED + void *pvThis = pThis->Base.pvThis; +#endif + uint32_t cRefs = rtVfsObjRelease(&pThis->Base); + Log(("RTVfsRelease(%p/%p) -> %d\n", pThis, pvThis, cRefs)); + return cRefs; +} + + +RTDECL(int) RTVfsOpenRoot(RTVFS hVfs, PRTVFSDIR phDir) +{ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(phDir, VERR_INVALID_POINTER); + *phDir = NIL_RTVFSDIR; + + if (!pThis->pOps->pfnOpenRoot) + return VERR_NOT_SUPPORTED; + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnOpenRoot(pThis->Base.pvThis, phDir); + RTVfsLockReleaseRead(pThis->Base.hLock); + + return rc; +} + + +RTDECL(int) RTVfsQueryPathInfo(RTVFS hVfs, const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Parse the path, assume current directory is root since we've got no + * caller context here. Then traverse to the parent directory. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszPath, "/", &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnQueryEntryInfo. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING; + for (uint32_t cLoops = 1; ; cLoops++) + { + /* If we end with a directory slash, adjust open flags. */ + if (pPath->fDirSlash) + { + fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY; + if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY) + fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING; + } + if (fObjFlags & RTPATH_F_FOLLOW_LINK) + fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK; + + /* Do the querying. If pfnQueryEntryInfo is available, we use it first, + falling back on pfnOpen in case of symbolic links that needs following. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if (pVfsParentDir->pOps->pfnQueryEntryInfo) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnQueryEntryInfo(pVfsParentDir->Base.pvThis, pszEntryName, pObjInfo, enmAddAttr); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + if ( !RTFS_IS_SYMLINK(pObjInfo->Attr.fMode) + || !(fFlags & RTPATH_F_FOLLOW_LINK)) + { + if ( (fObjFlags & RTVFSOBJ_F_OPEN_MASK) != RTVFSOBJ_F_OPEN_ANY + && !RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + rc = VERR_NOT_A_DIRECTORY; + break; + } + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, + RTFILE_O_ACCESS_ATTR_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, + fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAddAttr); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + + +RTDECL(int) RTVfsQueryRangeState(RTVFS hVfs, uint64_t off, size_t cb, bool *pfUsed) +{ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + + if (!pThis->pOps->pfnQueryRangeState) + return VERR_NOT_SUPPORTED; + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnQueryRangeState(pThis->Base.pvThis, off, cb, pfUsed); + RTVfsLockReleaseRead(pThis->Base.hLock); + + return rc; +} + + + + +/* + * + * F I L E S Y S T E M S T R E A M + * F I L E S Y S T E M S T R E A M + * F I L E S Y S T E M S T R E A M + * + */ + + +RTDECL(int) RTVfsNewFsStream(PCRTVFSFSSTREAMOPS pFsStreamOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock, bool fReadOnly, + PRTVFSFSSTREAM phVfsFss, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pFsStreamOps); + AssertReturn(pFsStreamOps->uVersion == RTVFSFSSTREAMOPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pFsStreamOps->uEndMarker == RTVFSFSSTREAMOPS_VERSION, VERR_VERSION_MISMATCH); + Assert(!pFsStreamOps->fReserved); + RTVFSOBJ_ASSERT_OPS(&pFsStreamOps->Obj, RTVFSOBJTYPE_FS_STREAM); + if (fReadOnly) + AssertPtr(pFsStreamOps->pfnNext); + else + { + AssertPtr(pFsStreamOps->pfnAdd); + AssertPtr(pFsStreamOps->pfnEnd); + } + Assert(cbInstance > 0); + AssertPtr(ppvInstance); + AssertPtr(phVfsFss); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSFSSTREAMINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSFSSTREAMINTERNAL *pThis = (RTVFSFSSTREAMINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Base, &pFsStreamOps->Obj, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFSFSSTREAM_MAGIC; + pThis->fFlags = fReadOnly + ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE + : RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL; + pThis->pOps = pFsStreamOps; + + *phVfsFss = pThis; + *ppvInstance = pThis->Base.pvThis; + return VINF_SUCCESS; +} + + +#ifdef DEBUG +# undef RTVfsFsStrmRetain +#endif +RTDECL(uint32_t) RTVfsFsStrmRetain(RTVFSFSSTREAM hVfsFss) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRetain(&pThis->Base); +} +#ifdef DEBUG +# define RTVfsFsStrmRetain(hVfsFss) RTVfsFsStrmRetainDebug(hVfsFss, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsFsStrmRetainDebug(RTVFSFSSTREAM hVfsFss, RT_SRC_POS_DECL) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRetainDebug(&pThis->Base, "RTVfsFsStrmRetain", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsFsStrmRelease(RTVFSFSSTREAM hVfsFss) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + if (pThis == NIL_RTVFSFSSTREAM) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRelease(&pThis->Base); +} + + +RTDECL(int) RTVfsFsStrmQueryInfo(RTVFSFSSTREAM hVfsFss, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE); + return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr); +} + + +RTDECL(int) RTVfsFsStrmNext(RTVFSFSSTREAM hVfsFss, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNullReturn(ppszName, VERR_INVALID_POINTER); + if (ppszName) + *ppszName = NULL; + AssertPtrNullReturn(penmType, VERR_INVALID_POINTER); + if (penmType) + *penmType = RTVFSOBJTYPE_INVALID; + AssertPtrNullReturn(penmType, VERR_INVALID_POINTER); + if (phVfsObj) + *phVfsObj = NIL_RTVFSOBJ; + + AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_INVALID_FUNCTION); + + return pThis->pOps->pfnNext(pThis->Base.pvThis, ppszName, penmType, phVfsObj); +} + + +RTDECL(int) RTVfsFsStrmAdd(RTVFSFSSTREAM hVfsFss, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath != '\0', VERR_INVALID_NAME); + AssertPtrReturn(hVfsObj, VERR_INVALID_HANDLE); + AssertReturn(hVfsObj->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fFlags & ~RTVFSFSSTRM_ADD_F_VALID_MASK), VERR_INVALID_FLAGS); + AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_INVALID_FUNCTION); + + return pThis->pOps->pfnAdd(pThis->Base.pvThis, pszPath, hVfsObj, fFlags); +} + + +RTDECL(int) RTVfsFsStrmPushFile(RTVFSFSSTREAM hVfsFss, const char *pszPath, uint64_t cbFile, + PCRTFSOBJINFO paObjInfo, uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER); + *phVfsIos = NIL_RTVFSIOSTREAM; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE); + + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath != '\0', VERR_INVALID_NAME); + + AssertReturn(!(fFlags & ~RTVFSFSSTRM_PUSH_F_VALID_MASK), VERR_INVALID_FLAGS); + AssertReturn(RT_BOOL(cbFile == UINT64_MAX) == RT_BOOL(fFlags & RTVFSFSSTRM_PUSH_F_STREAM), VERR_INVALID_FLAGS); + + if (cObjInfo) + { + AssertPtrReturn(paObjInfo, VERR_INVALID_POINTER); + AssertReturn(paObjInfo[0].Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER); + } + + AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_INVALID_FUNCTION); + if (pThis->pOps->pfnPushFile) + return pThis->pOps->pfnPushFile(pThis->Base.pvThis, pszPath, cbFile, paObjInfo, cObjInfo, fFlags, phVfsIos); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTVfsFsStrmEnd(RTVFSFSSTREAM hVfsFss) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE); + + return pThis->pOps->pfnEnd(pThis->Base.pvThis); +} + + +RTDECL(void *) RTVfsFsStreamToPrivate(RTVFSFSSTREAM hVfsFss, PCRTVFSFSSTREAMOPS pFsStreamOps) +{ + RTVFSFSSTREAMINTERNAL *pThis = hVfsFss; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, NULL); + if (pThis->pOps != pFsStreamOps) + return NULL; + return pThis->Base.pvThis; +} + + +/* + * + * D I R D I R D I R + * D I R D I R D I R + * D I R D I R D I R + * + */ + + +RTDECL(int) RTVfsNewDir(PCRTVFSDIROPS pDirOps, size_t cbInstance, uint32_t fFlags, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFSDIR phVfsDir, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pDirOps); + AssertReturn(pDirOps->uVersion == RTVFSDIROPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pDirOps->uEndMarker == RTVFSDIROPS_VERSION, VERR_VERSION_MISMATCH); + Assert(!pDirOps->fReserved); + RTVFSDIR_ASSERT_OPS(pDirOps, RTVFSOBJTYPE_DIR); + Assert(cbInstance > 0); + AssertReturn(!(fFlags & ~RTVFSDIR_F_NO_VFS_REF), VERR_INVALID_FLAGS); + AssertPtr(ppvInstance); + AssertPtr(phVfsDir); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSDIRINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSDIRINTERNAL *pThis = (RTVFSDIRINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Base, &pDirOps->Obj, hVfs, RT_BOOL(fFlags & RTVFSDIR_F_NO_VFS_REF), hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFSDIR_MAGIC; + pThis->fReserved = 0; + pThis->pOps = pDirOps; + + *phVfsDir = pThis; + *ppvInstance = pThis->Base.pvThis; + return VINF_SUCCESS; +} + + +RTDECL(void *) RTVfsDirToPrivate(RTVFSDIR hVfsDir, PCRTVFSDIROPS pDirOps) +{ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, NULL); + if (pThis->pOps != pDirOps) + return NULL; + return pThis->Base.pvThis; +} + + +#ifdef DEBUG +# undef RTVfsDirRetain +#endif +RTDECL(uint32_t) RTVfsDirRetain(RTVFSDIR hVfsDir) +{ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX); + uint32_t cRefs = rtVfsObjRetain(&pThis->Base); + LogFlow(("RTVfsDirRetain(%p/%p) -> %#x\n", pThis, pThis->Base.pvThis, cRefs)); + return cRefs; +} +#ifdef DEBUG +# define RTVfsDirRetain(hVfsDir) RTVfsDirRetainDebug(hVfsDir, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsDirRetainDebug(RTVFSDIR hVfsDir, RT_SRC_POS_DECL) +{ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX); + return rtVfsObjRetainDebug(&pThis->Base, "RTVfsDirRetain", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsDirRelease(RTVFSDIR hVfsDir) +{ + RTVFSDIRINTERNAL *pThis = hVfsDir; + if (pThis == NIL_RTVFSDIR) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX); +#ifdef LOG_ENABLED + void *pvThis = pThis->Base.pvThis; +#endif + uint32_t cRefs = rtVfsObjRelease(&pThis->Base); + LogFlow(("RTVfsDirRelease(%p/%p) -> %#x\n", pThis, pvThis, cRefs)); + return cRefs; +} + + +RTDECL(int) RTVfsDirOpen(RTVFS hVfs, const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir) +{ + /* + * Validate input. + */ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); /** @todo sort out flags! */ + + /* + * Parse the path, assume current directory is root since we've got no + * caller context here. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszPath, "/", &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN; + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING; + for (uint32_t cLoops = 1; ; cLoops++) + { + /* Do the querying. If pfnOpenDir is available, we use it first, falling + back on pfnOpen in case of symbolic links that needs following. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if (pVfsParentDir->pOps->pfnOpenDir) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpenDir(pVfsParentDir->Base.pvThis, pszEntryName, fFlags, phVfsDir); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if ( RT_SUCCESS(rc) + || ( rc != VERR_NOT_A_DIRECTORY + && rc != VERR_IS_A_SYMLINK)) + break; + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsDir = RTVfsObjToDir(hVfsObj); + AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirOpenDir(RTVFSDIR hVfsDir, const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); /** @todo sort out flags! */ + + /* + * Parse the path, it's always relative to the given directory. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + uint32_t const fTraverse = (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK; + rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN; + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING | fTraverse; + for (uint32_t cLoops = 1; ; cLoops++) + { + /* Do the querying. If pfnOpenDir is available, we use it first, falling + back on pfnOpen in case of symbolic links that needs following. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if (pVfsParentDir->pOps->pfnOpenDir) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpenDir(pVfsParentDir->Base.pvThis, pszEntryName, fFlags, phVfsDir); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if ( RT_SUCCESS(rc) + || ( rc != VERR_NOT_A_DIRECTORY + && rc != VERR_IS_A_SYMLINK)) + break; + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsDir = RTVfsObjToDir(hVfsObj); + AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirCreateDir(RTVFSDIR hVfsDir, const char *pszRelPath, RTFMODE fMode, uint32_t fFlags, PRTVFSDIR phVfsDir) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszRelPath, VERR_INVALID_POINTER); + AssertPtrNullReturn(phVfsDir, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTDIRCREATE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + fMode = rtFsModeNormalize(fMode, pszRelPath, 0, RTFS_TYPE_DIRECTORY); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + if (!(fFlags & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET)) + fMode |= RTFS_DOS_NT_NOT_CONTENT_INDEXED; + + /* + * Parse the path, it's always relative to the given directory. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszRelPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + uint32_t fTraverse = (fFlags & RTDIRCREATE_FLAGS_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK; + rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_CREATE + | ((fMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK); + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_DIRECTORY | fTraverse; + for (uint32_t cLoops = 1; ; cLoops++) + { + /* Do the querying. If pfnOpenDir is available, we use it first, falling + back on pfnOpen in case of symbolic links that needs following. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if (pVfsParentDir->pOps->pfnCreateDir) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnCreateDir(pVfsParentDir->Base.pvThis, pszEntryName, fMode, phVfsDir); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if ( RT_SUCCESS(rc) + || ( rc != VERR_NOT_A_DIRECTORY + && rc != VERR_IS_A_SYMLINK)) + break; + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + if (phVfsDir) + { + *phVfsDir = RTVfsObjToDir(hVfsObj); + AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3); + } + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirOpenFile(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fOpen, PRTVFSFILE phVfsFile) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER); + + int rc = rtFileRecalcAndValidateFlags(&fOpen); + if (RT_FAILURE(rc)) + return rc; + + /* + * Parse the path, it's always relative to the given directory. + */ + PRTVFSPARSEDPATH pPath; + rc = RTVfsParsePathA(pszPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenFile. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + uint32_t const fTraverse = (fOpen & RTFILE_O_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK; + rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /** @todo join path with RTVfsFileOpen. */ + + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + bool fDirSlash = pPath->fDirSlash; + + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY_FILE | RTVFSOBJ_F_OPEN_SYMLINK; + if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + fObjFlags |= RTVFSOBJ_F_CREATE_FILE; + else + fObjFlags |= RTVFSOBJ_F_CREATE_NOTHING; + fObjFlags |= fTraverse & RTPATH_F_MASK; + + for (uint32_t cLoops = 1;; cLoops++) + { + /* Do the querying. If pfnOpenFile is available, we use it first, falling + back on pfnOpen in case of symbolic links that needs following or we got + a trailing directory slash (to get file-not-found error). */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if ( pVfsParentDir->pOps->pfnOpenFile + && !fDirSlash) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpenFile(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, phVfsFile); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if ( RT_SUCCESS(rc) + || ( rc != VERR_NOT_A_FILE + && rc != VERR_IS_A_SYMLINK)) + break; + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsFile = RTVfsObjToFile(hVfsObj); + AssertStmt(*phVfsFile != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + fDirSlash |= pPath->fDirSlash; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirOpenFileAsIoStream(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fOpen, PRTVFSIOSTREAM phVfsIos) +{ + RTVFSFILE hVfsFile; + int rc = RTVfsDirOpenFile(hVfsDir, pszPath, fOpen, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsIos = RTVfsFileToIoStream(hVfsFile); + AssertStmt(*phVfsIos != NIL_RTVFSIOSTREAM, rc = VERR_INTERNAL_ERROR_2); + RTVfsFileRelease(hVfsFile); + } + return rc; +} + + +RTDECL(int) RTVfsDirOpenObj(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fFileOpen, uint32_t fObjFlags, PRTVFSOBJ phVfsObj) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER); + + int rc = rtFileRecalcAndValidateFlags(&fFileOpen); + if (RT_FAILURE(rc)) + return rc; + AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK) + && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY, + ("fObjFlags=%#x\n", fObjFlags), + VERR_INVALID_FLAGS); + + /* + * Parse the relative path. If it ends with a directory slash or it boils + * down to an empty path (i.e. re-opening hVfsDir), adjust the flags to only + * open/create directories. + */ + PRTVFSPARSEDPATH pPath; + rc = RTVfsParsePathA(pszPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsDirTraverseToParent(pThis, pPath, (fObjFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + for (uint32_t cLoops = 1;; cLoops++) + { + /* If we end with a directory slash, adjust open flags. */ + if (pPath->fDirSlash) + { + fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY; + if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY) + fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING; + } + if (fObjFlags & RTPATH_F_FOLLOW_LINK) + fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK; + + /* Open it. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fFileOpen, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* We're done if we don't follow links or this wasn't a link. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(*phVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsObj = hVfsObj; + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirQueryPathInfo(RTVFSDIR hVfsDir, const char *pszPath, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Parse the relative path. Then traverse to the parent directory. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsDirTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING; + for (uint32_t cLoops = 1;; cLoops++) + { + /* If we end with a directory slash, adjust open flags. */ + if (pPath->fDirSlash) + { + fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY; + if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY) + fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING; + } + if (fObjFlags & RTPATH_F_FOLLOW_LINK) + fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK; + + /* Do the querying. If pfnQueryEntryInfo is available, we use it first, + falling back on pfnOpen in case of symbolic links that needs following. */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if (pVfsParentDir->pOps->pfnQueryEntryInfo) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnQueryEntryInfo(pVfsParentDir->Base.pvThis, pszEntryName, pObjInfo, enmAddAttr); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + if ( !RTFS_IS_SYMLINK(pObjInfo->Attr.fMode) + || !(fFlags & RTPATH_F_FOLLOW_LINK)) + { + if ( (fObjFlags & RTVFSOBJ_F_OPEN_MASK) != RTVFSOBJ_F_OPEN_ANY + && !RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + rc = VERR_NOT_A_DIRECTORY; + break; + } + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, + RTFILE_O_ACCESS_ATTR_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, + fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAddAttr); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + } + + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; +} + + +RTDECL(int) RTVfsDirRemoveDir(RTVFSDIR hVfsDir, const char *pszRelPath, uint32_t fFlags) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszRelPath, VERR_INVALID_POINTER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Parse the path, it's always relative to the given directory. + */ + PRTVFSPARSEDPATH pPath; + int rc = RTVfsParsePathA(pszRelPath, NULL, &pPath); + if (RT_SUCCESS(rc)) + { + if (pPath->cComponents > 0) + { + /* + * Tranverse the path, resolving the parent node, not checking for symbolic + * links in the final element, and ask the directory to remove the subdir. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + rc = rtVfsDirTraverseToParent(pThis, pPath, RTPATH_F_ON_LINK, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnUnlinkEntry(pVfsParentDir->Base.pvThis, pszEntryName, RTFS_TYPE_DIRECTORY); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + + RTVfsDirRelease(pVfsParentDir); + } + } + else + rc = VERR_PATH_ZERO_LENGTH; + RTVfsParsePathFree(pPath); + } + return rc; +} + + + +RTDECL(int) RTVfsDirReadEx(RTVFSDIR hVfsDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER); + + size_t cbDirEntry = sizeof(*pDirEntry); + if (!pcbDirEntry) + pcbDirEntry = &cbDirEntry; + else + { + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]), + ("Invalid *pcbDirEntry=%d (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Call the directory method. + */ + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnReadDir(pThis->Base.pvThis, pDirEntry, pcbDirEntry, enmAddAttr); + RTVfsLockReleaseRead(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsDirRewind(RTVFSDIR hVfsDir) +{ + /* + * Validate input. + */ + RTVFSDIRINTERNAL *pThis = hVfsDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Call the directory method. + */ + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnRewindDir(pThis->Base.pvThis); + RTVfsLockReleaseRead(pThis->Base.hLock); + return rc; +} + + +/* + * + * S Y M B O L I C L I N K + * S Y M B O L I C L I N K + * S Y M B O L I C L I N K + * + */ + +RTDECL(int) RTVfsNewSymlink(PCRTVFSSYMLINKOPS pSymlinkOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFSSYMLINK phVfsSym, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pSymlinkOps); + AssertReturn(pSymlinkOps->uVersion == RTVFSSYMLINKOPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pSymlinkOps->uEndMarker == RTVFSSYMLINKOPS_VERSION, VERR_VERSION_MISMATCH); + Assert(!pSymlinkOps->fReserved); + RTVFSSYMLINK_ASSERT_OPS(pSymlinkOps, RTVFSOBJTYPE_SYMLINK); + Assert(cbInstance > 0); + AssertPtr(ppvInstance); + AssertPtr(phVfsSym); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSSYMLINKINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSSYMLINKINTERNAL *pThis = (RTVFSSYMLINKINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Base, &pSymlinkOps->Obj, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFSSYMLINK_MAGIC; + pThis->pOps = pSymlinkOps; + + *phVfsSym = pThis; + *ppvInstance = pThis->Base.pvThis; + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTVfsSymlinkRetain(RTVFSSYMLINK hVfsSym) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX); + return rtVfsObjRetain(&pThis->Base); +} + + +RTDECL(uint32_t) RTVfsSymlinkRetainDebug(RTVFSSYMLINK hVfsSym, RT_SRC_POS_DECL) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX); + return rtVfsObjRetainDebug(&pThis->Base, "RTVfsSymlinkRetainDebug", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsSymlinkRelease(RTVFSSYMLINK hVfsSym) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + if (pThis == NIL_RTVFSSYMLINK) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX); + return rtVfsObjRelease(&pThis->Base); +} + + +RTDECL(int) RTVfsSymlinkQueryInfo(RTVFSSYMLINK hVfsSym, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE); + return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr); +} + + +RTDECL(int) RTVfsSymlinkSetMode(RTVFSSYMLINK hVfsSym, RTFMODE fMode, RTFMODE fMask) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE); + + fMode = rtFsModeNormalize(fMode, NULL, 0, RTFS_TYPE_SYMLINK); + if (!rtFsModeIsValid(fMode)) + return VERR_INVALID_PARAMETER; + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->ObjSet.pfnSetMode(pThis->Base.pvThis, fMode, fMask); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsSymlinkSetTimes(RTVFSSYMLINK hVfsSym, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE); + + AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->ObjSet.pfnSetTimes(pThis->Base.pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsSymlinkSetOwner(RTVFSSYMLINK hVfsSym, RTUID uid, RTGID gid) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->ObjSet.pfnSetOwner(pThis->Base.pvThis, uid, gid); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsSymlinkRead(RTVFSSYMLINK hVfsSym, char *pszTarget, size_t cbTarget) +{ + RTVFSSYMLINKINTERNAL *pThis = hVfsSym; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, pszTarget, cbTarget); + RTVfsLockReleaseWrite(pThis->Base.hLock); + + return rc; +} + + + +/* + * + * I / O S T R E A M I / O S T R E A M I / O S T R E A M + * I / O S T R E A M I / O S T R E A M I / O S T R E A M + * I / O S T R E A M I / O S T R E A M I / O S T R E A M + * + */ + +RTDECL(int) RTVfsNewIoStream(PCRTVFSIOSTREAMOPS pIoStreamOps, size_t cbInstance, uint32_t fOpen, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFSIOSTREAM phVfsIos, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + AssertPtr(pIoStreamOps); + AssertReturn(pIoStreamOps->uVersion == RTVFSIOSTREAMOPS_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pIoStreamOps->uEndMarker == RTVFSIOSTREAMOPS_VERSION, VERR_VERSION_MISMATCH); + Assert(!(pIoStreamOps->fFeatures & ~RTVFSIOSTREAMOPS_FEAT_VALID_MASK)); + RTVFSIOSTREAM_ASSERT_OPS(pIoStreamOps, RTVFSOBJTYPE_IO_STREAM); + Assert(cbInstance > 0); + Assert(fOpen & RTFILE_O_ACCESS_MASK); + AssertPtr(ppvInstance); + AssertPtr(phVfsIos); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSIOSTREAMINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSIOSTREAMINTERNAL *pThis = (RTVFSIOSTREAMINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Base, &pIoStreamOps->Obj, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFSIOSTREAM_MAGIC; + pThis->fFlags = fOpen; + pThis->pOps = pIoStreamOps; + + *phVfsIos = pThis; + *ppvInstance = pThis->Base.pvThis; + return VINF_SUCCESS; +} + + +RTDECL(void *) RTVfsIoStreamToPrivate(RTVFSIOSTREAM hVfsIos, PCRTVFSIOSTREAMOPS pIoStreamOps) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, NULL); + if (pThis->pOps != pIoStreamOps) + return NULL; + return pThis->Base.pvThis; +} + + +#ifdef DEBUG +# undef RTVfsIoStrmRetain +#endif +RTDECL(uint32_t) RTVfsIoStrmRetain(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRetain(&pThis->Base); +} +#ifdef DEBUG +# define RTVfsIoStrmRetain(hVfsIos) RTVfsIoStrmRetainDebug(hVfsIos, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsIoStrmRetainDebug(RTVFSIOSTREAM hVfsIos, RT_SRC_POS_DECL) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRetainDebug(&pThis->Base, "RTVfsIoStrmRetainDebug", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsIoStrmRelease(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + if (pThis == NIL_RTVFSIOSTREAM) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX); + return rtVfsObjRelease(&pThis->Base); +} + + +RTDECL(RTVFSFILE) RTVfsIoStrmToFile(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, NIL_RTVFSFILE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, NIL_RTVFSFILE); + + if (pThis->pOps->Obj.enmType == RTVFSOBJTYPE_FILE) + { + rtVfsObjRetainVoid(&pThis->Base, "RTVfsIoStrmToFile"); + return RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream); + } + + /* this is no crime, so don't assert. */ + return NIL_RTVFSFILE; +} + + +RTDECL(int) RTVfsIoStrmQueryInfo(RTVFSIOSTREAM hVfsIos, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr); +} + + +RTDECL(int) RTVfsIoStrmRead(RTVFSIOSTREAM hVfsIos, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED); + + RTSGSEG Seg = { pvBuf, cbToRead }; + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &Seg, 1); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, -1 /*off*/, &SgBuf, fBlocking, pcbRead); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsIoStrmReadAt(RTVFSIOSTREAM hVfsIos, RTFOFF off, void *pvBuf, size_t cbToRead, + bool fBlocking, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED); + + RTSGSEG Seg = { pvBuf, cbToRead }; + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &Seg, 1); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbRead); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsIoStrmWrite(RTVFSIOSTREAM hVfsIos, const void *pvBuf, size_t cbToWrite, bool fBlocking, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED); + + int rc; + if (pThis->pOps->pfnWrite) + { + RTSGSEG Seg = { (void *)pvBuf, cbToWrite }; + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &Seg, 1); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, -1 /*off*/, &SgBuf, fBlocking, pcbWritten); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(int) RTVfsIoStrmWriteAt(RTVFSIOSTREAM hVfsIos, RTFOFF off, const void *pvBuf, size_t cbToWrite, + bool fBlocking, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED); + + int rc; + if (pThis->pOps->pfnWrite) + { + RTSGSEG Seg = { (void *)pvBuf, cbToWrite }; + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &Seg, 1); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbWritten); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(int) RTVfsIoStrmSgRead(RTVFSIOSTREAM hVfsIos, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtr(pSgBuf); + AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc; + if (!(pThis->pOps->fFeatures & RTVFSIOSTREAMOPS_FEAT_NO_SG)) + rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, pSgBuf, fBlocking, pcbRead); + else + { + size_t cbRead = 0; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &pSgBuf->paSegs[iSeg], 1); + + size_t cbReadSeg = pcbRead ? 0 : pSgBuf->paSegs[iSeg].cbSeg; + rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbRead ? &cbReadSeg : NULL); + if (RT_FAILURE(rc)) + break; + cbRead += cbReadSeg; + if ((pcbRead && cbReadSeg != SgBuf.paSegs[0].cbSeg) || rc != VINF_SUCCESS) + break; + if (off != -1) + off += cbReadSeg; + } + + if (pcbRead) + *pcbRead = cbRead; + } + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsIoStrmSgWrite(RTVFSIOSTREAM hVfsIos, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtr(pSgBuf); + AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER); + AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED); + + int rc; + if (pThis->pOps->pfnWrite) + { + RTVfsLockAcquireWrite(pThis->Base.hLock); + if (!(pThis->pOps->fFeatures & RTVFSIOSTREAMOPS_FEAT_NO_SG)) + rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, pSgBuf, fBlocking, pcbWritten); + else + { + size_t cbWritten = 0; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, &pSgBuf->paSegs[iSeg], 1); + + size_t cbWrittenSeg = 0; + rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbWritten ? &cbWrittenSeg : NULL); + if (RT_FAILURE(rc)) + break; + if (pcbWritten) + { + cbWritten += cbWrittenSeg; + if (cbWrittenSeg != SgBuf.paSegs[0].cbSeg) + break; + if (off != -1) + off += cbWrittenSeg; + } + else if (off != -1) + off += pSgBuf->paSegs[iSeg].cbSeg; + } + + if (pcbWritten) + *pcbWritten = cbWritten; + } + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(int) RTVfsIoStrmFlush(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + + RTVfsLockAcquireWrite(pThis->Base.hLock); + int rc = pThis->pOps->pfnFlush(pThis->Base.pvThis); + RTVfsLockReleaseWrite(pThis->Base.hLock); + return rc; +} + + +RTDECL(int) RTVfsIoStrmPoll(RTVFSIOSTREAM hVfsIos, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, + uint32_t *pfRetEvents) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE); + + int rc; + if (pThis->pOps->pfnPollOne) + { + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThis->pOps->pfnPollOne(pThis->Base.pvThis, fEvents, cMillies, fIntr, pfRetEvents); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + /* + * Default implementation. Polling for non-error events returns + * immediately, waiting for errors will work like sleep. + */ + else if (fEvents != RTPOLL_EVT_ERROR) + { + *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR; + rc = VINF_SUCCESS; + } + else if (fIntr) + rc = RTThreadSleep(cMillies); + else + { + uint64_t uMsStart = RTTimeMilliTS(); + do + rc = RTThreadSleep(cMillies); + while ( rc == VERR_INTERRUPTED + && !fIntr + && RTTimeMilliTS() - uMsStart < cMillies); + if (rc == VERR_INTERRUPTED) + rc = VERR_TIMEOUT; + } + return rc; +} + + +RTDECL(RTFOFF) RTVfsIoStrmTell(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1); + + RTFOFF off; + RTVfsLockAcquireRead(pThis->Base.hLock); + int rc = pThis->pOps->pfnTell(pThis->Base.pvThis, &off); + RTVfsLockReleaseRead(pThis->Base.hLock); + if (RT_FAILURE(rc)) + off = rc; + return off; +} + + +RTDECL(int) RTVfsIoStrmSkip(RTVFSIOSTREAM hVfsIos, RTFOFF cb) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1); + AssertReturn(cb >= 0, VERR_INVALID_PARAMETER); + + int rc; + if (pThis->pOps->pfnSkip) + { + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThis->pOps->pfnSkip(pThis->Base.pvThis, cb); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else if (pThis->pOps->Obj.enmType == RTVFSOBJTYPE_FILE) + { + RTVFSFILEINTERNAL *pThisFile = RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream); + RTFOFF offIgnored; + + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThisFile->pOps->pfnSeek(pThis->Base.pvThis, cb, RTFILE_SEEK_CURRENT, &offIgnored); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else + { + void *pvBuf = RTMemTmpAlloc(_64K); + if (pvBuf) + { + rc = VINF_SUCCESS; + while (cb > 0) + { + size_t cbToRead = (size_t)RT_MIN(cb, _64K); + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = RTVfsIoStrmRead(hVfsIos, pvBuf, cbToRead, true /*fBlocking*/, NULL); + RTVfsLockReleaseWrite(pThis->Base.hLock); + if (RT_FAILURE(rc)) + break; + cb -= cbToRead; + } + + RTMemTmpFree(pvBuf); + } + else + rc = VERR_NO_TMP_MEMORY; + } + return rc; +} + + +RTDECL(int) RTVfsIoStrmZeroFill(RTVFSIOSTREAM hVfsIos, RTFOFF cb) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1); + + int rc; + if (pThis->pOps->pfnZeroFill) + { + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = pThis->pOps->pfnZeroFill(pThis->Base.pvThis, cb); + RTVfsLockReleaseWrite(pThis->Base.hLock); + } + else + { + rc = VINF_SUCCESS; + while (cb > 0) + { + size_t cbToWrite = (size_t)RT_MIN(cb, (ssize_t)sizeof(g_abRTZero64K)); + RTVfsLockAcquireWrite(pThis->Base.hLock); + rc = RTVfsIoStrmWrite(hVfsIos, g_abRTZero64K, cbToWrite, true /*fBlocking*/, NULL); + RTVfsLockReleaseWrite(pThis->Base.hLock); + if (RT_FAILURE(rc)) + break; + cb -= cbToWrite; + } + } + return rc; +} + + +RTDECL(bool) RTVfsIoStrmIsAtEnd(RTVFSIOSTREAM hVfsIos) +{ + /* + * There is where the zero read behavior comes in handy. + */ + char bDummy; + size_t cbRead; + int rc = RTVfsIoStrmRead(hVfsIos, &bDummy, 0 /*cbToRead*/, false /*fBlocking*/, &cbRead); + return rc == VINF_EOF; +} + + + +RTDECL(uint64_t) RTVfsIoStrmGetOpenFlags(RTVFSIOSTREAM hVfsIos) +{ + RTVFSIOSTREAMINTERNAL *pThis = hVfsIos; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, 0); + return pThis->fFlags; +} + + + +/* + * + * F I L E F I L E F I L E + * F I L E F I L E F I L E + * F I L E F I L E F I L E + * + */ + +RTDECL(int) RTVfsNewFile(PCRTVFSFILEOPS pFileOps, size_t cbInstance, uint32_t fOpen, RTVFS hVfs, RTVFSLOCK hLock, + PRTVFSFILE phVfsFile, void **ppvInstance) +{ + /* + * Validate the input, be extra strict in strict builds. + */ + RTVFSFILE_ASSERT_OPS(pFileOps, RTVFSOBJTYPE_FILE); + Assert(cbInstance > 0); + Assert(fOpen & (RTFILE_O_ACCESS_MASK | RTFILE_O_ACCESS_ATTR_MASK)); + AssertPtr(ppvInstance); + AssertPtr(phVfsFile); + RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE); + + /* + * Allocate the handle + instance data. + */ + size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSFILEINTERNAL), RTVFS_INST_ALIGNMENT) + + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT); + RTVFSFILEINTERNAL *pThis = (RTVFSFILEINTERNAL *)RTMemAllocZ(cbThis); + if (!pThis) + return VERR_NO_MEMORY; + + int rc = rtVfsObjInitNewObject(&pThis->Stream.Base, &pFileOps->Stream.Obj, hVfs, false /*fNoVfsRef*/, hLock, + (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT)); + if (RT_FAILURE(rc)) + { + RTMemFree(pThis); + return rc; + } + + pThis->uMagic = RTVFSFILE_MAGIC; + pThis->fReserved = 0; + pThis->pOps = pFileOps; + pThis->Stream.uMagic = RTVFSIOSTREAM_MAGIC; + pThis->Stream.fFlags = fOpen; + pThis->Stream.pOps = &pFileOps->Stream; + + *phVfsFile = pThis; + *ppvInstance = pThis->Stream.Base.pvThis; + return VINF_SUCCESS; +} + + +RTDECL(int) RTVfsFileOpen(RTVFS hVfs, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile) +{ + /* + * Validate input. + */ + RTVFSINTERNAL *pThis = hVfs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER); + + int rc = rtFileRecalcAndValidateFlags(&fOpen); + if (RT_FAILURE(rc)) + return rc; + + /* + * Parse the path, assume current directory is root since we've got no + * caller context here. + */ + PRTVFSPARSEDPATH pPath; + rc = RTVfsParsePathA(pszFilename, "/", &pPath); + if (RT_SUCCESS(rc)) + { + /* + * Tranverse the path, resolving the parent node. + * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenFile. + */ + RTVFSDIRINTERNAL *pVfsParentDir; + uint32_t const fTraverse = (fOpen & RTFILE_O_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK; + rc = rtVfsTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir); + if (RT_SUCCESS(rc)) + { + /** @todo join path with RTVfsDirOpenFile. */ + /* + * Do the opening. Loop if we need to follow symbolic links. + */ + bool fDirSlash = pPath->fDirSlash; + + uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY_FILE | RTVFSOBJ_F_OPEN_SYMLINK; + if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + fObjFlags |= RTVFSOBJ_F_CREATE_FILE; + else + fObjFlags |= RTVFSOBJ_F_CREATE_NOTHING; + fObjFlags |= fTraverse & RTPATH_F_MASK; + + for (uint32_t cLoops = 1;; cLoops++) + { + /* Do the querying. If pfnOpenFile is available, we use it first, falling + back on pfnOpen in case of symbolic links that needs following or we got + a trailing directory slash (to get file-not-found error). */ + const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]]; + if ( pVfsParentDir->pOps->pfnOpenFile + && !fDirSlash) + { + RTVfsLockAcquireRead(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpenFile(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, phVfsFile); + RTVfsLockReleaseRead(pVfsParentDir->Base.hLock); + if ( RT_SUCCESS(rc) + || ( rc != VERR_NOT_A_FILE + && rc != VERR_IS_A_SYMLINK)) + break; + } + + RTVFSOBJ hVfsObj; + RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock); + rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, fObjFlags, &hVfsObj); + RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock); + if (RT_FAILURE(rc)) + break; + + /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */ + if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK) + || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK) + { + *phVfsFile = RTVfsObjToFile(hVfsObj); + AssertStmt(*phVfsFile != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3); + RTVfsObjRelease(hVfsObj); + break; + } + + /* Follow symbolic link. */ + if (cLoops < RTVFS_MAX_LINKS) + rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse); + else + rc = VERR_TOO_MANY_SYMLINKS; + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(rc)) + break; + fDirSlash |= pPath->fDirSlash; + } + RTVfsDirRelease(pVfsParentDir); + } + RTVfsParsePathFree(pPath); + } + return rc; + +} + + +#ifdef DEBUG +# undef RTVfsFileRetain +#endif +RTDECL(uint32_t) RTVfsFileRetain(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX); + return rtVfsObjRetain(&pThis->Stream.Base); +} +#ifdef DEBUG +# define RTVfsFileRetain(hVfsFile) RTVfsFileRetainDebug(hVfsFile, RT_SRC_POS) +#endif + + +RTDECL(uint32_t) RTVfsFileRetainDebug(RTVFSFILE hVfsFile, RT_SRC_POS_DECL) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX); + return rtVfsObjRetainDebug(&pThis->Stream.Base, "RTVFsFileRetainDebug", RT_SRC_POS_ARGS); +} + + +RTDECL(uint32_t) RTVfsFileRelease(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + if (pThis == NIL_RTVFSFILE) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX); + return rtVfsObjRelease(&pThis->Stream.Base); +} + + +RTDECL(RTVFSIOSTREAM) RTVfsFileToIoStream(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, NIL_RTVFSIOSTREAM); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, NIL_RTVFSIOSTREAM); + + rtVfsObjRetainVoid(&pThis->Stream.Base, "RTVfsFileToIoStream"); + return &pThis->Stream; +} + + +RTDECL(int) RTVfsFileQueryInfo(RTVFSFILE hVfsFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsObjQueryInfo(&pThis->Stream.Base, pObjInfo, enmAddAttr); +} + + +RTDECL(int) RTVfsFileRead(RTVFSFILE hVfsFile, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsIoStrmRead(&pThis->Stream, pvBuf, cbToRead, true /*fBlocking*/, pcbRead); +} + + +RTDECL(int) RTVfsFileWrite(RTVFSFILE hVfsFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsIoStrmWrite(&pThis->Stream, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten); +} + + +RTDECL(int) RTVfsFileWriteAt(RTVFSFILE hVfsFile, RTFOFF off, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTVfsFileSeek(hVfsFile, off, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = RTVfsIoStrmWriteAt(&pThis->Stream, off, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten); + + return rc; +} + + +RTDECL(int) RTVfsFileReadAt(RTVFSFILE hVfsFile, RTFOFF off, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTVfsFileSeek(hVfsFile, off, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = RTVfsIoStrmReadAt(&pThis->Stream, off, pvBuf, cbToRead, true /*fBlocking*/, pcbRead); + + return rc; +} + + +RTDECL(int) RTVfsFileSgRead(RTVFSFILE hVfsFile, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); + if (pcbRead) + *pcbRead = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + + return RTVfsIoStrmSgRead(&pThis->Stream, off, pSgBuf, fBlocking, pcbRead); +} + + +RTDECL(int) RTVfsFileSgWrite(RTVFSFILE hVfsFile, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER); + if (pcbWritten) + *pcbWritten = 0; + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + + return RTVfsIoStrmSgWrite(&pThis->Stream, off, pSgBuf, fBlocking, pcbWritten); +} + + +RTDECL(int) RTVfsFileFlush(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsIoStrmFlush(&pThis->Stream); +} + + +RTDECL(RTFOFF) RTVfsFilePoll(RTVFSFILE hVfsFile, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, + uint32_t *pfRetEvents) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsIoStrmPoll(&pThis->Stream, fEvents, cMillies, fIntr, pfRetEvents); +} + + +RTDECL(RTFOFF) RTVfsFileTell(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + return RTVfsIoStrmTell(&pThis->Stream); +} + + +RTDECL(int) RTVfsFileSeek(RTVFSFILE hVfsFile, RTFOFF offSeek, uint32_t uMethod, uint64_t *poffActual) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + + AssertReturn( uMethod == RTFILE_SEEK_BEGIN + || uMethod == RTFILE_SEEK_CURRENT + || uMethod == RTFILE_SEEK_END, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(poffActual, VERR_INVALID_POINTER); + + RTFOFF offActual = 0; + RTVfsLockAcquireWrite(pThis->Stream.Base.hLock); + int rc = pThis->pOps->pfnSeek(pThis->Stream.Base.pvThis, offSeek, uMethod, &offActual); + RTVfsLockReleaseWrite(pThis->Stream.Base.hLock); + if (RT_SUCCESS(rc) && poffActual) + { + Assert(offActual >= 0); + *poffActual = offActual; + } + + return rc; +} + + +RTDECL(int) RTVfsFileQuerySize(RTVFSFILE hVfsFile, uint64_t *pcbSize) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); + + RTVfsLockAcquireWrite(pThis->Stream.Base.hLock); + int rc = pThis->pOps->pfnQuerySize(pThis->Stream.Base.pvThis, pcbSize); + RTVfsLockReleaseWrite(pThis->Stream.Base.hLock); + + return rc; +} + + +RTDECL(int) RTVfsFileSetSize(RTVFSFILE hVfsFile, uint64_t cbSize, uint32_t fFlags) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTVFSFILE_SIZE_F_IS_VALID(fFlags), VERR_INVALID_FLAGS); + AssertReturn(pThis->Stream.fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED); + + int rc; + if (pThis->pOps->pfnSetSize) + { + RTVfsLockAcquireWrite(pThis->Stream.Base.hLock); + rc = pThis->pOps->pfnSetSize(pThis->Stream.Base.pvThis, cbSize, fFlags); + RTVfsLockReleaseWrite(pThis->Stream.Base.hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(RTFOFF) RTVfsFileGetMaxSize(RTVFSFILE hVfsFile) +{ + uint64_t cbMax; + int rc = RTVfsFileQueryMaxSize(hVfsFile, &cbMax); + return RT_SUCCESS(rc) ? (RTFOFF)RT_MIN(cbMax, (uint64_t)RTFOFF_MAX) : -1; +} + + +RTDECL(int) RTVfsFileQueryMaxSize(RTVFSFILE hVfsFile, uint64_t *pcbMax) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pcbMax, VERR_INVALID_POINTER); + *pcbMax = RTFOFF_MAX; + + int rc; + if (pThis->pOps->pfnQueryMaxSize) + { + RTVfsLockAcquireWrite(pThis->Stream.Base.hLock); + rc = pThis->pOps->pfnQueryMaxSize(pThis->Stream.Base.pvThis, pcbMax); + RTVfsLockReleaseWrite(pThis->Stream.Base.hLock); + } + else + rc = VERR_WRITE_PROTECT; + return rc; +} + + +RTDECL(uint64_t) RTVfsFileGetOpenFlags(RTVFSFILE hVfsFile) +{ + RTVFSFILEINTERNAL *pThis = hVfsFile; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, 0); + return pThis->Stream.fFlags; +} + diff --git a/src/VBox/Runtime/common/vfs/vfschain.cpp b/src/VBox/Runtime/common/vfs/vfschain.cpp new file mode 100644 index 00000000..c929ac7c --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfschain.cpp @@ -0,0 +1,1851 @@ +/* $Id: vfschain.cpp $ */ +/** @file + * IPRT - Virtual File System, Chains. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/asm.h> +#include <iprt/critsect.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> + +#include "internal/file.h" +#include "internal/magics.h" +//#include "internal/vfs.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PCRTVFSCHAINELEMENTREG rtVfsChainFindProviderLocked(const char *pszProvider); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init the critical section once. */ +static RTONCE g_rtVfsChainElementInitOnce = RTONCE_INITIALIZER; +/** Critical section protecting g_rtVfsChainElementProviderList. */ +static RTCRITSECTRW g_rtVfsChainElementCritSect; +/** List of VFS chain element providers (RTVFSCHAINELEMENTREG). */ +static RTLISTANCHOR g_rtVfsChainElementProviderList; + + + +RTDECL(int) RTVfsChainValidateOpenFileOrIoStream(PRTVFSCHAINSPEC pSpec, PRTVFSCHAINELEMSPEC pElement, + uint32_t *poffError, PRTERRINFO pErrInfo) +{ + if (pElement->cArgs < 1) + return VERR_VFS_CHAIN_AT_LEAST_ONE_ARG; + if (pElement->cArgs > 4) + return VERR_VFS_CHAIN_AT_MOST_FOUR_ARGS; + if (!*pElement->paArgs[0].psz) + return VERR_VFS_CHAIN_EMPTY_ARG; + + /* + * Calculate the flags, storing them in the first argument. + */ + const char *pszAccess = pElement->cArgs >= 2 ? pElement->paArgs[1].psz : ""; + if (!*pszAccess) + pszAccess = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READWRITE ? "rw" + : (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ ? "r" + : (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE ? "w" + : "rw"; + + const char *pszDisp = pElement->cArgs >= 3 ? pElement->paArgs[2].psz : ""; + if (!*pszDisp) + pszDisp = strchr(pszAccess, 'w') != NULL ? "open-create" : "open"; + + const char *pszSharing = pElement->cArgs >= 4 ? pElement->paArgs[3].psz : ""; + + int rc = RTFileModeToFlagsEx(pszAccess, pszDisp, pszSharing, &pElement->uProvider); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* + * Now try figure out which argument offended us. + */ + AssertReturn(pElement->cArgs > 1, VERR_VFS_CHAIN_IPE); + if ( pElement->cArgs == 2 + || RT_FAILURE(RTFileModeToFlagsEx(pszAccess, "open-create", "", &pElement->uProvider))) + { + *poffError = pElement->paArgs[1].offSpec; + rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected valid access flags: 'r', 'rw', or 'w'"); + } + else if ( pElement->cArgs == 3 + || RT_FAILURE(RTFileModeToFlagsEx(pszAccess, pszDisp, "", &pElement->uProvider))) + { + *poffError = pElement->paArgs[2].offSpec; + rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, + "Expected valid open disposition: create, create-replace, open, open-create, open-append, open-truncate"); + } + else + { + *poffError = pElement->paArgs[3].offSpec; + rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected valid sharing flags: nr, nw, nrw, d"); + + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtVfsChainOpen_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg); + + /* + * Basic checks. + */ + if ( pElement->enmType != RTVFSOBJTYPE_DIR + && pElement->enmType != RTVFSOBJTYPE_FILE + && pElement->enmType != RTVFSOBJTYPE_IO_STREAM) + return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS_OR_DIR; + if ( pElement->enmTypeIn != RTVFSOBJTYPE_DIR + && pElement->enmTypeIn != RTVFSOBJTYPE_FS_STREAM + && pElement->enmTypeIn != RTVFSOBJTYPE_VFS) + { + if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID) + { + /* + * First element: Transform into 'stdfile' or 'stddir' if registered. + */ + const char *pszNewProvider = pElement->enmType == RTVFSOBJTYPE_DIR ? "stddir" : "stdfile"; + PCRTVFSCHAINELEMENTREG pNewProvider = rtVfsChainFindProviderLocked(pszNewProvider); + if (pNewProvider) + { + pElement->pProvider = pNewProvider; + return pNewProvider->pfnValidate(pNewProvider, pSpec, pElement, poffError, pErrInfo); + } + return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT; + } + return VERR_VFS_CHAIN_TAKES_DIR_OR_FSS_OR_VFS; + } + + /* + * Make common cause with 'stdfile' if we're opening a file or I/O stream. + * If the input is a FSS, we have to make sure it's a read-only operation. + */ + if ( pElement->enmType == RTVFSOBJTYPE_FILE + || pElement->enmType == RTVFSOBJTYPE_IO_STREAM) + { + int rc = RTVfsChainValidateOpenFileOrIoStream(pSpec, pElement, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pElement->enmTypeIn != RTVFSOBJTYPE_FS_STREAM) + return VINF_SUCCESS; + if ( !(pElement->uProvider & RTFILE_O_WRITE) + && (pElement->uProvider & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN) + return VINF_SUCCESS; + *poffError = pElement->cArgs > 1 ? pElement->paArgs[1].offSpec : pElement->offSpec; + return VERR_VFS_CHAIN_INVALID_ARGUMENT; + } + return rc; + } + + + /* + * Directory checks. Path argument only, optional. If not given the root directory of a VFS or the + */ + if (pElement->cArgs > 1) + return VERR_VFS_CHAIN_AT_MOST_ONE_ARG; + pElement->uProvider = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtVfsChainOpen_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo); + AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE); + + /* + * File system stream: Seek thru the stream looking for the object to open. + */ + RTVFSFSSTREAM hVfsFssIn = RTVfsObjToFsStream(hPrevVfsObj); + if (hVfsFssIn != NIL_RTVFSFSSTREAM) + { + return VERR_NOT_IMPLEMENTED; + } + + /* + * VFS: Use RTVfsFileOpen or RTVfsDirOpen. + */ + RTVFS hVfsIn = RTVfsObjToVfs(hPrevVfsObj); + if (hVfsIn != NIL_RTVFS) + { + if ( pElement->enmType == RTVFSOBJTYPE_FILE + || pElement->enmType == RTVFSOBJTYPE_IO_STREAM) + { + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + int rc = RTVfsFileOpen(hVfsIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile); + RTVfsRelease(hVfsIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; + } + if (pElement->enmType == RTVFSOBJTYPE_DIR) + { + RTVFSDIR hVfsDir = NIL_RTVFSDIR; + int rc = RTVfsDirOpen(hVfsIn, pElement->paArgs[0].psz, (uint32_t)pElement->uProvider, &hVfsDir); + RTVfsRelease(hVfsIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; + } + RTVfsRelease(hVfsIn); + return VERR_VFS_CHAIN_IPE; + } + + /* + * Directory: Similar to above, just relative to a directory. + */ + RTVFSDIR hVfsDirIn = RTVfsObjToDir(hPrevVfsObj); + if (hVfsDirIn != NIL_RTVFSDIR) + { + if ( pElement->enmType == RTVFSOBJTYPE_FILE + || pElement->enmType == RTVFSOBJTYPE_IO_STREAM) + { + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + int rc = RTVfsDirOpenFile(hVfsDirIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile); + RTVfsDirRelease(hVfsDirIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; + } + if (pElement->enmType == RTVFSOBJTYPE_DIR) + { + RTVFSDIR hVfsDir = NIL_RTVFSDIR; + int rc = RTVfsDirOpenDir(hVfsDirIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsDir); + RTVfsDirRelease(hVfsDirIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; + } + RTVfsDirRelease(hVfsDirIn); + return VERR_VFS_CHAIN_IPE; + } + + AssertFailed(); + return VERR_VFS_CHAIN_CAST_FAILED; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtVfsChainOpen_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement); + return false; +} + + +/** VFS chain element 'gunzip'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainGunzipReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "open", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Generic VFS open, that can open files (or I/O stream) and directories in a VFS, directory or file system stream.\n" + "If used as the first element in a chain, it will work like 'stdfile' or 'stddir' and work on the real file system.\n" + "First argument is the filename or directory path.\n" + "Second argument is access mode, files only, optional: r, w, rw.\n" + "Third argument is open disposition, files only, optional: create, create-replace, open, open-create, open-append, open-truncate.\n" + "Forth argument is file sharing, files only, optional: nr, nw, nrw, d.", + /* pfnValidate = */ rtVfsChainOpen_Validate, + /* pfnInstantiate = */ rtVfsChainOpen_Instantiate, + /* pfnCanReuseElement = */ rtVfsChainOpen_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGunzipReg, rtVfsChainGunzipReg); + + + + +/** + * Initializes the globals via RTOnce. + * + * @returns IPRT status code + * @param pvUser Unused, ignored. + */ +static DECLCALLBACK(int) rtVfsChainElementRegisterInit(void *pvUser) +{ + NOREF(pvUser); + if (!g_rtVfsChainElementProviderList.pNext) + RTListInit(&g_rtVfsChainElementProviderList); + int rc = RTCritSectRwInit(&g_rtVfsChainElementCritSect); + if (RT_SUCCESS(rc)) + { + } + return rc; +} + + +RTDECL(int) RTVfsChainElementRegisterProvider(PRTVFSCHAINELEMENTREG pRegRec, bool fFromCtor) +{ + int rc; + + /* + * Input validation. + */ + AssertPtrReturn(pRegRec, VERR_INVALID_POINTER); + AssertMsgReturn(pRegRec->uVersion == RTVFSCHAINELEMENTREG_VERSION, ("%#x", pRegRec->uVersion), VERR_INVALID_POINTER); + AssertMsgReturn(pRegRec->uEndMarker == RTVFSCHAINELEMENTREG_VERSION, ("%#zx", pRegRec->uEndMarker), VERR_INVALID_POINTER); + AssertReturn(pRegRec->fReserved == 0, VERR_INVALID_POINTER); + AssertPtrReturn(pRegRec->pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pRegRec->pfnValidate, VERR_INVALID_POINTER); + AssertPtrReturn(pRegRec->pfnInstantiate, VERR_INVALID_POINTER); + AssertPtrReturn(pRegRec->pfnCanReuseElement, VERR_INVALID_POINTER); + + /* + * Init and take the lock. + */ + if (!fFromCtor) + { + rc = RTOnce(&g_rtVfsChainElementInitOnce, rtVfsChainElementRegisterInit, NULL); + if (RT_FAILURE(rc)) + return rc; + rc = RTCritSectRwEnterExcl(&g_rtVfsChainElementCritSect); + if (RT_FAILURE(rc)) + return rc; + } + else if (!g_rtVfsChainElementProviderList.pNext) + RTListInit(&g_rtVfsChainElementProviderList); + + /* + * Duplicate name? + */ + rc = VINF_SUCCESS; + PRTVFSCHAINELEMENTREG pIterator, pIterNext; + RTListForEachSafe(&g_rtVfsChainElementProviderList, pIterator, pIterNext, RTVFSCHAINELEMENTREG, ListEntry) + { + if (!strcmp(pIterator->pszName, pRegRec->pszName)) + { + AssertMsgFailed(("duplicate name '%s' old=%p new=%p\n", pIterator->pszName, pIterator, pRegRec)); + rc = VERR_ALREADY_EXISTS; + break; + } + } + + /* + * If not, append the record to the list. + */ + if (RT_SUCCESS(rc)) + RTListAppend(&g_rtVfsChainElementProviderList, &pRegRec->ListEntry); + + /* + * Leave the lock and return. + */ + if (!fFromCtor) + RTCritSectRwLeaveExcl(&g_rtVfsChainElementCritSect); + return rc; +} + + +/** + * Allocates and initializes an empty spec + * + * @returns Pointer to the spec on success, NULL on failure. + */ +static PRTVFSCHAINSPEC rtVfsChainSpecAlloc(void) +{ + PRTVFSCHAINSPEC pSpec = (PRTVFSCHAINSPEC)RTMemTmpAlloc(sizeof(*pSpec)); + if (pSpec) + { + pSpec->fOpenFile = 0; + pSpec->fOpenDir = 0; + pSpec->cElements = 0; + pSpec->paElements = NULL; + } + return pSpec; +} + + +/** + * Checks if @a ch is a character that can be escaped. + * + * @returns true / false. + * @param ch The character to consider. + */ +DECLINLINE(bool) rtVfsChainSpecIsEscapableChar(char ch) +{ + return ch == '(' + || ch == ')' + || ch == '{' + || ch == '}' + || ch == '\\' + || ch == ',' + || ch == '|' + || ch == ':'; +} + + +/** + * Duplicate a spec string after unescaping it. + * + * This differs from RTStrDupN in that it uses RTMemTmpAlloc instead of + * RTMemAlloc. + * + * @returns String copy on success, NULL on failure. + * @param psz The string to duplicate. + * @param cch The number of bytes to duplicate. + * @param prc The status code variable to set on failure. (Leeps the + * code shorter. -lazy bird) + */ +DECLINLINE(char *) rtVfsChainSpecDupStrN(const char *psz, size_t cch, int *prc) +{ + char *pszCopy = (char *)RTMemTmpAlloc(cch + 1); + if (pszCopy) + { + if (!memchr(psz, '\\', cch)) + { + /* Plain string, copy it raw. */ + memcpy(pszCopy, psz, cch); + pszCopy[cch] = '\0'; + } + else + { + /* Has escape sequences, must unescape it. */ + char *pszDst = pszCopy; + while (cch-- > 0) + { + char ch = *psz++; + if (ch == '\\' && cch > 0) + { + char ch2 = *psz; + if (rtVfsChainSpecIsEscapableChar(ch2)) + { + psz++; + cch--; + ch = ch2; + } + } + *pszDst++ = ch; + } + *pszDst = '\0'; + } + } + else + *prc = VERR_NO_TMP_MEMORY; + return pszCopy; +} + + +/** + * Adds an empty element to the chain specification. + * + * The caller is responsible for filling it the element attributes. + * + * @returns Pointer to the new element on success, NULL on failure. The + * pointer is only valid till the next call to this function. + * @param pSpec The chain specification. + * @param prc The status code variable to set on failure. (Leeps the + * code shorter. -lazy bird) + */ +static PRTVFSCHAINELEMSPEC rtVfsChainSpecAddElement(PRTVFSCHAINSPEC pSpec, uint16_t offSpec, int *prc) +{ + AssertPtr(pSpec); + + /* + * Resize the element table if necessary. + */ + uint32_t const iElement = pSpec->cElements; + if ((iElement % 32) == 0) + { + PRTVFSCHAINELEMSPEC paNew = (PRTVFSCHAINELEMSPEC)RTMemTmpAlloc((iElement + 32) * sizeof(paNew[0])); + if (!paNew) + { + *prc = VERR_NO_TMP_MEMORY; + return NULL; + } + + if (iElement) + memcpy(paNew, pSpec->paElements, iElement * sizeof(paNew[0])); + RTMemTmpFree(pSpec->paElements); + pSpec->paElements = paNew; + } + + /* + * Initialize and add the new element. + */ + PRTVFSCHAINELEMSPEC pElement = &pSpec->paElements[iElement]; + pElement->pszProvider = NULL; + pElement->enmTypeIn = iElement ? pSpec->paElements[iElement - 1].enmType : RTVFSOBJTYPE_INVALID; + pElement->enmType = RTVFSOBJTYPE_INVALID; + pElement->offSpec = offSpec; + pElement->cchSpec = 0; + pElement->cArgs = 0; + pElement->paArgs = NULL; + pElement->pProvider = NULL; + pElement->hVfsObj = NIL_RTVFSOBJ; + + pSpec->cElements = iElement + 1; + return pElement; +} + + +/** + * Adds an argument to the element spec. + * + * @returns IPRT status code. + * @param pElement The element. + * @param psz The start of the argument string. + * @param cch The length of the argument string, escape + * sequences counted twice. + */ +static int rtVfsChainSpecElementAddArg(PRTVFSCHAINELEMSPEC pElement, const char *psz, size_t cch, uint16_t offSpec) +{ + uint32_t iArg = pElement->cArgs; + if ((iArg % 32) == 0) + { + PRTVFSCHAINELEMENTARG paNew = (PRTVFSCHAINELEMENTARG)RTMemTmpAlloc((iArg + 32) * sizeof(paNew[0])); + if (!paNew) + return VERR_NO_TMP_MEMORY; + if (iArg) + memcpy(paNew, pElement->paArgs, iArg * sizeof(paNew[0])); + RTMemTmpFree(pElement->paArgs); + pElement->paArgs = paNew; + } + + int rc = VINF_SUCCESS; + pElement->paArgs[iArg].psz = rtVfsChainSpecDupStrN(psz, cch, &rc); + pElement->paArgs[iArg].offSpec = offSpec; + pElement->cArgs = iArg + 1; + return rc; +} + + +RTDECL(void) RTVfsChainSpecFree(PRTVFSCHAINSPEC pSpec) +{ + if (!pSpec) + return; + + uint32_t i = pSpec->cElements; + while (i-- > 0) + { + uint32_t iArg = pSpec->paElements[i].cArgs; + while (iArg-- > 0) + RTMemTmpFree(pSpec->paElements[i].paArgs[iArg].psz); + RTMemTmpFree(pSpec->paElements[i].paArgs); + RTMemTmpFree(pSpec->paElements[i].pszProvider); + if (pSpec->paElements[i].hVfsObj != NIL_RTVFSOBJ) + { + RTVfsObjRelease(pSpec->paElements[i].hVfsObj); + pSpec->paElements[i].hVfsObj = NIL_RTVFSOBJ; + } + } + + RTMemTmpFree(pSpec->paElements); + pSpec->paElements = NULL; + RTMemTmpFree(pSpec); +} + + +/** + * Checks if @a psz is pointing to the final element specification. + * + * @returns true / false. + * @param psz Start of an element or path. + * @param pcch Where to return the length. + */ +static bool rtVfsChainSpecIsFinalElement(const char *psz, size_t *pcch) +{ + size_t off = 0; + char ch; + while ((ch = psz[off]) != '\0') + { + if (ch == '|' || ch == ':') + return false; + if ( ch == '\\' + && rtVfsChainSpecIsEscapableChar(psz[off + 1])) + off++; + off++; + } + *pcch = off; + return off > 0; +} + + +/** + * Makes the final path element. + * @returns IPRT status code + * @param pElement The element. + * @param pszPath The path. + * @param cchPath The path length. + */ +static int rtVfsChainSpecMakeFinalPathElement(PRTVFSCHAINELEMSPEC pElement, const char *pszPath, size_t cchPath) +{ + pElement->pszProvider = NULL; + pElement->enmType = RTVFSOBJTYPE_END; + pElement->cchSpec = (uint16_t)cchPath; + return rtVfsChainSpecElementAddArg(pElement, pszPath, cchPath, pElement->offSpec); +} + + +/** + * Finds the end of the argument string. + * + * @returns The offset of the end character relative to @a psz. + * @param psz The argument string. + * @param chCloseParen The closing parenthesis. + */ +static size_t rtVfsChainSpecFindArgEnd(const char *psz, char const chCloseParen) +{ + size_t off = 0; + char ch; + while ( (ch = psz[off]) != '\0' + && ch != ',' + && ch != chCloseParen) + { + if ( ch == '\\' + && rtVfsChainSpecIsEscapableChar(psz[off+1])) + off++; + off++; + } + return off; +} + + +RTDECL(int) RTVfsChainSpecParse(const char *pszSpec, uint32_t fFlags, RTVFSOBJTYPE enmDesiredType, + PRTVFSCHAINSPEC *ppSpec, uint32_t *poffError) +{ + if (poffError) + { + AssertPtrReturn(poffError, VERR_INVALID_POINTER); + *poffError = 0; + } + AssertPtrReturn(ppSpec, VERR_INVALID_POINTER); + *ppSpec = NULL; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTVFSCHAIN_PF_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(enmDesiredType > RTVFSOBJTYPE_INVALID && enmDesiredType < RTVFSOBJTYPE_END, VERR_INVALID_PARAMETER); + + /* + * Check the start of the specification and allocate an empty return spec. + */ + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1)) + return VERR_VFS_CHAIN_NO_PREFIX; + const char *pszSrc = RTStrStripL(pszSpec + sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1); + if (!*pszSrc) + return VERR_VFS_CHAIN_EMPTY; + + PRTVFSCHAINSPEC pSpec = rtVfsChainSpecAlloc(); + if (!pSpec) + return VERR_NO_TMP_MEMORY; + pSpec->enmDesiredType = enmDesiredType; + + /* + * Parse the spec one element at a time. + */ + int rc = VINF_SUCCESS; + while (*pszSrc && RT_SUCCESS(rc)) + { + /* + * Digest element separator, except for the first element. + */ + if (*pszSrc == '|' || *pszSrc == ':') + { + if (pSpec->cElements != 0) + pszSrc = RTStrStripL(pszSrc + 1); + else + { + rc = VERR_VFS_CHAIN_LEADING_SEPARATOR; + break; + } + } + else if (pSpec->cElements != 0) + { + rc = VERR_VFS_CHAIN_EXPECTED_SEPARATOR; + break; + } + + /* + * Ok, there should be an element here so add one to the return struct. + */ + PRTVFSCHAINELEMSPEC pElement = rtVfsChainSpecAddElement(pSpec, (uint16_t)(pszSrc - pszSpec), &rc); + if (!pElement) + break; + + /* + * First up is the VFS object type followed by a parenthesis/curly, or + * this could be the trailing action. Alternatively, we could have a + * final path-only element here. + */ + size_t cch; + if (strncmp(pszSrc, "base", cch = 4) == 0) + pElement->enmType = RTVFSOBJTYPE_BASE; + else if (strncmp(pszSrc, "vfs", cch = 3) == 0) + pElement->enmType = RTVFSOBJTYPE_VFS; + else if (strncmp(pszSrc, "fss", cch = 3) == 0) + pElement->enmType = RTVFSOBJTYPE_FS_STREAM; + else if (strncmp(pszSrc, "ios", cch = 3) == 0) + pElement->enmType = RTVFSOBJTYPE_IO_STREAM; + else if (strncmp(pszSrc, "dir", cch = 3) == 0) + pElement->enmType = RTVFSOBJTYPE_DIR; + else if (strncmp(pszSrc, "file", cch = 4) == 0) + pElement->enmType = RTVFSOBJTYPE_FILE; + else if (strncmp(pszSrc, "sym", cch = 3) == 0) + pElement->enmType = RTVFSOBJTYPE_SYMLINK; + else + { + if (rtVfsChainSpecIsFinalElement(pszSrc, &cch)) + rc = rtVfsChainSpecMakeFinalPathElement(pElement, pszSrc, cch); + else if (*pszSrc == '\0') + rc = VERR_VFS_CHAIN_TRAILING_SEPARATOR; + else + rc = VERR_VFS_CHAIN_UNKNOWN_TYPE; + break; + } + + /* Check and skip past the parenthesis/curly. If not there, we might + have a final path element at our hands. */ + char const chOpenParen = pszSrc[cch]; + if (chOpenParen != '(' && chOpenParen != '{') + { + if (rtVfsChainSpecIsFinalElement(pszSrc, &cch)) + rc = rtVfsChainSpecMakeFinalPathElement(pElement, pszSrc, cch); + else + rc = VERR_VFS_CHAIN_EXPECTED_LEFT_PARENTHESES; + break; + } + char const chCloseParen = (chOpenParen == '(' ? ')' : '}'); + pszSrc = RTStrStripL(pszSrc + cch + 1); + + /* + * The name of the element provider. + */ + cch = rtVfsChainSpecFindArgEnd(pszSrc, chCloseParen); + if (!cch) + { + rc = VERR_VFS_CHAIN_EXPECTED_PROVIDER_NAME; + break; + } + pElement->pszProvider = rtVfsChainSpecDupStrN(pszSrc, cch, &rc); + if (!pElement->pszProvider) + break; + pszSrc += cch; + + /* + * The arguments. + */ + while (*pszSrc == ',') + { + pszSrc = RTStrStripL(pszSrc + 1); + cch = rtVfsChainSpecFindArgEnd(pszSrc, chCloseParen); + rc = rtVfsChainSpecElementAddArg(pElement, pszSrc, cch, (uint16_t)(pszSrc - pszSpec)); + if (RT_FAILURE(rc)) + break; + pszSrc += cch; + } + if (RT_FAILURE(rc)) + break; + + /* Must end with a right parentheses/curly. */ + if (*pszSrc != chCloseParen) + { + rc = VERR_VFS_CHAIN_EXPECTED_RIGHT_PARENTHESES; + break; + } + pElement->cchSpec = (uint16_t)(pszSrc - pszSpec) - pElement->offSpec + 1; + + pszSrc = RTStrStripL(pszSrc + 1); + } + +#if 0 + /* + * Dump the chain. Useful for debugging the above code. + */ + RTAssertMsg2("dbg: cElements=%d rc=%Rrc\n", pSpec->cElements, rc); + for (uint32_t i = 0; i < pSpec->cElements; i++) + { + uint32_t const cArgs = pSpec->paElements[i].cArgs; + RTAssertMsg2("dbg: #%u: enmTypeIn=%d enmType=%d cArgs=%d", + i, pSpec->paElements[i].enmTypeIn, pSpec->paElements[i].enmType, cArgs); + for (uint32_t j = 0; j < cArgs; j++) + RTAssertMsg2(j == 0 ? (cArgs > 1 ? " [%s" : " [%s]") : j + 1 < cArgs ? ", %s" : ", %s]", + pSpec->paElements[i].paArgs[j].psz); + RTAssertMsg2(" offSpec=%d cchSpec=%d", pSpec->paElements[i].offSpec, pSpec->paElements[i].cchSpec); + RTAssertMsg2(" spec: %.*s\n", pSpec->paElements[i].cchSpec, &pszSpec[pSpec->paElements[i].offSpec]); + } +#endif + + /* + * Return the chain on success; Cleanup and set the error indicator on + * failure. + */ + if (RT_SUCCESS(rc)) + *ppSpec = pSpec; + else + { + if (poffError) + *poffError = (uint32_t)(pszSrc - pszSpec); + RTVfsChainSpecFree(pSpec); + } + return rc; +} + + +/** + * Looks up @a pszProvider among the registered providers. + * + * @returns Pointer to registration record if found, NULL if not. + * @param pszProvider The provider. + */ +static PCRTVFSCHAINELEMENTREG rtVfsChainFindProviderLocked(const char *pszProvider) +{ + PCRTVFSCHAINELEMENTREG pIterator; + RTListForEach(&g_rtVfsChainElementProviderList, pIterator, RTVFSCHAINELEMENTREG, ListEntry) + { + if (strcmp(pIterator->pszName, pszProvider) == 0) + return pIterator; + } + return NULL; +} + + +/** + * Does reusable object type matching. + * + * @returns true if the types matches, false if not. + * @param pElement The target element specification. + * @param pReuseElement The source element specification. + */ +static bool rtVfsChainMatchReusableType(PRTVFSCHAINELEMSPEC pElement, PRTVFSCHAINELEMSPEC pReuseElement) +{ + if (pElement->enmType == pReuseElement->enmType) + return true; + + /* File objects can always be cast to I/O streams. */ + if ( pElement->enmType == RTVFSOBJTYPE_IO_STREAM + && pReuseElement->enmType == RTVFSOBJTYPE_FILE) + return true; + + /* I/O stream objects may be file objects. */ + if ( pElement->enmType == RTVFSOBJTYPE_FILE + && pReuseElement->enmType == RTVFSOBJTYPE_IO_STREAM) + { + RTVFSFILE hVfsFile = RTVfsObjToFile(pReuseElement->hVfsObj); + if (hVfsFile != NIL_RTVFSFILE) + { + RTVfsFileRelease(hVfsFile); + return true; + } + } + return false; +} + + +RTDECL(int) RTVfsChainSpecCheckAndSetup(PRTVFSCHAINSPEC pSpec, PCRTVFSCHAINSPEC pReuseSpec, + PRTVFSOBJ phVfsObj, const char **ppszFinalPath, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + AssertPtrReturn(poffError, VERR_INVALID_POINTER); + *poffError = 0; + AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER); + *phVfsObj = NIL_RTVFSOBJ; + AssertPtrReturn(ppszFinalPath, VERR_INVALID_POINTER); + *ppszFinalPath = NULL; + AssertPtrReturn(pSpec, VERR_INVALID_POINTER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Check for final path-only component as we will not touch it yet. + */ + uint32_t cElements = pSpec->cElements; + if (cElements > 0) + { + if (pSpec->paElements[pSpec->cElements - 1].enmType == RTVFSOBJTYPE_END) + { + if (cElements > 1) + cElements--; + else + { + *ppszFinalPath = pSpec->paElements[0].paArgs[0].psz; + return VERR_VFS_CHAIN_PATH_ONLY; + } + } + } + else + return VERR_VFS_CHAIN_EMPTY; + + /* + * Enter the critical section after making sure it has been initialized. + */ + int rc = RTOnce(&g_rtVfsChainElementInitOnce, rtVfsChainElementRegisterInit, NULL); + if (RT_SUCCESS(rc)) + rc = RTCritSectRwEnterShared(&g_rtVfsChainElementCritSect); + if (RT_SUCCESS(rc)) + { + /* + * Resolve and check each element first. + */ + for (uint32_t i = 0; i < cElements; i++) + { + PRTVFSCHAINELEMSPEC const pElement = &pSpec->paElements[i]; + *poffError = pElement->offSpec; + pElement->pProvider = rtVfsChainFindProviderLocked(pElement->pszProvider); + if (pElement->pProvider) + { + rc = pElement->pProvider->pfnValidate(pElement->pProvider, pSpec, pElement, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + continue; + } + else + rc = VERR_VFS_CHAIN_PROVIDER_NOT_FOUND; + break; + } + + /* + * Check that the desired type is compatible with the last element. + */ + if (RT_SUCCESS(rc)) + { + PRTVFSCHAINELEMSPEC const pLast = &pSpec->paElements[cElements - 1]; + if (cElements == pSpec->cElements) + { + if ( pLast->enmType == pSpec->enmDesiredType + || pSpec->enmDesiredType == RTVFSOBJTYPE_BASE + || ( pLast->enmType == RTVFSOBJTYPE_FILE + && pSpec->enmDesiredType == RTVFSOBJTYPE_IO_STREAM) ) + rc = VINF_SUCCESS; + else + { + *poffError = pLast->offSpec; + rc = VERR_VFS_CHAIN_FINAL_TYPE_MISMATCH; + } + } + /* Ends with a path-only element, so check the type of the element preceding it. */ + else if ( pLast->enmType == RTVFSOBJTYPE_DIR + || pLast->enmType == RTVFSOBJTYPE_VFS + || pLast->enmType == RTVFSOBJTYPE_FS_STREAM) + rc = VINF_SUCCESS; + else + { + *poffError = pLast->offSpec; + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Try construct the chain. + */ + RTVFSOBJ hPrevVfsObj = NIL_RTVFSOBJ; /* No extra reference, kept in chain structure. */ + for (uint32_t i = 0; i < cElements; i++) + { + PRTVFSCHAINELEMSPEC const pElement = &pSpec->paElements[i]; + *poffError = pElement->offSpec; + + /* + * Try reuse the VFS objects at the start of the passed in reuse chain. + */ + if (!pReuseSpec) + { /* likely */ } + else + { + if (i < pReuseSpec->cElements) + { + PRTVFSCHAINELEMSPEC const pReuseElement = &pReuseSpec->paElements[i]; + if (pReuseElement->hVfsObj != NIL_RTVFSOBJ) + { + if (strcmp(pElement->pszProvider, pReuseElement->pszProvider) == 0) + { + if (rtVfsChainMatchReusableType(pElement, pReuseElement)) + { + if (pElement->pProvider->pfnCanReuseElement(pElement->pProvider, pSpec, pElement, + pReuseSpec, pReuseElement)) + { + uint32_t cRefs = RTVfsObjRetain(pReuseElement->hVfsObj); + if (cRefs != UINT32_MAX) + { + pElement->hVfsObj = hPrevVfsObj = pReuseElement->hVfsObj; + continue; + } + } + } + } + } + } + pReuseSpec = NULL; + } + + /* + * Instantiate a new VFS object. + */ + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + rc = pElement->pProvider->pfnInstantiate(pElement->pProvider, pSpec, pElement, hPrevVfsObj, + &hVfsObj, poffError, pErrInfo); + if (RT_FAILURE(rc)) + break; + pElement->hVfsObj = hVfsObj; + hPrevVfsObj = hVfsObj; + } + + /* + * Add another reference to the final object and return. + */ + if (RT_SUCCESS(rc)) + { + uint32_t cRefs = RTVfsObjRetain(hPrevVfsObj); + AssertStmt(cRefs != UINT32_MAX, rc = VERR_VFS_CHAIN_IPE); + *phVfsObj = hPrevVfsObj; + *ppszFinalPath = cElements == pSpec->cElements ? NULL : pSpec->paElements[cElements].paArgs[0].psz; + } + } + + int rc2 = RTCritSectRwLeaveShared(&g_rtVfsChainElementCritSect); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + return rc; +} + + +RTDECL(int) RTVfsChainElementDeregisterProvider(PRTVFSCHAINELEMENTREG pRegRec, bool fFromDtor) +{ + /* + * Fend off wildlife. + */ + if (pRegRec == NULL) + return VINF_SUCCESS; + AssertPtrReturn(pRegRec, VERR_INVALID_POINTER); + AssertMsgReturn(pRegRec->uVersion == RTVFSCHAINELEMENTREG_VERSION, ("%#x", pRegRec->uVersion), VERR_INVALID_POINTER); + AssertMsgReturn(pRegRec->uEndMarker == RTVFSCHAINELEMENTREG_VERSION, ("%#zx", pRegRec->uEndMarker), VERR_INVALID_POINTER); + AssertPtrReturn(pRegRec->pszName, VERR_INVALID_POINTER); + + /* + * Take the lock if that's safe. + */ + if (!fFromDtor) + RTCritSectRwEnterExcl(&g_rtVfsChainElementCritSect); + else if (!g_rtVfsChainElementProviderList.pNext) + RTListInit(&g_rtVfsChainElementProviderList); + + /* + * Ok, remove it. + */ + int rc = VERR_NOT_FOUND; + PRTVFSCHAINELEMENTREG pIterator, pIterNext; + RTListForEachSafe(&g_rtVfsChainElementProviderList, pIterator, pIterNext, RTVFSCHAINELEMENTREG, ListEntry) + { + if (pIterator == pRegRec) + { + RTListNodeRemove(&pRegRec->ListEntry); + rc = VINF_SUCCESS; + break; + } + } + + /* + * Leave the lock and return. + */ + if (!fFromDtor) + RTCritSectRwLeaveExcl(&g_rtVfsChainElementCritSect); + return rc; +} + + +RTDECL(int) RTVfsChainOpenObj(const char *pszSpec, uint64_t fFileOpen, uint32_t fObjFlags, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + /* + * Validate input. + */ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + int rc = rtFileRecalcAndValidateFlags(&fFileOpen); + if (RT_FAILURE(rc)) + return rc; + AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK) + && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY, + ("fObjFlags=%#x\n", fObjFlags), + VERR_INVALID_FLAGS); + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = fFileOpen; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + *phVfsObj = hVfsObj; + rc = VINF_SUCCESS; + } + else + { + /* + * Do a file open with the final path on the returned object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + if (hVfs != NIL_RTVFS) + rc = RTVfsObjOpen(hVfs, pszFinal, fFileOpen, fObjFlags, phVfsObj); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirOpenObj(hVfsDir, pszFinal, fFileOpen, fObjFlags, phVfsObj); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_IMPLEMENTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + RTVfsObjRelease(hVfsObj); + } + } + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + * Go via the directory VFS wrapper to avoid duplicating code. + */ + RTVFSDIR hVfsParentDir = NIL_RTVFSDIR; + const char *pszFilename; + if (RTPathHasPath(pszSpec)) + { + char *pszCopy = RTStrDup(pszSpec); + if (pszCopy) + { + RTPathStripFilename(pszCopy); + rc = RTVfsDirOpenNormal(pszCopy, 0 /*fOpen*/, &hVfsParentDir); + RTStrFree(pszCopy); + } + else + rc = VERR_NO_STR_MEMORY; + pszFilename = RTPathFilename(pszSpec); + } + else + { + pszFilename = pszSpec; + rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsParentDir); + } + if (RT_SUCCESS(rc)) + { + rc = RTVfsDirOpenObj(hVfsParentDir, pszFilename, fFileOpen, fObjFlags, phVfsObj); + RTVfsDirRelease(hVfsParentDir); + } + + RTVfsChainSpecFree(pSpec); + return rc; +} + + +RTDECL(int) RTVfsChainOpenDir(const char *pszSpec, uint32_t fOpen, + PRTVFSDIR phVfsDir, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + int rc; + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = RTFILE_O_READ; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + /* Try convert it to a directory object and we're done. */ + *phVfsDir = RTVfsObjToDir(hVfsObj); + if (*phVfsDir) + rc = VINF_SUCCESS; + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + else + { + /* + * Do a file open with the final path on the returned object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + if (hVfs != NIL_RTVFS) + rc = RTVfsDirOpen(hVfs, pszFinal, fOpen, phVfsDir); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirOpenDir(hVfsDir, pszFinal, fOpen, phVfsDir); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_IMPLEMENTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + } + RTVfsObjRelease(hVfsObj); + } + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + */ + rc = RTVfsDirOpenNormal(pszSpec, fOpen, phVfsDir); + + RTVfsChainSpecFree(pSpec); + return rc; +} + + +RTDECL(int) RTVfsChainOpenParentDir(const char *pszSpec, uint32_t fOpen, PRTVFSDIR phVfsDir, const char **ppszChild, + uint32_t *poffError, PRTERRINFO pErrInfo) +{ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER); + AssertPtrReturn(ppszChild, VERR_INVALID_POINTER); + *ppszChild = NULL; + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Process the spec from the end, trying to find the child part of it. + * We cannot use RTPathFilename here because we must ignore trailing slashes. + */ + const char * const pszEnd = RTStrEnd(pszSpec, RTSTR_MAX); + const char *pszChild = pszEnd; + while ( pszChild != pszSpec + && RTPATH_IS_SLASH(pszChild[-1])) + pszChild--; + while ( pszChild != pszSpec + && !RTPATH_IS_SLASH(pszChild[-1]) + && !RTPATH_IS_VOLSEP(pszChild[-1])) + pszChild--; + size_t const cchChild = pszEnd - pszChild; + *ppszChild = pszChild; + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + int rc; + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + /* + * Check that it ends with a path-only element and that this in turn ends with + * what pszChild points to. (We cannot easiy figure out the parent part of + * an element that isn't path-only, so we don't bother trying try.) + */ + PRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1]; + if (pLast->pszProvider == NULL) + { + size_t cchFinal = strlen(pLast->paArgs[0].psz); + if ( cchFinal >= cchChild + && memcmp(&pLast->paArgs[0].psz[cchFinal - cchChild], pszChild, cchChild + 1) == 0) + { + /* + * Drop the child part so we have a path to the parent, then setup the chain. + */ + if (cchFinal > cchChild) + pLast->paArgs[0].psz[cchFinal - cchChild] = '\0'; + else + pSpec->cElements--; + + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = fOpen; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + Assert(cchFinal == cchChild); + + /* Try convert it to a file object and we're done. */ + *phVfsDir = RTVfsObjToDir(hVfsObj); + if (*phVfsDir) + rc = VINF_SUCCESS; + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + else + { + /* + * Do a file open with the final path on the returned object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + if (hVfs != NIL_RTVFS) + rc = RTVfsDirOpen(hVfs, pszFinal, fOpen, phVfsDir); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirOpenDir(hVfsDir, pszFinal, fOpen, phVfsDir); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_IMPLEMENTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + } + RTVfsObjRelease(hVfsObj); + } + } + else + rc = VERR_VFS_CHAIN_TOO_SHORT_FOR_PARENT; + } + else + rc = VERR_VFS_CHAIN_NOT_PATH_ONLY; + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + */ + if (RTPathHasPath(pszSpec)) + { + char *pszCopy = RTStrDup(pszSpec); + if (pszCopy) + { + RTPathStripFilename(pszCopy); + rc = RTVfsDirOpenNormal(pszCopy, fOpen, phVfsDir); + RTStrFree(pszCopy); + } + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = RTVfsDirOpenNormal(".", fOpen, phVfsDir); + + RTVfsChainSpecFree(pSpec); + return rc; + +} + + +RTDECL(int) RTVfsChainOpenFile(const char *pszSpec, uint64_t fOpen, + PRTVFSFILE phVfsFile, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + int rc; + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_FILE, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = fOpen; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + /* Try convert it to a file object and we're done. */ + *phVfsFile = RTVfsObjToFile(hVfsObj); + if (*phVfsFile) + rc = VINF_SUCCESS; + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + else + { + /* + * Do a file open with the final path on the returned object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + if (hVfs != NIL_RTVFS) + rc = RTVfsFileOpen(hVfs, pszFinal, fOpen, phVfsFile); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirOpenFile(hVfsDir, pszFinal, fOpen, phVfsFile); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_IMPLEMENTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + } + RTVfsObjRelease(hVfsObj); + } + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszSpec, fOpen); + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, &hVfsFile); + if (RT_SUCCESS(rc)) + *phVfsFile = hVfsFile; + else + RTFileClose(hFile); + } + + RTVfsChainSpecFree(pSpec); + return rc; +} + + +RTDECL(int) RTVfsChainOpenIoStream(const char *pszSpec, uint64_t fOpen, + PRTVFSIOSTREAM phVfsIos, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + int rc; + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_IO_STREAM, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = fOpen; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + /* Try convert it to an I/O object and we're done. */ + *phVfsIos = RTVfsObjToIoStream(hVfsObj); + if (*phVfsIos) + rc = VINF_SUCCESS; + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + else + { + /* + * Do a file open with the final path on the returned object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + if (hVfs != NIL_RTVFS) + rc = RTVfsFileOpen(hVfs, pszFinal, fOpen, &hVfsFile); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirOpenFile(hVfsDir, pszFinal, fOpen, &hVfsFile); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_IMPLEMENTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + if (RT_SUCCESS(rc)) + { + *phVfsIos = RTVfsFileToIoStream(hVfsFile); + if (*phVfsIos) + rc = VINF_SUCCESS; + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + RTVfsFileRelease(hVfsFile); + } + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + } + RTVfsObjRelease(hVfsObj); + } + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszSpec, fOpen); + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsIos = RTVfsFileToIoStream(hVfsFile); + RTVfsFileRelease(hVfsFile); + } + else + RTFileClose(hFile); + } + + RTVfsChainSpecFree(pSpec); + return rc; +} + + +/** + * The equivalent of RTPathQueryInfoEx + */ +RTDECL(int) RTVfsChainQueryInfo(const char *pszSpec, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, + uint32_t fFlags, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + AssertPtrReturn(pszSpec, VERR_INVALID_POINTER); + AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER); + + /* + * Try for a VFS chain first, falling back on regular file system stuff if it's just a path. + */ + int rc; + PRTVFSCHAINSPEC pSpec = NULL; + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0) + { + rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError); + if (RT_FAILURE(rc)) + return rc; + + Assert(pSpec->cElements > 0); + if ( pSpec->cElements > 1 + || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END) + { + const char *pszFinal = NULL; + RTVFSOBJ hVfsObj = NIL_RTVFSOBJ; + pSpec->fOpenFile = RTFILE_O_READ | RTFILE_O_OPEN; + rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!pszFinal) + { + /* + * Do the job on the final object. + */ + rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAdditionalAttribs); + } + else + { + /* + * Do a path query operation on the penultimate object. + */ + RTVFS hVfs = RTVfsObjToVfs(hVfsObj); + RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj); + RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj); + if (hVfs != NIL_RTVFS) + rc = RTVfsQueryPathInfo(hVfs, pszFinal, pObjInfo, enmAdditionalAttribs, fFlags); + else if (hVfsDir != NIL_RTVFSDIR) + rc = RTVfsDirQueryPathInfo(hVfsDir, pszFinal, pObjInfo, enmAdditionalAttribs, fFlags); + else if (hVfsFss != NIL_RTVFSFSSTREAM) + rc = VERR_NOT_SUPPORTED; + else + rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY; + RTVfsRelease(hVfs); + RTVfsDirRelease(hVfsDir); + RTVfsFsStrmRelease(hVfsFss); + } + RTVfsObjRelease(hVfsObj); + } + + RTVfsChainSpecFree(pSpec); + return rc; + } + + /* Only a path element. */ + pszSpec = pSpec->paElements[0].paArgs[0].psz; + } + + /* + * Path to regular file system. + */ + rc = RTPathQueryInfoEx(pszSpec, pObjInfo, enmAdditionalAttribs, fFlags); + + RTVfsChainSpecFree(pSpec); + return rc; +} + + +RTDECL(bool) RTVfsChainIsSpec(const char *pszSpec) +{ + return pszSpec + && strncmp(pszSpec, RT_STR_TUPLE(RTVFSCHAIN_SPEC_PREFIX)) == 0; +} + + +RTDECL(int) RTVfsChainQueryFinalPath(const char *pszSpec, char **ppszFinalPath, uint32_t *poffError) +{ + /* Make sure we've got an error info variable. */ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + + /* + * If not chain specifier, just duplicate the input and return. + */ + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) != 0) + return RTStrDupEx(ppszFinalPath, pszSpec); + + /* + * Parse it and check out the last element. + */ + PRTVFSCHAINSPEC pSpec = NULL; + int rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError); + if (RT_SUCCESS(rc)) + { + PCRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1]; + if (pLast->pszProvider == NULL) + rc = RTStrDupEx(ppszFinalPath, pLast->paArgs[0].psz); + else + { + rc = VERR_VFS_CHAIN_NOT_PATH_ONLY; + *poffError = pLast->offSpec; + } + RTVfsChainSpecFree(pSpec); + } + return rc; +} + + +RTDECL(int) RTVfsChainSplitOffFinalPath(char *pszSpec, char **ppszSpec, char **ppszFinalPath, uint32_t *poffError) +{ + /* Make sure we've got an error info variable. */ + uint32_t offErrorIgn; + if (!poffError) + poffError = &offErrorIgn; + *poffError = 0; + + /* + * If not chain specifier, just duplicate the input and return. + */ + if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) != 0) + { + *ppszSpec = NULL; + *ppszFinalPath = pszSpec; + return VINF_SUCCESS; + } + + /* + * Parse it and check out the last element. + */ + PRTVFSCHAINSPEC pSpec = NULL; + int rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError); + if (RT_SUCCESS(rc)) + { + Assert(pSpec->cElements > 0); + PCRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1]; + if (pLast->pszProvider == NULL) + { + char *psz = &pszSpec[pLast->offSpec]; + *ppszFinalPath = psz; + if (pSpec->cElements > 1) + { + *ppszSpec = pszSpec; + + /* Remove the separator and any whitespace around it. */ + while ( psz != pszSpec + && RT_C_IS_SPACE(psz[-1])) + psz--; + if ( psz != pszSpec + && ( psz[-1] == ':' + || psz[-1] == '|')) + psz--; + while ( psz != pszSpec + && RT_C_IS_SPACE(psz[-1])) + psz--; + *psz = '\0'; + } + else + *ppszSpec = NULL; + } + else + { + *ppszFinalPath = NULL; + *ppszSpec = pszSpec; + } + RTVfsChainSpecFree(pSpec); + } + else + { + *ppszSpec = NULL; + *ppszFinalPath = NULL; + } + return rc; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp b/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp new file mode 100644 index 00000000..33de3f5a --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp @@ -0,0 +1,434 @@ +/* $Id: vfsfss2dir.cpp $ */ +/** @file + * IPRT - Virtual File System, FS write stream dumping in a normal directory. + * + * This is just a simple mechanism to provide a drop in for the TAR creator + * that writes files individually to the disk instead of a TAR archive. It + * has an additional feature for removing the files to help bail out on error. + */ + +/* + * Copyright (C) 2010-2020 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 * +*********************************************************************************************************************************/ +/// @todo #define RTVFSFSS2DIR_USE_DIR +#include "internal/iprt.h" +#include <iprt/vfs.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#ifndef RTVFSFSS2DIR_USE_DIR +# include <iprt/path.h> +#endif +#include <iprt/string.h> +#include <iprt/vfslowlevel.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Undo entry for RTVFSFSSWRITE2DIR. + */ +typedef struct RTVFSFSSWRITE2DIRENTRY +{ + /** The list entry (head is RTVFSFSSWRITE2DIR::Entries). */ + RTLISTNODE Entry; + /** The file mode mask. */ + RTFMODE fMode; +#ifdef RTVFSFSS2DIR_USE_DIR + /** The name (relative to RTVFSFSSWRITE2DIR::hVfsBaseDir). */ +#else + /** The name (relative to RTVFSFSSWRITE2DIR::szBaseDir). */ +#endif + RT_FLEXIBLE_ARRAY_EXTENSION + char szName[RT_FLEXIBLE_ARRAY]; +} RTVFSFSSWRITE2DIRENTRY; +/** Pointer to a RTVFSFSSWRITE2DIR undo entry. */ +typedef RTVFSFSSWRITE2DIRENTRY *PRTVFSFSSWRITE2DIRENTRY; + +/** + * FSS write to directory instance. + */ +typedef struct RTVFSFSSWRITE2DIR +{ + /** Flags (RTVFSFSS2DIR_F_XXX). */ + uint32_t fFlags; + /** Number of files and stuff we've created. */ + uint32_t cEntries; + /** Files and stuff we've created (RTVFSFSSWRITE2DIRENTRY). + * This is used for reverting changes on failure. */ + RTLISTANCHOR Entries; +#ifdef RTVFSFSS2DIR_USE_DIR + /** The handle of the base directory. */ + RTVFSDIR hVfsBaseDir; +#else + /** Path to the directory that all operations are relative to. */ + RT_FLEXIBLE_ARRAY_EXTENSION + char szBaseDir[RT_FLEXIBLE_ARRAY]; +#endif +} RTVFSFSSWRITE2DIR; +/** Pointer to a write-to-directory FSS instance. */ +typedef RTVFSFSSWRITE2DIR *PRTVFSFSSWRITE2DIR; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtVfsFssToDir_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo, + uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos); + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsFssToDir_Close(void *pvThis) +{ + PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis; + +#ifdef RTVFSFSS2DIR_USE_DIR + RTVfsDirRelease(pThis->hVfsBaseDir); + pThis->hVfsBaseDir = NIL_RTVFSDIR; +#endif + + PRTVFSFSSWRITE2DIRENTRY pCur; + PRTVFSFSSWRITE2DIRENTRY pNext; + RTListForEachSafe(&pThis->Entries, pCur, pNext, RTVFSFSSWRITE2DIRENTRY, Entry) + { + RTMemFree(pCur); + } + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsFssToDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RT_NOREF(pvThis); + + /* no info here, sorry. */ + RT_ZERO(*pObjInfo); + pObjInfo->Attr.enmAdditional = enmAddAttr; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSFSSTREAMOPS,pfnAdd} + */ +static DECLCALLBACK(int) rtVfsFssToDir_Add(void *pvThis, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags) +{ + PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis; + RT_NOREF(fFlags); + + /* + * Query information about the object. + */ + RTFSOBJINFO ObjInfo; + int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_UNIX); + AssertRCReturn(rc, rc); + + /* + * Deal with files. + */ + if (RTFS_IS_FILE(ObjInfo.Attr.fMode)) + { + RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj); + AssertReturn(hVfsIosSrc != NIL_RTVFSIOSTREAM, VERR_WRONG_TYPE); + + RTVFSIOSTREAM hVfsIosDst; + rc = rtVfsFssToDir_PushFile(pvThis, pszPath, ObjInfo.cbObject, &ObjInfo, 1, 0 /*fFlags*/, &hVfsIosDst); + if (RT_SUCCESS(rc)) + { + rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (size_t)RT_ALIGN(ObjInfo.cbObject, _4K)); + RTVfsIoStrmRelease(hVfsIosDst); + } + RTVfsIoStrmRelease(hVfsIosSrc); + } + /* + * Symbolic links. + */ + else if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) + { + RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj); + AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_WRONG_TYPE); + + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + RT_NOREF(pThis); + + RTVfsSymlinkRelease(hVfsSymlink); + } + /* + * Directories. + */ + else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + /* + * And whatever else we need when we need it... + */ + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSFSSTREAMOPS,pfnPushFile} + */ +static DECLCALLBACK(int) rtVfsFssToDir_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo, + uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos) +{ + PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis; + RT_NOREF(cbFile, fFlags); + int rc; + +#ifndef RTVFSFSS2DIR_USE_DIR + /* + * Join up the path with the base dir and make sure it fits. + */ + char szFullPath[RTPATH_MAX]; + rc = RTPathJoin(szFullPath, sizeof(szFullPath), pThis->szBaseDir, pszPath); + if (RT_SUCCESS(rc)) + { +#endif + /* + * Create an undo entry for it. + */ + size_t const cbRelativePath = strlen(pszPath); + PRTVFSFSSWRITE2DIRENTRY pEntry; + pEntry = (PRTVFSFSSWRITE2DIRENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(RTVFSFSSWRITE2DIRENTRY, szName[cbRelativePath])); + if (pEntry) + { + if (cObjInfo) + pEntry->fMode = (paObjInfo[0].Attr.fMode & ~RTFS_TYPE_MASK) | RTFS_TYPE_FILE; + else + pEntry->fMode = RTFS_TYPE_FILE | 0664; + memcpy(pEntry->szName, pszPath, cbRelativePath); + + /* + * Create the file. + */ + uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE; + fOpen |= ((pEntry->fMode & RTFS_UNIX_ALL_ACCESS_PERMS) << RTFILE_O_CREATE_MODE_SHIFT); + if (!(pThis->fFlags & RTVFSFSS2DIR_F_OVERWRITE_FILES)) + fOpen |= RTFILE_O_CREATE; + else + fOpen |= RTFILE_O_CREATE_REPLACE; +#ifdef RTVFSFSS2DIR_USE_DIR + rc = RTVfsDirOpenFileAsIoStream(pThis->hVfsBaseDir, pszPath, fOpen, phVfsIos); +#else + rc = RTVfsIoStrmOpenNormal(szFullPath, fOpen, phVfsIos); +#endif + if (RT_SUCCESS(rc)) + RTListAppend(&pThis->Entries, &pEntry->Entry); + else + RTMemFree(pEntry); + } + else + rc = VERR_NO_MEMORY; +#ifndef RTVFSFSS2DIR_USE_DIR + } + else if (rc == VERR_BUFFER_OVERFLOW) + rc = VERR_FILENAME_TOO_LONG; +#endif + return rc; +} + + +/** + * @interface_method_impl{RTVFSFSSTREAMOPS,pfnEnd} + */ +static DECLCALLBACK(int) rtVfsFssToDir_End(void *pvThis) +{ + RT_NOREF(pvThis); + return VINF_SUCCESS; +} + + +/** + * The write-to-directory FSS operations. + */ +static const RTVFSFSSTREAMOPS g_rtVfsFssToDirOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_FS_STREAM, + "TarFsStreamWriter", + rtVfsFssToDir_Close, + rtVfsFssToDir_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSFSSTREAMOPS_VERSION, + 0, + NULL, + rtVfsFssToDir_Add, + rtVfsFssToDir_PushFile, + rtVfsFssToDir_End, + RTVFSFSSTREAMOPS_VERSION +}; + + +#ifdef RTVFSFSS2DIR_USE_DIR +RTDECL(int) RTVfsFsStrmToDir(RTVFSDIR hVfsBaseDir, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss) +{ + /* + * Input validation. + */ + AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE); + *phVfsFss = NIL_RTVFSFSSTREAM; + AssertReturn(!(fFlags & ~RTVFSFSS2DIR_F_VALID_MASK), VERR_INVALID_FLAGS); + uint32_t cRefs = RTVfsDirRetain(hVfsBaseDir); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Create the file system stream handle and init our data. + */ + PRTVFSFSSWRITE2DIR pThis; + RTVFSFSSTREAM hVfsFss; + int rc = RTVfsNewFsStream(&g_rtVfsFssToDirOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, false /*fReadOnly*/, + &hVfsFss, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->fFlags = fFlags; + pThis->cEntries = 0; + pThis->hVfsBaseDir = hVfsBaseDir; + RTListInit(&pThis->Entries); + + *phVfsFss = hVfsFss; + return VINF_SUCCESS; + } + RTVfsDirRelease(hVfsBaseDir); + +} +#endif /* RTVFSFSS2DIR_USE_DIR */ + + +RTDECL(int) RTVfsFsStrmToNormalDir(const char *pszBaseDir, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss) +{ +#ifdef RTVFSFSS2DIR_USE_DIR + RTVFSDIR hVfsBaseDir; + int rc = RTVfsDirOpenNormal(pszBaseDir, 0 /*fFlags*/, &hVfsBaseDir); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFsStrmToDir(hVfsBaseDir, fFlags, phVfsFss); + RTVfsDirRelease(hVfsBaseDir); + } +#else + + /* + * Input validation. + */ + AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE); + *phVfsFss = NIL_RTVFSFSSTREAM; + AssertReturn(!(fFlags & ~RTVFSFSS2DIR_F_VALID_MASK), VERR_INVALID_FLAGS); + AssertPtrReturn(pszBaseDir, VERR_INVALID_POINTER); + AssertReturn(*pszBaseDir != '\0', VERR_INVALID_NAME); + + /* + * Straighten the path and make sure it's an existing directory. + */ + char szAbsPath[RTPATH_MAX]; + int rc = RTPathAbs(pszBaseDir, szAbsPath, sizeof(szAbsPath)); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfo(szAbsPath, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + { + /* + * Create the file system stream handle and init our data. + */ + size_t const cbBaseDir = strlen(szAbsPath) + 1; + PRTVFSFSSWRITE2DIR pThis; + RTVFSFSSTREAM hVfsFss; + rc = RTVfsNewFsStream(&g_rtVfsFssToDirOps, RT_UOFFSETOF_DYN(RTVFSFSSWRITE2DIR, szBaseDir[cbBaseDir]), + NIL_RTVFS, NIL_RTVFSLOCK, false /*fReadOnly*/, &hVfsFss, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->fFlags = fFlags; + pThis->cEntries = 0; + RTListInit(&pThis->Entries); + memcpy(pThis->szBaseDir, szAbsPath, cbBaseDir); + + *phVfsFss = hVfsFss; + return VINF_SUCCESS; + } + } + else + rc = VERR_NOT_A_DIRECTORY; + } + } +#endif + return rc; +} + + +RTDECL(int) RTVfsFsStrmToDirUndo(RTVFSFSSTREAM hVfsFss) +{ + /* + * Validate input. + */ + PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)RTVfsFsStreamToPrivate(hVfsFss, &g_rtVfsFssToDirOps); + AssertReturn(pThis, VERR_WRONG_TYPE); + + /* + * Do the job, in reverse order. Dropping stuff we + * successfully remove from the list. + */ + int rc = VINF_SUCCESS; + PRTVFSFSSWRITE2DIRENTRY pCur; + PRTVFSFSSWRITE2DIRENTRY pPrev; + RTListForEachReverseSafe(&pThis->Entries, pCur, pPrev, RTVFSFSSWRITE2DIRENTRY, Entry) + { +#ifdef RTVFSFSS2DIR_USE_DIR + int rc2 = RTVfsDirUnlinkEntry(pThis->hVfsBaseDir, pCur->szName); +#else + char szFullPath[RTPATH_MAX]; + int rc2 = RTPathJoin(szFullPath, sizeof(szFullPath), pThis->szBaseDir, pCur->szName); + AssertRC(rc2); + if (RT_SUCCESS(rc2)) + rc2 = RTPathUnlink(szFullPath, 0 /*fUnlink*/); +#endif + if ( RT_SUCCESS(rc2) + || rc2 == VERR_PATH_NOT_FOUND + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_NOT_FOUND) + { + RTListNodeRemove(&pCur->Entry); + RTMemFree(pCur); + } + else if (RT_SUCCESS(rc)) + rc = rc2; + } + return rc; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp b/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp new file mode 100644 index 00000000..87cbf72f --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp @@ -0,0 +1,228 @@ +/* $Id: vfsiosmisc.cpp $ */ +/** @file + * IPRT - Virtual File System, Misc I/O Stream Operations. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + + + +RTDECL(int) RTVfsIoStrmValidateUtf8Encoding(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, PRTFOFF poffError) +{ + /* + * Validate input. + */ + if (poffError) + { + AssertPtrReturn(poffError, VINF_SUCCESS); + *poffError = 0; + } + AssertReturn(!(fFlags & ~RTVFS_VALIDATE_UTF8_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * The loop. + */ + char achBuf[1024 + 1]; + size_t cbUsed = 0; + int rc; + for (;;) + { + /* + * Fill the buffer + */ + size_t cbRead = 0; + rc = RTVfsIoStrmRead(hVfsIos, &achBuf[cbUsed], sizeof(achBuf) - cbUsed - 1, true /*fBlocking*/, &cbRead); + if (RT_FAILURE(rc)) + break; + cbUsed += cbRead; + if (!cbUsed) + { + Assert(rc == VINF_EOF); + break; + } + achBuf[sizeof(achBuf) - 1] = '\0'; + + /* + * Process the data in the buffer, maybe leaving the final chars till + * the next round. + */ + const char *pszCur = achBuf; + size_t offEnd = rc == VINF_EOF + ? cbUsed + : cbUsed >= 7 + ? cbUsed - 7 + : 0; + size_t off; + while ((off = (pszCur - &achBuf[0])) < offEnd) + { + RTUNICP uc; + rc = RTStrGetCpEx(&pszCur, &uc); + if (RT_FAILURE(rc)) + break; + if (!uc) + { + if (fFlags & RTVFS_VALIDATE_UTF8_NO_NULL) + { + rc = VERR_INVALID_UTF8_ENCODING; + break; + } + } + else if (uc > 0x10ffff) + { + if (fFlags & RTVFS_VALIDATE_UTF8_BY_RTC_3629) + { + rc = VERR_INVALID_UTF8_ENCODING; + break; + } + } + } + + if (off < cbUsed) + { + cbUsed -= off; + memmove(achBuf, pszCur, cbUsed); + } + } + + /* + * Set the offset on failure. + */ + if (poffError && RT_FAILURE(rc)) + { + } + + return rc == VINF_EOF ? VINF_SUCCESS : rc; +} + + +/** Header size. */ +#define READ_ALL_HEADER_SIZE 0x20 +/** The header magic. It's followed by the size (both size_t). */ +#define READ_ALL_HEADER_MAGIC UINT32_C(0x11223355) + +RTDECL(int) RTVfsIoStrmReadAll(RTVFSIOSTREAM hVfsIos, void **ppvBuf, size_t *pcbBuf) +{ + /* + * Try query the object information and in case the stream has a known + * size we could use for guidance. + */ + RTFSOBJINFO ObjInfo; + int rc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING); + size_t cbAllocated = RT_SUCCESS(rc) && ObjInfo.cbObject > 0 && ObjInfo.cbObject < _1G + ? (size_t)ObjInfo.cbObject + 1 : _16K; + cbAllocated += READ_ALL_HEADER_SIZE; + void *pvBuf = RTMemAlloc(cbAllocated); + if (pvBuf) + { + memset(pvBuf, 0xfe, READ_ALL_HEADER_SIZE); + size_t off = 0; + for (;;) + { + /* + * Handle buffer growing and detecting the end of it all. + */ + size_t cbToRead = cbAllocated - off - READ_ALL_HEADER_SIZE - 1; + if (!cbToRead) + { + /* The end? */ + uint8_t bIgn; + size_t cbIgn; + rc = RTVfsIoStrmRead(hVfsIos, &bIgn, 0, true /*fBlocking*/, &cbIgn); + if (rc == VINF_EOF) + break; + + /* Grow the buffer. */ + cbAllocated -= READ_ALL_HEADER_SIZE - 1; + cbAllocated = RT_MAX(RT_MIN(cbAllocated, _32M), _1K); + cbAllocated = RT_ALIGN_Z(cbAllocated, _4K); + cbAllocated += READ_ALL_HEADER_SIZE + 1; + + void *pvNew = RTMemRealloc(pvBuf, cbAllocated); + AssertBreakStmt(pvNew, rc = VERR_NO_MEMORY); + pvBuf = pvNew; + + cbToRead = cbAllocated - off - READ_ALL_HEADER_SIZE - 1; + } + Assert(cbToRead < cbAllocated); + + /* + * Read. + */ + size_t cbActual; + rc = RTVfsIoStrmRead(hVfsIos, (uint8_t *)pvBuf + READ_ALL_HEADER_SIZE + off, cbToRead, + true /*fBlocking*/, &cbActual); + if (RT_FAILURE(rc)) + break; + Assert(cbActual > 0); + Assert(cbActual <= cbToRead); + off += cbActual; + if (rc == VINF_EOF) + break; + } + Assert(rc != VERR_EOF); + if (RT_SUCCESS(rc)) + { + ((size_t *)pvBuf)[0] = READ_ALL_HEADER_MAGIC; + ((size_t *)pvBuf)[1] = off; + ((uint8_t *)pvBuf)[READ_ALL_HEADER_SIZE + off] = 0; + + *ppvBuf = (uint8_t *)pvBuf + READ_ALL_HEADER_SIZE; + *pcbBuf = off; + return VINF_SUCCESS; + } + + RTMemFree(pvBuf); + } + else + rc = VERR_NO_MEMORY; + *ppvBuf = NULL; + *pcbBuf = 0; + return rc; +} + + +RTDECL(void) RTVfsIoStrmReadAllFree(void *pvBuf, size_t cbBuf) +{ + AssertPtrReturnVoid(pvBuf); + + /* Spool back to the start of the header. */ + pvBuf = (uint8_t *)pvBuf - READ_ALL_HEADER_SIZE; + + /* Make sure the caller isn't messing with us. Hardcoded, but works. */ + Assert(((size_t *)pvBuf)[0] == READ_ALL_HEADER_MAGIC); + Assert(((size_t *)pvBuf)[1] == cbBuf); RT_NOREF_PV(cbBuf); + + /* Free it. */ + RTMemFree(pvBuf); +} + diff --git a/src/VBox/Runtime/common/vfs/vfsmemory.cpp b/src/VBox/Runtime/common/vfs/vfsmemory.cpp new file mode 100644 index 00000000..4238b151 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsmemory.cpp @@ -0,0 +1,943 @@ +/* $Id: vfsmemory.cpp $ */ +/** @file + * IPRT - Virtual File System, Memory Backed VFS. + */ + +/* + * Copyright (C) 2010-2020 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; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsmisc.cpp b/src/VBox/Runtime/common/vfs/vfsmisc.cpp new file mode 100644 index 00000000..9da91615 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsmisc.cpp @@ -0,0 +1,89 @@ +/* $Id: vfsmisc.cpp $ */ +/** @file + * IPRT - Virtual File System, Misc functions with heavy dependencies. + */ + +/* + * Copyright (C) 2010-2020 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/assert.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/handle.h> + + + +RTDECL(int) RTVfsIoStrmFromStdHandle(RTHANDLESTD enmStdHandle, uint64_t fOpen, bool fLeaveOpen, + PRTVFSIOSTREAM phVfsIos) +{ + /* + * Input validation. + */ + AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER); + *phVfsIos = NIL_RTVFSIOSTREAM; + AssertReturn( enmStdHandle == RTHANDLESTD_INPUT + || enmStdHandle == RTHANDLESTD_OUTPUT + || enmStdHandle == RTHANDLESTD_ERROR, + VERR_INVALID_PARAMETER); + AssertReturn(!(fOpen & ~RTFILE_O_VALID_MASK), VERR_INVALID_PARAMETER); + if (enmStdHandle == RTHANDLESTD_INPUT) + fOpen |= RTFILE_O_READ; + else + fOpen |= RTFILE_O_WRITE; + + /* + * Open the handle and see what we get back. + */ + RTHANDLE h; + int rc = RTHandleGetStandard(enmStdHandle, &h); + if (RT_SUCCESS(rc)) + { + switch (h.enmType) + { + case RTHANDLETYPE_FILE: + rc = RTVfsIoStrmFromRTFile(h.u.hFile, fOpen, fLeaveOpen, phVfsIos); + break; + + case RTHANDLETYPE_PIPE: + rc = RTVfsIoStrmFromRTPipe(h.u.hPipe, fLeaveOpen, phVfsIos); + break; + + case RTHANDLETYPE_SOCKET: + /** @todo */ + rc = VERR_NOT_IMPLEMENTED; + break; + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + } + + return rc; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsmount.cpp b/src/VBox/Runtime/common/vfs/vfsmount.cpp new file mode 100644 index 00000000..f71c97c2 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsmount.cpp @@ -0,0 +1,574 @@ +/* $Id: vfsmount.cpp $ */ +/** @file + * IPRT - Virtual File System, Mounting. + */ + +/* + * Copyright (C) 2012-2020 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_VFS +#include <iprt/vfs.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/fsvfs.h> +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/string.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/formats/fat.h> +#include <iprt/formats/iso9660.h> +#include <iprt/formats/udf.h> +#include <iprt/formats/ext.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Buffer structure for the detection routines. */ +typedef union RTVFSMOUNTBUF +{ + uint8_t ab[2048]; + uint32_t au32[2048/4]; + FATBOOTSECTOR Bootsector; + ISO9660VOLDESCHDR IsoHdr; +} RTVFSMOUNTBUF; +AssertCompileSize(RTVFSMOUNTBUF, 2048); +typedef RTVFSMOUNTBUF *PRTVFSMOUNTBUF; + + + +/** + * Checks if the given 2K sector at offset 32KB looks like ISO-9660 or UDF. + * + * @returns true if likely ISO or UDF, otherwise false. + * @param pVolDescHdr Whatever is at offset 32KB. 2KB buffer. + */ +static bool rtVfsMountIsIsoFs(PCISO9660VOLDESCHDR pVolDescHdr) +{ + if ( memcmp(pVolDescHdr->achStdId, RT_STR_TUPLE(ISO9660VOLDESC_STD_ID)) == 0 + && pVolDescHdr->bDescType <= ISO9660VOLDESC_TYPE_PARTITION + && pVolDescHdr->bDescVersion != 0 + && pVolDescHdr->bDescVersion <= 3 /* don't be too picky, just increase the likelyhood */ ) + return true; + + if ( memcmp(pVolDescHdr->achStdId, RT_STR_TUPLE(UDF_EXT_VOL_DESC_STD_ID_BEGIN)) == 0 + && pVolDescHdr->bDescType == UDF_EXT_VOL_DESC_TYPE + && pVolDescHdr->bDescVersion == UDF_EXT_VOL_DESC_VERSION) + return true; + + return false; +} + + +/** + * Check if the given bootsector is a NTFS boot sector. + * + * @returns true if NTFS, false if not. + * @param pBootSector The boot sector to inspect. + */ +static bool rtVfsMountIsNtfs(PCFATBOOTSECTOR pBootSector) +{ + if (memcmp(pBootSector->achOemName, RT_STR_TUPLE("NTFS ")) != 0) + return false; + + uint16_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Bpb331.cbSector); + if ( cbSector < 0x100 + || cbSector >= 0x1000 + || (cbSector & 0xff) != 0) + { + Log2(("rtVfsMountIsNtfs: cbSector=%#x: out of range\n", cbSector)); + return false; + } + + if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb331.cSectorsPerCluster) + || pBootSector->Bpb.Bpb331.cSectorsPerCluster == 0 + || pBootSector->Bpb.Bpb331.cSectorsPerCluster > 128) + { + Log2(("rtVfsMountIsNtfs: cSectorsPerCluster=%#x: out of range\n", pBootSector->Bpb.Bpb331.cSectorsPerCluster)); + return false; + } + + if ((uint32_t)pBootSector->Bpb.Bpb331.cSectorsPerCluster * cbSector > _64K) + { + Log2(("rtVfsMountIsNtfs: cSectorsPerCluster=%#x * cbSector=%#x => %#x: out of range\n", + pBootSector->Bpb.Bpb331.cSectorsPerCluster, cbSector, + (uint32_t)pBootSector->Bpb.Bpb331.cSectorsPerCluster * cbSector)); + return false; + } + + if ( pBootSector->Bpb.Bpb331.cReservedSectors != 0 + || pBootSector->Bpb.Bpb331.cMaxRootDirEntries != 0 + || pBootSector->Bpb.Bpb331.cTotalSectors16 != 0 + || pBootSector->Bpb.Bpb331.cTotalSectors32 != 0 + || pBootSector->Bpb.Bpb331.cSectorsPerFat != 0 + || pBootSector->Bpb.Bpb331.cFats != 0) + { + Log2(("rtVfsMountIsNtfs: cReservedSectors=%#x cMaxRootDirEntries=%#x cTotalSectors=%#x cTotalSectors32=%#x cSectorsPerFat=%#x cFats=%#x: should all be zero, but one or more aren't\n", + RT_LE2H_U16(pBootSector->Bpb.Bpb331.cReservedSectors), + RT_LE2H_U16(pBootSector->Bpb.Bpb331.cMaxRootDirEntries), + RT_LE2H_U16(pBootSector->Bpb.Bpb331.cTotalSectors16), + RT_LE2H_U32(pBootSector->Bpb.Bpb331.cTotalSectors32), + RT_LE2H_U16(pBootSector->Bpb.Bpb331.cSectorsPerFat), + pBootSector->Bpb.Bpb331.cFats)); + return false; + } + + /** @todo NTFS specific checks: MFT cluster number, cluster per index block. */ + + return true; +} + + +/** + * Check if the given bootsector is a HPFS boot sector. + * + * @returns true if NTFS, false if not. + * @param pBootSector The boot sector to inspect. + * @param hVfsFileIn The volume file. + * @param pBuf2 A 2nd buffer. + */ +static bool rtVfsMountIsHpfs(PCFATBOOTSECTOR pBootSector, RTVFSFILE hVfsFileIn, PRTVFSMOUNTBUF pBuf2) +{ + if (memcmp(pBootSector->Bpb.Ebpb.achType, RT_STR_TUPLE("HPFS ")) != 0) + return false; + + /* Superblock is at sector 16, spare superblock at 17. */ + int rc = RTVfsFileReadAt(hVfsFileIn, 16 * 512, pBuf2, 512 * 2, NULL); + if (RT_FAILURE(rc)) + { + Log2(("rtVfsMountIsHpfs: Error reading superblock: %Rrc\n", rc)); + return false; + } + + if ( RT_LE2H_U32(pBuf2->au32[0]) != UINT32_C(0xf995e849) + || RT_LE2H_U32(pBuf2->au32[1]) != UINT32_C(0xfa53e9c5) + || RT_LE2H_U32(pBuf2->au32[512/4 + 0]) != UINT32_C(0xf9911849) + || RT_LE2H_U32(pBuf2->au32[512/4 + 1]) != UINT32_C(0xfa5229c5)) + { + Log2(("rtVfsMountIsHpfs: Superblock or spare superblock signature mismatch: %#x %#x %#x %#x\n", + RT_LE2H_U32(pBuf2->au32[0]), RT_LE2H_U32(pBuf2->au32[1]), + RT_LE2H_U32(pBuf2->au32[512/4 + 0]), RT_LE2H_U32(pBuf2->au32[512/4 + 1]) )); + return false; + } + + return true; +} + + +/** + * Check if the given bootsector is a FAT boot sector. + * + * @returns true if NTFS, false if not. + * @param pBootSector The boot sector to inspect. + * @param pbRaw Pointer to the raw boot sector buffer. + * @param cbRaw Number of bytes read starting with the boot + * sector (which @a pbRaw points to). + * @param hVfsFileIn The volume file. + * @param pBuf2 A 2nd buffer. + */ +static bool rtVfsMountIsFat(PCFATBOOTSECTOR pBootSector, uint8_t const *pbRaw, size_t cbRaw, + RTVFSFILE hVfsFileIn, PRTVFSMOUNTBUF pBuf2) +{ + Assert(cbRaw >= 1024); + + /* + * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have + * a signature and we ASSUME this is the case for all floppies formated by it. + */ + if (pBootSector->uSignature != FATBOOTSECTOR_SIGNATURE) + { + if (pBootSector->uSignature != 0) + return false; + + /* + * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it. + * Instead the following are three words and a 9 byte build date + * string. The remaining space is zero filled. + * + * Note! No idea how this would look like for 8" floppies, only got 5"1/4'. + * + * ASSUME all non-BPB disks are using this format. + */ + if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */ + || pBootSector->abJmp[1] < 0x2f + || pBootSector->abJmp[1] >= 0x80 + || pBootSector->abJmp[2] == 0x90 /* nop */) + { + Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs\n", pBootSector->abJmp)); + return false; + } + + /* Check the FAT ID so we can tell if this is double or single sided, as well as being a valid FAT12 start. */ + if ( (pbRaw[512] != 0xfe && pbRaw[0] != 0xff) + || pbRaw[512 + 1] != 0xff + || pbRaw[512 + 2] != 0xff) + { + Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs\n", &pbRaw[512])); + return false; + } + + uint32_t const offJump = 2 + pBootSector->abJmp[1]; + uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */; + Assert(offFirstZero >= RT_UOFFSETOF(FATBOOTSECTOR, Bpb)); + uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero, + sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_UOFFSETOF(FATBOOTSECTOR, Bpb))); + + if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0)) + { + Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs\n", + offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero)); + return false; + } + } + else + { + /* + * DOS 2.0 or later. + * + * Start by checking if we've got a known jump instruction first, because + * that will give us a max (E)BPB size hint. + */ + uint8_t offJmp = UINT8_MAX; + if ( pBootSector->abJmp[0] == 0xeb + && pBootSector->abJmp[1] <= 0x7f) + offJmp = pBootSector->abJmp[1] + 2; + else if ( pBootSector->abJmp[0] == 0x90 + && pBootSector->abJmp[1] == 0xeb + && pBootSector->abJmp[2] <= 0x7f) + offJmp = pBootSector->abJmp[2] + 3; + else if ( pBootSector->abJmp[0] == 0xe9 + && pBootSector->abJmp[2] <= 0x7f) + offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2])); + uint8_t const cbMaxBpb = offJmp - RT_UOFFSETOF(FATBOOTSECTOR, Bpb); + if (cbMaxBpb < sizeof(FATBPB20)) + { + Log2(("rtVfsMountIsFat: DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)\n", offJmp, cbMaxBpb)); + return false; + } + + if ( pBootSector->Bpb.Bpb20.cFats == 0 + || pBootSector->Bpb.Bpb20.cFats > 4) + { + if (pBootSector->Bpb.Bpb20.cFats == 0) + Log2(("rtVfsMountIsFat: DOS signature, number of FATs is zero, so not FAT file system\n")); + else + Log2(("rtVfsMountIsFat: DOS signature, too many FATs: %#x\n", pBootSector->Bpb.Bpb20.cFats)); + return false; + } + + if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia)) + { + Log2(("rtVfsMountIsFat: DOS signature, invalid media byte: %#x\n", pBootSector->Bpb.Bpb20.bMedia)); + return false; + } + + uint16_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Bpb20.cbSector); + if ( cbSector != 512 + && cbSector != 4096 + && cbSector != 1024 + && cbSector != 128) + { + Log2(("rtVfsMountIsFat: DOS signature, unsupported sector size: %#x\n", cbSector)); + return false; + } + + if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster) + || !pBootSector->Bpb.Bpb20.cSectorsPerCluster) + { + Log2(("rtVfsMountIsFat: DOS signature, cluster size not non-zero power of two: %#x", + pBootSector->Bpb.Bpb20.cSectorsPerCluster)); + return false; + } + + uint16_t const cReservedSectors = RT_LE2H_U16(pBootSector->Bpb.Bpb20.cReservedSectors); + if ( cReservedSectors == 0 + || cReservedSectors >= _32K) + { + Log2(("rtVfsMountIsFat: DOS signature, bogus reserved sector count: %#x\n", cReservedSectors)); + return false; + } + + /* + * Match the media byte with the first FAT byte and check that the next + * 4 bits are set. (To match further bytes in the FAT we'd need to + * determin the FAT type, which is too much hazzle to do here.) + */ + uint8_t const *pbFat; + if ((size_t)cReservedSectors * cbSector < cbRaw) + pbFat = &pbRaw[cReservedSectors * cbSector]; + else + { + int rc = RTVfsFileReadAt(hVfsFileIn, cReservedSectors * cbSector, pBuf2, 512, NULL); + if (RT_FAILURE(rc)) + { + Log2(("rtVfsMountIsFat: error reading first FAT sector at %#x: %Rrc\n", cReservedSectors * cbSector, rc)); + return false; + } + pbFat = pBuf2->ab; + } + if (*pbFat != pBootSector->Bpb.Bpb20.bMedia) + { + Log2(("rtVfsMountIsFat: Media byte and FAT ID mismatch: %#x vs %#x (%.8Rhxs)\n", + pbFat[0], pBootSector->Bpb.Bpb20.bMedia, pbFat)); + return false; + } + if ((pbFat[1] & 0xf) != 0xf) + { + Log2(("rtVfsMountIsFat: Media byte and FAT ID mismatch: %#x vs %#x (%.8Rhxs)\n", + pbFat[0], pBootSector->Bpb.Bpb20.bMedia, pbFat)); + return false; + } + } + + return true; +} + + +/** + * Check if the given bootsector is an ext2/3/4 super block. + * + * @returns true if NTFS, false if not. + * @param pSuperBlock The ext2 superblock. + */ +static bool rtVfsMountIsExt(PCEXTSUPERBLOCK pSuperBlock) +{ + if (RT_LE2H_U16(pSuperBlock->u16Signature) != EXT_SB_SIGNATURE) + return false; + + uint32_t cShift = RT_LE2H_U32(pSuperBlock->cLogBlockSize); + if (cShift > 54) + { + Log2(("rtVfsMountIsExt: cLogBlockSize=%#x: out of range\n", cShift)); + return false; + } + + cShift = RT_LE2H_U32(pSuperBlock->cLogClusterSize); + if (cShift > 54) + { + Log2(("rtVfsMountIsExt: cLogClusterSize=%#x: out of range\n", cShift)); + return false; + } + + /* Some more checks here would be nice actually since a 16-bit word and a + couple of field limits doesn't feel all that conclusive. */ + + return true; +} + + +/** + * Does the file system detection and mounting. + * + * Since we only support a handful of file systems at the moment and the + * interface isn't yet extensible in any way, we combine the file system + * recognition code for all. This reduces the number of reads we need to do and + * avoids unnecessary processing. + * + * @returns IPRT status code. + * @param hVfsFileIn The volume file. + * @param fFlags RTVFSMTN_F_XXX. + * @param pBuf Pointer to the primary buffer + * @param pBuf2 Pointer to the secondary buffer. + * @param phVfs Where to return the . + * @param pErrInfo Where to return additional error information. + * Optional. + */ +static int rtVfsMountInner(RTVFSFILE hVfsFileIn, uint32_t fFlags, RTVFSMOUNTBUF *pBuf, + RTVFSMOUNTBUF *pBuf2, PRTVFS phVfs, PRTERRINFO pErrInfo) +{ + AssertCompile(sizeof(*pBuf) >= ISO9660_SECTOR_SIZE); + + /* Start by checking for ISO-9660 and UDFS since these may have confusing + data at the start of the volume. */ + int rc = RTVfsFileReadAt(hVfsFileIn, _32K, pBuf, ISO9660_SECTOR_SIZE, NULL); + if (RT_SUCCESS(rc)) + { + if (rtVfsMountIsIsoFs(&pBuf->IsoHdr)) + { + Log(("RTVfsMount: Detected ISO-9660 or UDF.\n")); + return RTFsIso9660VolOpen(hVfsFileIn, 0 /*fFlags*/, phVfs, pErrInfo); + } + } + + /* Now read the boot sector and whatever the next 1536 bytes may contain. + With ext2 superblock at 1024, we can recognize quite a bit thru this read. */ + rc = RTVfsFileReadAt(hVfsFileIn, 0, pBuf, sizeof(*pBuf), NULL); + if (RT_FAILURE(rc)) + return RTErrInfoSet(pErrInfo, rc, "Error reading boot sector"); + + if (rtVfsMountIsNtfs(&pBuf->Bootsector)) + return RTFsNtfsVolOpen(hVfsFileIn, fFlags, 0 /*fNtfsFlags*/, phVfs, pErrInfo); + + if (rtVfsMountIsHpfs(&pBuf->Bootsector, hVfsFileIn, pBuf2)) + return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "HPFS not yet supported"); + + if (rtVfsMountIsFat(&pBuf->Bootsector, pBuf->ab, sizeof(*pBuf), hVfsFileIn, pBuf2)) + { + Log(("RTVfsMount: Detected ISO-9660 or UDF.\n")); + return RTFsFatVolOpen(hVfsFileIn, RT_BOOL(fFlags & RTVFSMNT_F_READ_ONLY), 0 /*offBootSector*/, phVfs, pErrInfo); + } + + AssertCompile(sizeof(*pBuf) >= 1024 + sizeof(EXTSUPERBLOCK)); + if (rtVfsMountIsExt((PCEXTSUPERBLOCK)&pBuf->ab[1024])) + { + Log(("RTVfsMount: Detected EXT2/3/4.\n")); + return RTFsExtVolOpen(hVfsFileIn, fFlags, 0 /*fExt2Flags*/, phVfs, pErrInfo); + } + + return VERR_VFS_UNSUPPORTED_FORMAT; +} + + +RTDECL(int) RTVfsMountVol(RTVFSFILE hVfsFileIn, uint32_t fFlags, PRTVFS phVfs, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS); + AssertPtrReturn(hVfsFileIn, VERR_INVALID_HANDLE); + AssertPtrReturn(phVfs, VERR_INVALID_HANDLE); + + *phVfs = NIL_RTVFS; + + RTVFSMOUNTBUF *pBufs = (RTVFSMOUNTBUF *)RTMemTmpAlloc(sizeof(*pBufs) * 2); + AssertReturn(pBufs, VERR_NO_TMP_MEMORY); + + int rc = rtVfsMountInner(hVfsFileIn, fFlags, pBufs, pBufs + 1, phVfs, pErrInfo); + + RTMemTmpFree(pBufs); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtVfsChainMountVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg); + + /* + * Basic checks. + */ + if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE) + return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE; + if ( pElement->enmType != RTVFSOBJTYPE_VFS + && pElement->enmType != RTVFSOBJTYPE_DIR) + return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS; + if (pElement->cArgs > 1) + return VERR_VFS_CHAIN_AT_MOST_ONE_ARG; + + /* + * Parse the flag if present, save in pElement->uProvider. + */ + bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ; + if (pElement->cArgs > 0) + { + const char *psz = pElement->paArgs[0].psz; + if (*psz) + { + if (!strcmp(psz, "ro")) + fReadOnly = true; + else if (!strcmp(psz, "rw")) + fReadOnly = false; + else + { + *poffError = pElement->paArgs[0].offSpec; + return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument"); + } + } + } + + pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtVfsChainMountVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, poffError); + + int rc; + RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj); + if (hVfsFileIn != NIL_RTVFSFILE) + { + RTVFS hVfs; + rc = RTVfsMountVol(hVfsFileIn, (uint32_t)pElement->uProvider, &hVfs, pErrInfo); + RTVfsFileRelease(hVfsFileIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromVfs(hVfs); + RTVfsRelease(hVfs); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + } + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtVfsChainMountVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pReuseSpec); + if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider + || !pReuseElement->paArgs[0].uProvider) + return true; + return false; +} + + +/** VFS chain element 'file'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainMountVolReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "mount", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Open a file system, requires a file object on the left side.\n" + "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n", + /* pfnValidate = */ rtVfsChainMountVol_Validate, + /* pfnInstantiate = */ rtVfsChainMountVol_Instantiate, + /* pfnCanReuseElement = */ rtVfsChainMountVol_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainMountVolReg, rtVfsChainMountVolReg); + diff --git a/src/VBox/Runtime/common/vfs/vfsmsg.cpp b/src/VBox/Runtime/common/vfs/vfsmsg.cpp new file mode 100644 index 00000000..c2fa2ba4 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsmsg.cpp @@ -0,0 +1,68 @@ +/* $Id: vfsmsg.cpp $ */ +/** @file + * IPRT - Virtual File System, Error Messaging for Chains. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> + +#include <iprt/errcore.h> +#include <iprt/message.h> + + +RTDECL(void) RTVfsChainMsgError(const char *pszFunction, const char *pszSpec, int rc, uint32_t offError, PRTERRINFO pErrInfo) +{ + if (RTErrInfoIsSet(pErrInfo)) + { + if (offError > 0) + RTMsgError("%s failed with rc=%Rrc: %s\n" + " '%s'\n" + " %*s^\n", + pszFunction, rc, pErrInfo->pszMsg, pszSpec, offError, ""); + else + RTMsgError("%s failed to open '%s': %Rrc: %s\n", pszFunction, pszSpec, rc, pErrInfo->pszMsg); + } + else + { + if (offError > 0) + RTMsgError("%s failed with rc=%Rrc:\n" + " '%s'\n" + " %*s^\n", + pszFunction, rc, pszSpec, offError, ""); + else + RTMsgError("%s failed to open '%s': %Rrc\n", pszFunction, pszSpec, rc); + } +} + + +RTDECL(RTEXITCODE) RTVfsChainMsgErrorExitFailure(const char *pszFunction, const char *pszSpec, + int rc, uint32_t offError, PRTERRINFO pErrInfo) +{ + RTVfsChainMsgError(pszFunction, pszSpec, rc, offError, pErrInfo); + return RTEXITCODE_FAILURE; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsprogress.cpp b/src/VBox/Runtime/common/vfs/vfsprogress.cpp new file mode 100644 index 00000000..4500ae99 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsprogress.cpp @@ -0,0 +1,542 @@ +/* $Id: vfsprogress.cpp $ */ +/** @file + * IPRT - Virtual File System, progress filter for files. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* 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, + 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, + 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; +} + diff --git a/src/VBox/Runtime/common/vfs/vfsreadahead.cpp b/src/VBox/Runtime/common/vfs/vfsreadahead.cpp new file mode 100644 index 00000000..dbf8672f --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsreadahead.cpp @@ -0,0 +1,1001 @@ +/* $Id: vfsreadahead.cpp $ */ +/** @file + * IPRT - Virtual File System, Read-Ahead Thread. + */ + +/* + * Copyright (C) 2010-2020 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_VFS +#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/log.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/critsect.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Buffer descriptor. + */ +typedef struct RTVFSREADAHEADBUFDESC +{ + /** List entry. */ + RTLISTNODE ListEntry; + /** The offset of this extent within the file. */ + uint64_t off; + /** The amount of the buffer that has been filled. + * (Buffer size is RTVFSREADAHEAD::cbBuffer.) */ + uint32_t cbFilled; + /** */ + uint32_t volatile fReserved; + /** Pointer to the buffer. */ + uint8_t *pbBuffer; +} RTVFSREADAHEADBUFDESC; +/** Pointer to a memory file extent. */ +typedef RTVFSREADAHEADBUFDESC *PRTVFSREADAHEADBUFDESC; + +/** + * Read ahead file or I/O stream. + */ +typedef struct RTVFSREADAHEAD +{ + /** The I/O critical section (protects offActual). + * The thread doing I/O or seeking always need to own this. */ + RTCRITSECT IoCritSect; + + /** The critical section protecting the buffer lists and offConsumer. + * + * This can be taken while holding IoCritSect as that eliminates a race + * condition between the read ahead thread inserting into ConsumerList and + * a consumer thread deciding to do a direct read. */ + RTCRITSECT BufferCritSect; + /** List of buffers available for consumption. + * The producer thread (hThread) puts buffers into this list once it's done + * reading into them. The consumer moves them to the FreeList once the + * current position has passed beyond each buffer. */ + RTLISTANCHOR ConsumerList; + /** List of buffers available for the producer. */ + RTLISTANCHOR FreeList; + + /** The current file position from the consumer point of view. */ + uint64_t offConsumer; + + /** The end-of-file(/stream) offset. This is initially UINT64_MAX and later + * set when reading past EOF. */ + uint64_t offEof; + + /** The read ahead thread. */ + RTTHREAD hThread; + /** Set when we want the thread to terminate. */ + bool volatile fTerminateThread; + /** Creation flags. */ + uint32_t fFlags; + + /** The I/O stream we read from. */ + RTVFSIOSTREAM hIos; + /** The file face of hIos, if we're fronting for an actual file. */ + RTVFSFILE hFile; + /** The buffer size. */ + uint32_t cbBuffer; + /** The number of buffers. */ + uint32_t cBuffers; + /** Single big buffer allocation, cBuffers * cbBuffer in size. */ + uint8_t *pbAllBuffers; + /** Array of buffer descriptors (cBuffers in size). */ + RTVFSREADAHEADBUFDESC aBufDescs[1]; +} RTVFSREADAHEAD; +/** Pointer to a memory file. */ +typedef RTVFSREADAHEAD *PRTVFSREADAHEAD; + + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Close(void *pvThis) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + int rc; + + /* + * Stop the read-ahead thread. + */ + if (pThis->hThread != NIL_RTTHREAD) + { + ASMAtomicWriteBool(&pThis->fTerminateThread, true); + rc = RTThreadUserSignal(pThis->hThread); + AssertRC(rc); + rc = RTThreadWait(pThis->hThread, RT_INDEFINITE_WAIT, NULL); + AssertRCReturn(rc, rc); + pThis->hThread = NIL_RTTHREAD; + } + + /* + * Release the upstream objects. + */ + RTCritSectEnter(&pThis->IoCritSect); + + RTVfsIoStrmRelease(pThis->hIos); + pThis->hIos = NIL_RTVFSIOSTREAM; + RTVfsFileRelease(pThis->hFile); + pThis->hFile = NIL_RTVFSFILE; + + RTCritSectLeave(&pThis->IoCritSect); + + /* + * Free the buffers. + */ + RTCritSectEnter(&pThis->BufferCritSect); + if (pThis->pbAllBuffers) + { + RTMemPageFree(pThis->pbAllBuffers, pThis->cBuffers * pThis->cbBuffer); + pThis->pbAllBuffers = NULL; + } + RTCritSectLeave(&pThis->BufferCritSect); + + /* + * Destroy the critical sections. + */ + RTCritSectDelete(&pThis->BufferCritSect); + RTCritSectDelete(&pThis->IoCritSect); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsReadAhead_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + return RTVfsIoStrmQueryInfo(pThis->hIos, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + + Assert(pSgBuf->cSegs == 1); /* Caller deals with multiple SGs. */ + + /* + * We loop here to repeat the buffer search after entering the I/O critical + * section, just in case a buffer got inserted while we were waiting for it. + */ + int rc = VINF_SUCCESS; + uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg; + size_t cbDst = pSgBuf->paSegs[0].cbSeg; + size_t cbTotalRead = 0; + bool fPokeReader = false; + bool fOwnsIoCritSect = false; + RTCritSectEnter(&pThis->BufferCritSect); + for (;;) + { + /* + * Try satisfy the read from the buffers. + */ + uint64_t offCur = pThis->offConsumer; + if (off != -1) + { + offCur = (uint64_t)off; + if (pThis->offConsumer != offCur) + fPokeReader = true; /* If the current position changed, poke it in case it stopped at EOF. */ + pThis->offConsumer = offCur; + } + + PRTVFSREADAHEADBUFDESC pBufDesc, pNextBufDesc; + RTListForEachSafe(&pThis->ConsumerList, pBufDesc, pNextBufDesc, RTVFSREADAHEADBUFDESC, ListEntry) + { + /* The buffers are sorted and reads must start in a buffer if + anything should be taken from the buffer (at least for now). */ + if (offCur < pBufDesc->off) + break; + + /* Anything we can read from this buffer? */ + uint64_t offCurBuf = offCur - pBufDesc->off; + if (offCurBuf < pBufDesc->cbFilled) + { + size_t const cbFromCurBuf = RT_MIN(pBufDesc->cbFilled - offCurBuf, cbDst); + memcpy(pbDst, pBufDesc->pbBuffer + offCurBuf, cbFromCurBuf); + pbDst += cbFromCurBuf; + cbDst -= cbFromCurBuf; + cbTotalRead += cbFromCurBuf; + offCur += cbFromCurBuf; + } + + /* Discard buffers we've read past. */ + if (pBufDesc->off + pBufDesc->cbFilled <= offCur) + { + RTListNodeRemove(&pBufDesc->ListEntry); + RTListAppend(&pThis->FreeList, &pBufDesc->ListEntry); + fPokeReader = true; /* Poke it as there are now more buffers available. */ + } + + /* Stop if we're done. */ + if (!cbDst) + break; + } + + pThis->offConsumer = offCur; + if (off != -1) + off = offCur; + + if (!cbDst) + break; + + /* + * Check if we've reached the end of the file/stream. + */ + if (offCur >= pThis->offEof) + { + rc = pcbRead ? VINF_EOF : VERR_EOF; + Log(("rtVfsReadAhead_Read: ret %Rrc; offCur=%#llx offEof=%#llx\n", rc, offCur, pThis->offEof)); + break; + } + + + /* + * First time around we don't own the I/O critsect and need to take it + * and repeat the above buffer reading code. + */ + if (!fOwnsIoCritSect) + { + RTCritSectLeave(&pThis->BufferCritSect); + RTCritSectEnter(&pThis->IoCritSect); + RTCritSectEnter(&pThis->BufferCritSect); + fOwnsIoCritSect = true; + continue; + } + + + /* + * Do a direct read of the remaining data. + */ + if (off == -1) + { + RTFOFF offActual = RTVfsIoStrmTell(pThis->hIos); + if (offActual >= 0 && (uint64_t)offActual != offCur) + off = offCur; + } + RTSGSEG TmpSeg = { pbDst, cbDst }; + RTSGBUF TmpSgBuf; + RTSgBufInit(&TmpSgBuf, &TmpSeg, 1); + size_t cbThisRead = cbDst; + rc = RTVfsIoStrmSgRead(pThis->hIos, off, &TmpSgBuf, fBlocking, pcbRead ? &cbThisRead : NULL); + if (RT_SUCCESS(rc)) + { + cbTotalRead += cbThisRead; + offCur += cbThisRead; + pThis->offConsumer = offCur; + if (rc != VINF_EOF) + fPokeReader = true; + else + { + pThis->offEof = offCur; + Log(("rtVfsReadAhead_Read: EOF %llu (%#llx)\n", pThis->offEof, pThis->offEof)); + } + } + /* else if (rc == VERR_EOF): hard to say where exactly the current position + is here as cannot have had a non-NULL pcbRead. Set offEof later. */ + break; + } + RTCritSectLeave(&pThis->BufferCritSect); + if (fOwnsIoCritSect) + RTCritSectLeave(&pThis->IoCritSect); + if (fPokeReader && rc != VINF_EOF && rc != VERR_EOF) + RTThreadUserSignal(pThis->hThread); + + if (pcbRead) + *pcbRead = cbTotalRead; + Assert(cbTotalRead <= pSgBuf->paSegs[0].cbSeg); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten); + AssertFailed(); + return VERR_ACCESS_DENIED; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Flush(void *pvThis) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + return RTVfsIoStrmFlush(pThis->hIos); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} + */ +static DECLCALLBACK(int) rtVfsReadAhead_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, + uint32_t *pfRetEvents) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + if (pThis->hThread != NIL_RTTHREAD) + { + /** @todo poll one with read-ahead thread. */ + return VERR_NOT_IMPLEMENTED; + } + return RTVfsIoStrmPoll(pThis->hIos, fEvents, cMillies, fIntr, pfRetEvents); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Tell(void *pvThis, PRTFOFF poffActual) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + + RTCritSectEnter(&pThis->BufferCritSect); + *poffActual = pThis->offConsumer; + RTCritSectLeave(&pThis->BufferCritSect); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtVfsReadAhead_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); + RT_NOREF_PV(fMode); RT_NOREF_PV(fMask); /// @todo int rc = RTVfsFileSetMode(pThis->hFile, fMode, fMask); + int rc = VERR_NOT_SUPPORTED; + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtVfsReadAhead_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); + RT_NOREF_PV(pAccessTime); RT_NOREF_PV(pModificationTime); RT_NOREF_PV(pChangeTime); RT_NOREF_PV(pBirthTime); + /// @todo int rc = RTVfsFileSetTimes(pThis->hFile, pAccessTime, pModificationTime, pChangeTime, pBirthTime); + int rc = VERR_NOT_SUPPORTED; + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtVfsReadAhead_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); + RT_NOREF_PV(uid); RT_NOREF_PV(gid); + /// @todo int rc = RTVfsFileSetOwner(pThis->hFile, uid, gid); + int rc = VERR_NOT_SUPPORTED; + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnSeek} + */ +static DECLCALLBACK(int) rtVfsReadAhead_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); /* protects against concurrent I/O using the offset. */ + RTCritSectEnter(&pThis->BufferCritSect); /* protects offConsumer */ + + uint64_t offActual = UINT64_MAX; + int rc = RTVfsFileSeek(pThis->hFile, offSeek, uMethod, &offActual); + if (RT_SUCCESS(rc)) + { + pThis->offConsumer = offActual; + if (poffActual) + *poffActual = offActual; + } + + RTCritSectLeave(&pThis->BufferCritSect); + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} + */ +static DECLCALLBACK(int) rtVfsReadAhead_QuerySize(void *pvThis, uint64_t *pcbFile) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); /* paranoia */ + int rc = RTVfsFileQuerySize(pThis->hFile, pcbFile); + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnSetSize} + */ +static DECLCALLBACK(int) rtVfsReadAhead_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); /* paranoia */ + int rc = RTVfsFileSetSize(pThis->hFile, cbFile, fFlags); + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize} + */ +static DECLCALLBACK(int) rtVfsReadAhead_QueryMaxSize(void *pvThis, uint64_t *pcbMax) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis; + AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED); + + RTCritSectEnter(&pThis->IoCritSect); /* paranoia */ + int rc = RTVfsFileQueryMaxSize(pThis->hFile, pcbMax); + RTCritSectLeave(&pThis->IoCritSect); + + return rc; +} + + +/** + * Read ahead I/O stream operations. + */ +DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_VfsReadAheadIosOps = +{ /* Stream */ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_IO_STREAM, + "Read ahead I/O stream", + rtVfsReadAhead_Close, + rtVfsReadAhead_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSIOSTREAMOPS_VERSION, + RTVFSIOSTREAMOPS_FEAT_NO_SG, + rtVfsReadAhead_Read, + rtVfsReadAhead_Write, + rtVfsReadAhead_Flush, + rtVfsReadAhead_PollOne, + rtVfsReadAhead_Tell, + NULL /*Skip*/, + NULL /*ZeroFill*/, + RTVFSIOSTREAMOPS_VERSION, +}; + + +/** + * Read ahead file operations. + */ +DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_VfsReadAheadFileOps = +{ + { /* Stream */ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_FILE, + "Read ahead file", + rtVfsReadAhead_Close, + rtVfsReadAhead_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSIOSTREAMOPS_VERSION, + RTVFSIOSTREAMOPS_FEAT_NO_SG, + rtVfsReadAhead_Read, + rtVfsReadAhead_Write, + rtVfsReadAhead_Flush, + rtVfsReadAhead_PollOne, + rtVfsReadAhead_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), + rtVfsReadAhead_SetMode, + rtVfsReadAhead_SetTimes, + rtVfsReadAhead_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtVfsReadAhead_Seek, + rtVfsReadAhead_QuerySize, + rtVfsReadAhead_SetSize, + rtVfsReadAhead_QueryMaxSize, + RTVFSFILEOPS_VERSION +}; + + +/** + * @callback_method_impl{PFNRTTHREAD, Read ahead thread procedure} + */ +static DECLCALLBACK(int) rtVfsReadAheadThreadProc(RTTHREAD hThreadSelf, void *pvUser) +{ + PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvUser; + Assert(pThis); + + while (!pThis->fTerminateThread) + { + int rc; + + /* + * Is there a buffer handy for reading ahead. + */ + PRTVFSREADAHEADBUFDESC pBufDesc = NULL; + RTCritSectEnter(&pThis->BufferCritSect); + if (!pThis->fTerminateThread) + pBufDesc = RTListRemoveFirst(&pThis->FreeList, RTVFSREADAHEADBUFDESC, ListEntry); + RTCritSectLeave(&pThis->BufferCritSect); + + if (pBufDesc) + { + /* + * Got a buffer, take the I/O lock and read into it. + */ + rc = VERR_CALLBACK_RETURN; + RTCritSectEnter(&pThis->IoCritSect); + if (!pThis->fTerminateThread) + { + + pBufDesc->off = RTVfsIoStrmTell(pThis->hIos); + size_t cbRead = 0; + rc = RTVfsIoStrmRead(pThis->hIos, pBufDesc->pbBuffer, pThis->cbBuffer, true /*fBlocking*/, &cbRead); + if (RT_SUCCESS(rc)) + { + if (rc == VINF_EOF) + { + pThis->offEof = pBufDesc->off + cbRead; + Log(("rtVfsReadAheadThreadProc: EOF %llu (%#llx)\n", pThis->offEof, pThis->offEof)); + } + pBufDesc->cbFilled = (uint32_t)cbRead; + + /* + * Put back the buffer. The consumer list is sorted by offset, but + * we should usually end up appending the buffer. + */ + RTCritSectEnter(&pThis->BufferCritSect); + PRTVFSREADAHEADBUFDESC pAfter = RTListGetLast(&pThis->ConsumerList, RTVFSREADAHEADBUFDESC, ListEntry); + if (!pAfter || pAfter->off <= pBufDesc->off) + RTListAppend(&pThis->ConsumerList, &pBufDesc->ListEntry); + else + { + do + pAfter = RTListGetPrev(&pThis->ConsumerList, pAfter, RTVFSREADAHEADBUFDESC, ListEntry); + while (pAfter && pAfter->off > pBufDesc->off); + if (!pAfter) + RTListPrepend(&pThis->ConsumerList, &pBufDesc->ListEntry); + else + { + Assert(pAfter->off <= pBufDesc->off); + RTListNodeInsertAfter(&pAfter->ListEntry, &pBufDesc->ListEntry); + } + } + RTCritSectLeave(&pThis->BufferCritSect); + pBufDesc = NULL; + +#ifdef RT_STRICT + /* Verify the list ordering. */ + unsigned cAsserted = 0; + uint64_t offAssert = 0; + PRTVFSREADAHEADBUFDESC pAssertCur; + RTListForEach(&pThis->ConsumerList, pAssertCur, RTVFSREADAHEADBUFDESC, ListEntry) + { + Assert(offAssert <= pAssertCur->off); + offAssert = pAssertCur->off; + Assert(cAsserted < pThis->cBuffers); + cAsserted++; + } +#endif + } + else + Assert(rc != VERR_EOF); + } + RTCritSectLeave(&pThis->IoCritSect); + + /* + * If we succeeded and we didn't yet reach the end of the stream, + * loop without delay to start processing the next buffer. + */ + if (RT_LIKELY(!pBufDesc && rc != VINF_EOF)) + continue; + + /* Put any unused buffer back in the free list (termination/failure, not EOF). */ + if (pBufDesc) + { + RTCritSectEnter(&pThis->BufferCritSect); + RTListPrepend(&pThis->FreeList, &pBufDesc->ListEntry); + RTCritSectLeave(&pThis->BufferCritSect); + } + if (pThis->fTerminateThread) + break; + } + + /* + * Wait for more to do. + */ + rc = RTThreadUserWait(hThreadSelf, RT_MS_1MIN); + if (RT_SUCCESS(rc)) + rc = RTThreadUserReset(hThreadSelf); + } + + return VINF_SUCCESS; +} + + +static int rtVfsCreateReadAheadInstance(RTVFSIOSTREAM hVfsIosSrc, RTVFSFILE hVfsFileSrc, uint32_t fFlags, + uint32_t cBuffers, uint32_t cbBuffer, PRTVFSIOSTREAM phVfsIos, PRTVFSFILE phVfsFile) +{ + /* + * Validate input a little. + */ + int rc = VINF_SUCCESS; + AssertStmt(cBuffers < _4K, rc = VERR_OUT_OF_RANGE); + if (cBuffers == 0) + cBuffers = 4; + AssertStmt(cbBuffer <= _4M, rc = VERR_OUT_OF_RANGE); + if (cbBuffer == 0) + cbBuffer = _256K / cBuffers; + AssertStmt(cbBuffer * cBuffers < (ARCH_BITS < 64 ? _64M : _256M), rc = VERR_OUT_OF_RANGE); + AssertStmt(!fFlags, rc = VERR_INVALID_FLAGS); + + if (RT_SUCCESS(rc)) + { + /* + * Create a file or I/O stream instance. + */ + RTVFSFILE hVfsFileReadAhead = NIL_RTVFSFILE; + RTVFSIOSTREAM hVfsIosReadAhead = NIL_RTVFSIOSTREAM; + PRTVFSREADAHEAD pThis; + size_t cbThis = RT_UOFFSETOF_DYN(RTVFSREADAHEAD, aBufDescs[cBuffers]); + if (hVfsFileSrc != NIL_RTVFSFILE) + rc = RTVfsNewFile(&g_VfsReadAheadFileOps, cbThis, RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsFileReadAhead, (void **)&pThis); + else + rc = RTVfsNewIoStream(&g_VfsReadAheadIosOps, cbThis, RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsIosReadAhead, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + RTListInit(&pThis->ConsumerList); + RTListInit(&pThis->FreeList); + pThis->hThread = NIL_RTTHREAD; + pThis->fTerminateThread = false; + pThis->fFlags = fFlags; + pThis->hFile = hVfsFileSrc; + pThis->hIos = hVfsIosSrc; + pThis->cBuffers = cBuffers; + pThis->cbBuffer = cbBuffer; + pThis->offEof = UINT64_MAX; + pThis->offConsumer = RTVfsIoStrmTell(hVfsIosSrc); + if ((RTFOFF)pThis->offConsumer >= 0) + { + rc = RTCritSectInit(&pThis->IoCritSect); + if (RT_SUCCESS(rc)) + rc = RTCritSectInit(&pThis->BufferCritSect); + if (RT_SUCCESS(rc)) + { + pThis->pbAllBuffers = (uint8_t *)RTMemPageAlloc(pThis->cbBuffer * pThis->cBuffers); + if (pThis->pbAllBuffers) + { + for (uint32_t i = 0; i < cBuffers; i++) + { + pThis->aBufDescs[i].cbFilled = 0; + pThis->aBufDescs[i].off = UINT64_MAX / 2; + pThis->aBufDescs[i].pbBuffer = &pThis->pbAllBuffers[cbBuffer * i]; + RTListAppend(&pThis->FreeList, &pThis->aBufDescs[i].ListEntry); + } + + /* + * Create thread. + */ + rc = RTThreadCreate(&pThis->hThread, rtVfsReadAheadThreadProc, pThis, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "vfsreadahead"); + if (RT_SUCCESS(rc)) + { + /* + * We're good. + */ + if (phVfsFile) + *phVfsFile = hVfsFileReadAhead; + else if (hVfsFileReadAhead == NIL_RTVFSFILE) + *phVfsIos = hVfsIosReadAhead; + else + { + *phVfsIos = RTVfsFileToIoStream(hVfsFileReadAhead); + RTVfsFileRelease(hVfsFileReadAhead); + AssertReturn(*phVfsIos != NIL_RTVFSIOSTREAM, VERR_INTERNAL_ERROR_5); + } + return VINF_SUCCESS; + } + } + } + } + else + rc = (int)pThis->offConsumer; + } + } + + RTVfsFileRelease(hVfsFileSrc); + RTVfsIoStrmRelease(hVfsIosSrc); + return rc; +} + + +RTDECL(int) RTVfsCreateReadAheadForIoStream(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, uint32_t cBuffers, uint32_t cbBuffer, + PRTVFSIOSTREAM phVfsIos) +{ + AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER); + *phVfsIos = NIL_RTVFSIOSTREAM; + + /* + * Retain the input stream, trying to obtain a file handle too so we can + * fully mirror it. + */ + uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + RTVFSFILE hVfsFile = RTVfsIoStrmToFile(hVfsIos); + + /* + * Do the job. (This always consumes the above retained references.) + */ + return rtVfsCreateReadAheadInstance(hVfsIos, hVfsFile, fFlags, cBuffers, cbBuffer, phVfsIos, NULL); +} + + +RTDECL(int) RTVfsCreateReadAheadForFile(RTVFSFILE hVfsFile, uint32_t fFlags, uint32_t cBuffers, uint32_t cbBuffer, + PRTVFSFILE phVfsFile) +{ + AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER); + *phVfsFile = NIL_RTVFSFILE; + + /* + * Retain the input file and cast it o an I/O stream. + */ + RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile); + AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE); + uint32_t cRefs = RTVfsFileRetain(hVfsFile); + AssertReturnStmt(cRefs != UINT32_MAX, RTVfsIoStrmRelease(hVfsIos), VERR_INVALID_HANDLE); + + /* + * Do the job. (This always consumes the above retained references.) + */ + return rtVfsCreateReadAheadInstance(hVfsIos, hVfsFile, fFlags, cBuffers, cbBuffer, NULL, phVfsFile); +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtVfsChainReadAhead_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, poffError, pErrInfo); + + /* + * Basics. + */ + if ( pElement->enmType != RTVFSOBJTYPE_FILE + && pElement->enmType != RTVFSOBJTYPE_IO_STREAM) + return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS; + if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID) + return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT; + if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE + && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM) + return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS; + if (pSpec->fOpenFile & RTFILE_O_WRITE) + return VERR_VFS_CHAIN_READ_ONLY_IOS; + if (pElement->cArgs > 2) + return VERR_VFS_CHAIN_AT_MOST_TWO_ARGS; + + /* + * Parse the two optional arguments. + */ + uint32_t cBuffers = 0; + if (pElement->cArgs > 0) + { + const char *psz = pElement->paArgs[0].psz; + if (*psz) + { + int rc = RTStrToUInt32Full(psz, 0, &cBuffers); + if (RT_FAILURE(rc)) + { + *poffError = pElement->paArgs[0].offSpec; + return VERR_VFS_CHAIN_INVALID_ARGUMENT; + } + } + } + + uint32_t cbBuffer = 0; + if (pElement->cArgs > 1) + { + const char *psz = pElement->paArgs[1].psz; + if (*psz) + { + int rc = RTStrToUInt32Full(psz, 0, &cbBuffer); + if (RT_FAILURE(rc)) + { + *poffError = pElement->paArgs[1].offSpec; + return VERR_VFS_CHAIN_INVALID_ARGUMENT; + } + } + } + + /* + * Save the parsed arguments in the spec since their both optional. + */ + pElement->uProvider = RT_MAKE_U64(cBuffers, cbBuffer); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtVfsChainReadAhead_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo); + AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE); + + /* Try for a file if we can. */ + int rc; + RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj); + if (hVfsFileIn != NIL_RTVFSFILE) + { + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + rc = RTVfsCreateReadAheadForFile(hVfsFileIn, 0 /*fFlags*/, RT_LO_U32(pElement->uProvider), + RT_HI_U32(pElement->uProvider), &hVfsFile); + RTVfsFileRelease(hVfsFileIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + } + else if (pElement->enmType == RTVFSOBJTYPE_IO_STREAM) + { + RTVFSIOSTREAM hVfsIosIn = RTVfsObjToIoStream(hPrevVfsObj); + if (hVfsIosIn != NIL_RTVFSIOSTREAM) + { + RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM; + rc = RTVfsCreateReadAheadForIoStream(hVfsIosIn, 0 /*fFlags*/, RT_LO_U32(pElement->uProvider), + RT_HI_U32(pElement->uProvider), &hVfsIos); + RTVfsIoStrmRelease(hVfsIosIn); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromIoStream(hVfsIos); + RTVfsIoStrmRelease(hVfsIos); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + } + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + else + rc = VERR_VFS_CHAIN_CAST_FAILED; + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtVfsChainReadAhead_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement); + return false; +} + + +/** VFS chain element 'pull'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainReadAheadReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "pull", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Takes an I/O stream or file and provides read-ahead caching.\n" + "Optional first argument specifies how many buffers to use, 0 indicating the default.\n" + "Optional second argument specifies the buffer size, 0 indicating the default.", + /* pfnValidate = */ rtVfsChainReadAhead_Validate, + /* pfnInstantiate = */ rtVfsChainReadAhead_Instantiate, + /* pfnCanReuseElement = */ rtVfsChainReadAhead_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainReadAheadReg, rtVfsChainReadAheadReg); + diff --git a/src/VBox/Runtime/common/vfs/vfsstddir.cpp b/src/VBox/Runtime/common/vfs/vfsstddir.cpp new file mode 100644 index 00000000..598ba862 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsstddir.cpp @@ -0,0 +1,855 @@ +/* $Id: vfsstddir.cpp $ */ +/** @file + * IPRT - Virtual File System, Standard Directory Implementation. + */ + +/* + * Copyright (C) 2010-2020 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_VFS +#include <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/string.h> + +#define RTDIR_AGNOSTIC +#include "internal/dir.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Private data of a standard directory. + */ +typedef struct RTVFSSTDDIR +{ + /** The directory handle. */ + RTDIR hDir; + /** Whether to leave the handle open when the VFS handle is closed. */ + bool fLeaveOpen; + /** Open flags, RTDIR_F_XXX. */ + uint32_t fFlags; + /** Handle to the director so we can make sure it sticks around for symbolic + * link objects. */ + RTVFSDIR hSelf; +} RTVFSSTDDIR; +/** Pointer to the private data of a standard directory. */ +typedef RTVFSSTDDIR *PRTVFSSTDDIR; + + +/** + * Private data of a standard symbolic link. + */ +typedef struct RTVFSSTDSYMLINK +{ + /** Pointer to the VFS directory where the symbolic link lives . */ + PRTVFSSTDDIR pDir; + /** The symbolic link name. */ + RT_FLEXIBLE_ARRAY_EXTENSION + char szSymlink[RT_FLEXIBLE_ARRAY]; +} RTVFSSTDSYMLINK; +/** Pointer to the private data of a standard symbolic link. */ +typedef RTVFSSTDSYMLINK *PRTVFSSTDSYMLINK; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtVfsStdDir_OpenDir(void *pvThis, const char *pszSubDir, uint32_t fFlags, PRTVFSDIR phVfsDir); +static DECLCALLBACK(int) rtVfsStdDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink); +static DECLCALLBACK(int) rtVfsStdDir_QueryEntryInfo(void *pvThis, const char *pszEntry, + PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr); +static int rtVfsDirFromRTDir(RTDIR hDir, uint32_t fFlags, bool fLeaveOpen, PRTVFSDIR phVfsDir); + + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsStdSym_Close(void *pvThis) +{ + PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis; + RTVfsDirRelease(pThis->pDir->hSelf); + pThis->pDir = NULL; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsStdSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis; + return rtVfsStdDir_QueryEntryInfo(pThis->pDir, pThis->szSymlink, pObjInfo, enmAddAttr); +} + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtVfsStdSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + NOREF(pvThis); NOREF(fMode); NOREF(fMask); + return VERR_ACCESS_DENIED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtVfsStdSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime); + return VERR_ACCESS_DENIED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtVfsStdSym_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ + NOREF(pvThis); NOREF(uid); NOREF(gid); + return VERR_ACCESS_DENIED; +} + + +/** + * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead} + */ +static DECLCALLBACK(int) rtVfsStdSym_Read(void *pvThis, char *pszTarget, size_t cbTarget) +{ + PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis; + return RTDirRelSymlinkRead(pThis->pDir->hDir, pThis->szSymlink, pszTarget, cbTarget, 0 /*fRead*/); +} + + +/** + * Symbolic operations for standard directory. + */ +static const RTVFSSYMLINKOPS g_rtVfsStdSymOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_SYMLINK, + "StdSymlink", + rtVfsStdSym_Close, + rtVfsStdSym_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSSYMLINKOPS_VERSION, + 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj), + rtVfsStdSym_SetMode, + rtVfsStdSym_SetTimes, + rtVfsStdSym_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtVfsStdSym_Read, + RTVFSSYMLINKOPS_VERSION +}; + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsStdDir_Close(void *pvThis) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + + int rc; + if (!pThis->fLeaveOpen) + rc = RTDirClose(pThis->hDir); + else + rc = VINF_SUCCESS; + pThis->hDir = NULL; + + return rc; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsStdDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + return RTDirQueryInfo(pThis->hDir, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtVfsStdDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + if (fMask != ~RTFS_TYPE_MASK) + { + RTFSOBJINFO ObjInfo; + int rc = RTDirQueryInfo(pThis->hDir, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return rc; + fMode |= ~fMask & ObjInfo.Attr.fMode; + } + //RTPathSetMode + //return RTFileSetMode(pThis->hDir, fMode); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtVfsStdDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + return RTDirSetTimes(pThis->hDir, pAccessTime, pModificationTime, pChangeTime, pBirthTime); +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtVfsStdDir_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + return RTDirRelPathSetOwner(pThis->hDir, ".", uid, gid, RTPATH_F_FOLLOW_LINK); +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpen} + */ +static DECLCALLBACK(int) rtVfsStdDir_Open(void *pvThis, const char *pszEntry, uint64_t fFileOpen, + uint32_t fObjFlags, PRTVFSOBJ phVfsObj) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + + /* + * This is subject to race conditions, but we haven't too much of a choice + * without going platform specific here (we'll do that eventually). + */ + RTFSOBJINFO ObjInfo; + int rc = RTDirRelPathQueryInfo(pThis->hDir, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + if (fObjFlags & RTVFSOBJ_F_OPEN_DIRECTORY) + { + if ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + { + RTDIR hSubDir; + rc = RTDirRelDirOpenFiltered(pThis->hDir, pszEntry, RTDIRFILTER_NONE, 0 /*fFlags*/, &hSubDir); + if (RT_SUCCESS(rc)) + { + RTVFSDIR hVfsDir; + rc = rtVfsDirFromRTDir(hSubDir, 0 /** @todo subdir open/inherit flags... */, false, &hVfsDir); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + else + RTDirClose(hSubDir); + } + } + else + rc = VERR_ALREADY_EXISTS; + } + else + rc = VERR_IS_A_DIRECTORY; + break; + + case RTFS_TYPE_FILE: + case RTFS_TYPE_DEV_BLOCK: + case RTFS_TYPE_DEV_CHAR: + case RTFS_TYPE_FIFO: + case RTFS_TYPE_SOCKET: + switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FILE: + rc = fObjFlags & RTVFSOBJ_F_OPEN_FILE ? VINF_SUCCESS : VERR_IS_A_FILE; + break; + case RTFS_TYPE_DEV_BLOCK: + rc = fObjFlags & RTVFSOBJ_F_OPEN_DEV_BLOCK ? VINF_SUCCESS : VERR_IS_A_BLOCK_DEVICE; + break; + case RTFS_TYPE_DEV_CHAR: + rc = fObjFlags & RTVFSOBJ_F_OPEN_DEV_CHAR ? VINF_SUCCESS : VERR_IS_A_CHAR_DEVICE; + break; + /** @todo These two types should not result in files, but pure I/O streams. + * possibly char device too. */ + case RTFS_TYPE_FIFO: + rc = fObjFlags & RTVFSOBJ_F_OPEN_FIFO ? VINF_SUCCESS : VERR_IS_A_FIFO; + break; + case RTFS_TYPE_SOCKET: + rc = fObjFlags & RTVFSOBJ_F_OPEN_SOCKET ? VINF_SUCCESS : VERR_IS_A_SOCKET; + break; + default: + rc = VERR_INVALID_FLAGS; + break; + } + if (RT_SUCCESS(rc)) + { + if ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + { + RTFILE hFile; + rc = RTDirRelFileOpen(pThis->hDir, pszEntry, fFileOpen, &hFile); + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsFileFromRTFile(hFile, fFileOpen, false /*fLeaveOpen*/, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + else + RTFileClose(hFile); + } + } + else + rc = VERR_ALREADY_EXISTS; + } + break; + + case RTFS_TYPE_SYMLINK: + if (fObjFlags & RTVFSOBJ_F_OPEN_SYMLINK) + { + uint32_t cRefs = RTVfsDirRetain(pThis->hSelf); + if (cRefs != UINT32_MAX) + { + RTVFSSYMLINK hVfsSymlink; + PRTVFSSTDSYMLINK pNewSymlink; + size_t cchSymlink = strlen(pszEntry); + rc = RTVfsNewSymlink(&g_rtVfsStdSymOps, RT_UOFFSETOF_DYN(RTVFSSTDSYMLINK, szSymlink[cchSymlink + 1]), + NIL_RTVFS, NIL_RTVFSLOCK, &hVfsSymlink, (void **)&pNewSymlink); + if (RT_SUCCESS(rc)) + { + memcpy(pNewSymlink->szSymlink, pszEntry, cchSymlink); + pNewSymlink->szSymlink[cchSymlink] = '\0'; + pNewSymlink->pDir = pThis; + + *phVfsObj = RTVfsObjFromSymlink(hVfsSymlink); + RTVfsSymlinkRelease(hVfsSymlink); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + else + RTVfsDirRelease(pThis->hSelf); + } + else + rc = VERR_INTERNAL_ERROR_2; + } + else + rc = VERR_IS_A_SYMLINK; + break; + + default: + break; + } + } + else if ( rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND) + { + /* + * Consider file or directory creation. + */ + if ( ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE + || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_NOTHING) + { + + if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_FILE) + { + RTFILE hFile; + rc = RTDirRelFileOpen(pThis->hDir, pszEntry, fFileOpen, &hFile); + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsFileFromRTFile(hFile, fFileOpen, false /*fLeaveOpen*/, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + else + RTFileClose(hFile); + } + } + else if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_DIRECTORY) + { + RTDIR hSubDir; + rc = RTDirRelDirCreate(pThis->hDir, pszEntry, (fFileOpen & RTFILE_O_CREATE_MODE_MASK) >> RTFILE_O_CREATE_MODE_SHIFT, + 0 /* fFlags */, &hSubDir); + if (RT_SUCCESS(rc)) + { + RTVFSDIR hVfsDir; + rc = rtVfsDirFromRTDir(hSubDir, 0 /** @todo subdir open/inherit flags... */, false, &hVfsDir); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + AssertStmt(*phVfsObj == NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + else + RTDirClose(hSubDir); + } + } + else + rc = VERR_VFS_UNSUPPORTED_CREATE_TYPE; + } + else + rc = VERR_FILE_NOT_FOUND; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnFollowAbsoluteSymlink} + */ +static DECLCALLBACK(int) rtVfsStdDir_FollowAbsoluteSymlink(void *pvThis, const char *pszRoot, PRTVFSDIR phVfsDir) +{ + //PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + RT_NOREF(pvThis); + /** @todo walking restriction. */ + return RTVfsDirOpenNormal(pszRoot, 0 /*fFlags*/, phVfsDir); +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpenFile} + */ +static DECLCALLBACK(int) rtVfsStdDir_OpenFile(void *pvThis, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + RTFILE hFile; + int rc = RTDirRelFileOpen(pThis->hDir, pszFilename, fOpen, &hFile); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, phVfsFile); + if (RT_FAILURE(rc)) + RTFileClose(hFile); + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpenDir} + */ +static DECLCALLBACK(int) rtVfsStdDir_OpenDir(void *pvThis, const char *pszSubDir, uint32_t fFlags, PRTVFSDIR phVfsDir) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + /** @todo subdir open flags */ + RTDIR hSubDir; + int rc = RTDirRelDirOpenFiltered(pThis->hDir, pszSubDir, RTDIRFILTER_NONE, fFlags, &hSubDir); + if (RT_SUCCESS(rc)) + { + rc = rtVfsDirFromRTDir(hSubDir, fFlags, false, phVfsDir); + if (RT_FAILURE(rc)) + RTDirClose(hSubDir); + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnCreateDir} + */ +static DECLCALLBACK(int) rtVfsStdDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + int rc; + if (!phVfsDir) + rc = RTDirRelDirCreate(pThis->hDir, pszSubDir, fMode, 0 /* fFlags */, NULL); + else + { + RTDIR hSubDir; + rc = RTDirRelDirCreate(pThis->hDir, pszSubDir, fMode, 0 /* fFlags */, &hSubDir); + if (RT_SUCCESS(rc)) + { + /** @todo subdir open flags... */ + rc = rtVfsDirFromRTDir(hSubDir, 0, false, phVfsDir); + if (RT_FAILURE(rc)) + RTDirClose(hSubDir); + } + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink} + */ +static DECLCALLBACK(int) rtVfsStdDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink) +{ + RTFSOBJINFO ObjInfo; + int rc = rtVfsStdDir_QueryEntryInfo(pvThis, pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) + { + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + uint32_t cRefs = RTVfsDirRetain(pThis->hSelf); + if (cRefs != UINT32_MAX) + { + PRTVFSSTDSYMLINK pNewSymlink; + size_t cchSymlink = strlen(pszSymlink); + rc = RTVfsNewSymlink(&g_rtVfsStdSymOps, RT_UOFFSETOF_DYN(RTVFSSTDSYMLINK, szSymlink[cchSymlink + 1]), + NIL_RTVFS, NIL_RTVFSLOCK, phVfsSymlink, (void **)&pNewSymlink); + if (RT_SUCCESS(rc)) + { + memcpy(pNewSymlink->szSymlink, pszSymlink, cchSymlink); + pNewSymlink->szSymlink[cchSymlink] = '\0'; + pNewSymlink->pDir = pThis; + return VINF_SUCCESS; + } + + RTVfsDirRelease(pThis->hSelf); + } + else + rc = VERR_INTERNAL_ERROR_2; + } + else + rc = VERR_NOT_SYMLINK; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink} + */ +static DECLCALLBACK(int) rtVfsStdDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget, + RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + int rc = RTDirRelSymlinkCreate(pThis->hDir, pszSymlink, pszTarget, enmType, 0 /*fCreate*/); + if (RT_SUCCESS(rc)) + { + if (!phVfsSymlink) + return VINF_SUCCESS; + return rtVfsStdDir_OpenSymlink(pThis, pszSymlink, phVfsSymlink); + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnQueryEntryInfo} + */ +static DECLCALLBACK(int) rtVfsStdDir_QueryEntryInfo(void *pvThis, const char *pszEntry, + PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + return RTDirRelPathQueryInfo(pThis->hDir, pszEntry, pObjInfo, enmAddAttr, RTPATH_F_ON_LINK); +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry} + */ +static DECLCALLBACK(int) rtVfsStdDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + if (fType != 0) + { + if (fType == RTFS_TYPE_DIRECTORY) + return RTDirRelDirRemove(pThis->hDir, pszEntry); + + RTFSOBJINFO ObjInfo; + int rc = rtVfsStdDir_QueryEntryInfo(pThis, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return rc; + if ((fType & RTFS_TYPE_MASK) != (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)) + return VERR_WRONG_TYPE; + } + return RTDirRelPathUnlink(pThis->hDir, pszEntry, 0 /*fUnlink*/); +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry} + */ +static DECLCALLBACK(int) rtVfsStdDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + if (fType != 0) + { + RTFSOBJINFO ObjInfo; + int rc = rtVfsStdDir_QueryEntryInfo(pThis, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return rc; + if ((fType & RTFS_TYPE_MASK) != (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)) + return VERR_WRONG_TYPE; + } + + /** @todo RTVFSDIROPS::pfnRenameEntry doesn't really work, this must move to + * file system level. */ + return RTDirRelPathRename(pThis->hDir, pszEntry, pThis->hDir, pszNewName, + RTPATHRENAME_FLAGS_NO_SYMLINKS | RTPATHRENAME_FLAGS_NO_REPLACE); +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnRewindDir} + */ +static DECLCALLBACK(int) rtVfsStdDir_RewindDir(void *pvThis) +{ + NOREF(pvThis); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnReadDir} + */ +static DECLCALLBACK(int) rtVfsStdDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis; + return RTDirReadEx(pThis->hDir, pDirEntry, pcbDirEntry, enmAddAttr, RTPATH_F_ON_LINK); +} + + +/** + * Standard file operations. + */ +DECL_HIDDEN_CONST(const RTVFSDIROPS) g_rtVfsStdDirOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_DIR, + "StdDir", + rtVfsStdDir_Close, + rtVfsStdDir_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSDIROPS_VERSION, + 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj), + rtVfsStdDir_SetMode, + rtVfsStdDir_SetTimes, + rtVfsStdDir_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtVfsStdDir_Open, + rtVfsStdDir_FollowAbsoluteSymlink, + rtVfsStdDir_OpenFile, + rtVfsStdDir_OpenDir, + rtVfsStdDir_CreateDir, + rtVfsStdDir_OpenSymlink, + rtVfsStdDir_CreateSymlink, + rtVfsStdDir_QueryEntryInfo, + rtVfsStdDir_UnlinkEntry, + rtVfsStdDir_RenameEntry, + rtVfsStdDir_RewindDir, + rtVfsStdDir_ReadDir, + RTVFSDIROPS_VERSION +}; + + +/** + * Internal worker for RTVfsDirFromRTDir and RTVfsDirOpenNormal. + * + * @returns IRPT status code. + * @param hDir The IPRT directory handle. + * @param fOpen Reserved for future. + * @param fLeaveOpen Whether to leave it open or close it. + * @param phVfsDir Where to return the handle. + */ +static int rtVfsDirFromRTDir(RTDIR hDir, uint32_t fFlags, bool fLeaveOpen, PRTVFSDIR phVfsDir) +{ + PRTVFSSTDDIR pThis; + RTVFSDIR hVfsDir; + int rc = RTVfsNewDir(&g_rtVfsStdDirOps, sizeof(RTVFSSTDDIR), 0 /*fFlags*/, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsDir, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->hDir = hDir; + pThis->fLeaveOpen = fLeaveOpen; + pThis->fFlags = fFlags; + pThis->hSelf = hVfsDir; + + *phVfsDir = hVfsDir; + return VINF_SUCCESS; + } + return rc; +} + + +RTDECL(int) RTVfsDirFromRTDir(RTDIR hDir, bool fLeaveOpen, PRTVFSDIR phVfsDir) +{ + AssertReturn(RTDirIsValid(hDir), VERR_INVALID_HANDLE); + return rtVfsDirFromRTDir(hDir, hDir->fFlags, fLeaveOpen, phVfsDir); +} + + +RTDECL(int) RTVfsDirOpenNormal(const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir) +{ + /* + * Open the file the normal way and pass it to RTVfsFileFromRTFile. + */ + RTDIR hDir; + int rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_NONE, fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Create a VFS file handle. + */ + rc = rtVfsDirFromRTDir(hDir, fFlags, false /*fLeaveOpen*/, phVfsDir); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + RTDirClose(hDir); + } + return rc; +} + + +RTDECL(bool) RTVfsDirIsStdDir(RTVFSDIR hVfsDir) +{ + return RTVfsDirToPrivate(hVfsDir, &g_rtVfsStdDirOps) != NULL; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtVfsChainStdDir_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec); + + /* + * Basic checks. + */ + if (pElement->enmTypeIn != RTVFSOBJTYPE_INVALID) + return VERR_VFS_CHAIN_MUST_BE_FIRST_ELEMENT; + if (pElement->enmType != RTVFSOBJTYPE_DIR) + return VERR_VFS_CHAIN_ONLY_DIR; + if (pElement->cArgs < 1) + return VERR_VFS_CHAIN_AT_LEAST_ONE_ARG; + + /* + * Parse flag arguments if any, storing them in the element. + */ + uint32_t fFlags = 0; + for (uint32_t i = 1; i < pElement->cArgs; i++) + if (strcmp(pElement->paArgs[i].psz, "deny-ascent") == 0) + fFlags |= RTDIR_F_DENY_ASCENT; + else if (strcmp(pElement->paArgs[i].psz, "allow-ascent") == 0) + fFlags &= ~RTDIR_F_DENY_ASCENT; + else + { + *poffError = pElement->paArgs[i].offSpec; + return RTErrInfoSetF(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Unknown flag argument: %s", pElement->paArgs[i].psz); + } + pElement->uProvider = fFlags; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtVfsChainStdDir_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo); + AssertReturn(hPrevVfsObj == NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE); + + RTVFSDIR hVfsDir; + int rc = RTVfsDirOpenNormal(pElement->paArgs[0].psz, (uint32_t)pElement->uProvider, &hVfsDir); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtVfsChainStdDir_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pReuseSpec); + if (strcmp(pElement->paArgs[0].psz, pReuseElement->paArgs[0].psz) == 0) + if (pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider) + return true; + return false; +} + + +/** VFS chain element 'file'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainStdDirReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "stddir", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Open a real directory. Initial element.\n" + "Takes zero or more flag arguments: deny-ascent, allow-ascent", + /* pfnValidate = */ rtVfsChainStdDir_Validate, + /* pfnInstantiate = */ rtVfsChainStdDir_Instantiate, + /* pfnCanReuseElement = */ rtVfsChainStdDir_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainStdDirReg, rtVfsChainStdDirReg); + diff --git a/src/VBox/Runtime/common/vfs/vfsstdfile.cpp b/src/VBox/Runtime/common/vfs/vfsstdfile.cpp new file mode 100644 index 00000000..82481049 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsstdfile.cpp @@ -0,0 +1,640 @@ +/* $Id: vfsstdfile.cpp $ */ +/** @file + * IPRT - Virtual File System, Standard File Implementation. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Private data of a standard file. + */ +typedef struct RTVFSSTDFILE +{ + /** The file handle. */ + RTFILE hFile; + /** Whether to leave the handle open when the VFS handle is closed. */ + bool fLeaveOpen; +} RTVFSSTDFILE; +/** Pointer to the private data of a standard file. */ +typedef RTVFSSTDFILE *PRTVFSSTDFILE; + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsStdFile_Close(void *pvThis) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + + int rc; + if (!pThis->fLeaveOpen) + rc = RTFileClose(pThis->hFile); + else + rc = VINF_SUCCESS; + pThis->hFile = NIL_RTFILE; + + return rc; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsStdFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + return RTFileQueryInfo(pThis->hFile, pObjInfo, enmAddAttr); +} + + +/** + * RTFileRead and RTFileReadAt does not return VINF_EOF or VINF_TRY_AGAIN, this + * function tries to fix this as best as it can. + * + * This fixing can be subject to races if some other thread or process is + * modifying the file size between the read and our size query here. + * + * @returns VINF_SUCCESS, VINF_EOF or VINF_TRY_AGAIN. + * @param pThis The instance data. + * @param off The offset parameter. + * @param cbToRead The number of bytes attempted read . + * @param cbActuallyRead The number of bytes actually read. + */ +DECLINLINE(int) rtVfsStdFile_ReadFixRC(PRTVFSSTDFILE pThis, RTFOFF off, size_t cbToRead, size_t cbActuallyRead) +{ + /* If the read returned less bytes than requested, it means the end of the + file has been reached. */ + if (cbToRead > cbActuallyRead) + return VINF_EOF; + + /* The other case here is the very special zero byte read at the end of the + file, where we're supposed to indicate EOF. */ + if (cbToRead > 0) + return VINF_SUCCESS; + + uint64_t cbFile; + int rc = RTFileQuerySize(pThis->hFile, &cbFile); + if (RT_FAILURE(rc)) + return rc; + + uint64_t off2; + if (off >= 0) + off2 = off; + else + { + rc = RTFileSeek(pThis->hFile, 0, RTFILE_SEEK_CURRENT, &off2); + if (RT_FAILURE(rc)) + return rc; + } + + return off2 >= cbFile ? VINF_EOF : VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} + */ +static DECLCALLBACK(int) rtVfsStdFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + int rc; + + NOREF(fBlocking); + if (pSgBuf->cSegs == 1) + { + if (off < 0) + rc = RTFileRead( pThis->hFile, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead); + else + rc = RTFileReadAt(pThis->hFile, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead); + if (rc == VINF_SUCCESS && pcbRead) + rc = rtVfsStdFile_ReadFixRC(pThis, off, pSgBuf->paSegs[0].cbSeg, *pcbRead); + } + else + { + size_t cbSeg = 0; + size_t cbRead = 0; + size_t cbReadSeg = 0; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg; + cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + + cbReadSeg = cbSeg; + if (off < 0) + rc = RTFileRead( pThis->hFile, pvSeg, cbSeg, pcbRead ? &cbReadSeg : NULL); + else + rc = RTFileReadAt(pThis->hFile, off, pvSeg, cbSeg, pcbRead ? &cbReadSeg : NULL); + if (RT_FAILURE(rc)) + break; + if (off >= 0) + off += cbReadSeg; + cbRead += cbReadSeg; + if ((pcbRead && cbReadSeg != cbSeg) || rc != VINF_SUCCESS) + break; + } + + if (pcbRead) + { + *pcbRead = cbRead; + if (rc == VINF_SUCCESS) + rc = rtVfsStdFile_ReadFixRC(pThis, off, cbSeg, cbReadSeg); + } + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} + */ +static DECLCALLBACK(int) rtVfsStdFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + int rc; + + NOREF(fBlocking); + if (pSgBuf->cSegs == 1) + { + if (off < 0) + rc = RTFileWrite(pThis->hFile, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten); + else + rc = RTFileWriteAt(pThis->hFile, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten); + } + else + { + size_t cbWritten = 0; + size_t cbWrittenSeg; + size_t *pcbWrittenSeg = pcbWritten ? &cbWrittenSeg : NULL; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg; + size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + + cbWrittenSeg = 0; + if (off < 0) + rc = RTFileWrite(pThis->hFile, pvSeg, cbSeg, pcbWrittenSeg); + else + { + rc = RTFileWriteAt(pThis->hFile, off, pvSeg, cbSeg, pcbWrittenSeg); + off += cbSeg; + } + if (RT_FAILURE(rc)) + break; + if (pcbWritten) + { + cbWritten += cbWrittenSeg; + if (cbWrittenSeg != cbSeg) + break; + } + } + + if (pcbWritten) + *pcbWritten = cbWritten; + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} + */ +static DECLCALLBACK(int) rtVfsStdFile_Flush(void *pvThis) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + int rc = RTFileFlush(pThis->hFile); +#ifdef RT_OS_WINDOWS + /* Workaround for console handles. */ /** @todo push this further down? */ + if ( rc == VERR_INVALID_HANDLE + && RTFileIsValid(pThis->hFile)) + rc = VINF_NOT_SUPPORTED; /* not flushable */ +#endif + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} + */ +static DECLCALLBACK(int) rtVfsStdFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, + uint32_t *pfRetEvents) +{ + NOREF(pvThis); + int rc; + if (fEvents != RTPOLL_EVT_ERROR) + { + *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR; + rc = VINF_SUCCESS; + } + else if (fIntr) + rc = RTThreadSleep(cMillies); + else + { + uint64_t uMsStart = RTTimeMilliTS(); + do + rc = RTThreadSleep(cMillies); + while ( rc == VERR_INTERRUPTED + && !fIntr + && RTTimeMilliTS() - uMsStart < cMillies); + if (rc == VERR_INTERRUPTED) + rc = VERR_TIMEOUT; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} + */ +static DECLCALLBACK(int) rtVfsStdFile_Tell(void *pvThis, PRTFOFF poffActual) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + uint64_t offActual; + int rc = RTFileSeek(pThis->hFile, 0, RTFILE_SEEK_CURRENT, &offActual); + if (RT_SUCCESS(rc)) + *poffActual = (RTFOFF)offActual; + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip} + */ +static DECLCALLBACK(int) rtVfsStdFile_Skip(void *pvThis, RTFOFF cb) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + uint64_t offIgnore; + return RTFileSeek(pThis->hFile, cb, RTFILE_SEEK_CURRENT, &offIgnore); +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtVfsStdFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + if (fMask != ~RTFS_TYPE_MASK) + { +#if 0 + RTFMODE fCurMode; + int rc = RTFileGetMode(pThis->hFile, &fCurMode); + if (RT_FAILURE(rc)) + return rc; + fMode |= ~fMask & fCurMode; +#else + RTFSOBJINFO ObjInfo; + int rc = RTFileQueryInfo(pThis->hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return rc; + fMode |= ~fMask & ObjInfo.Attr.fMode; +#endif + } + return RTFileSetMode(pThis->hFile, fMode); +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtVfsStdFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + return RTFileSetTimes(pThis->hFile, pAccessTime, pModificationTime, pChangeTime, pBirthTime); +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtVfsStdFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ +#if 0 + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + return RTFileSetOwner(pThis->hFile, uid, gid); +#else + NOREF(pvThis); NOREF(uid); NOREF(gid); + return VERR_NOT_IMPLEMENTED; +#endif +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnSeek} + */ +static DECLCALLBACK(int) rtVfsStdFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + uint64_t offActual = 0; + int rc = RTFileSeek(pThis->hFile, offSeek, uMethod, &offActual); + if (RT_SUCCESS(rc)) + *poffActual = offActual; + return rc; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} + */ +static DECLCALLBACK(int) rtVfsStdFile_QuerySize(void *pvThis, uint64_t *pcbFile) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + return RTFileQuerySize(pThis->hFile, pcbFile); +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnSetSize} + */ +static DECLCALLBACK(int) rtVfsStdFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + switch (fFlags & RTVFSFILE_SIZE_F_ACTION_MASK) + { + case RTVFSFILE_SIZE_F_NORMAL: + return RTFileSetSize(pThis->hFile, cbFile); + case RTVFSFILE_SIZE_F_GROW: + return RTFileSetAllocationSize(pThis->hFile, cbFile, RTFILE_ALLOC_SIZE_F_DEFAULT); + case RTVFSFILE_SIZE_F_GROW_KEEP_SIZE: + return RTFileSetAllocationSize(pThis->hFile, cbFile, RTFILE_ALLOC_SIZE_F_KEEP_SIZE); + default: + return VERR_NOT_SUPPORTED; + } +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize} + */ +static DECLCALLBACK(int) rtVfsStdFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax) +{ + PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis; + RTFOFF cbMax = 0; + int rc = RTFileQueryMaxSizeEx(pThis->hFile, &cbMax); + if (RT_SUCCESS(rc)) + *pcbMax = cbMax; + return rc; +} + + +/** + * Standard file operations. + */ +DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsStdFileOps = +{ + { /* Stream */ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_FILE, + "StdFile", + rtVfsStdFile_Close, + rtVfsStdFile_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSIOSTREAMOPS_VERSION, + 0, + rtVfsStdFile_Read, + rtVfsStdFile_Write, + rtVfsStdFile_Flush, + rtVfsStdFile_PollOne, + rtVfsStdFile_Tell, + rtVfsStdFile_Skip, + NULL /*ZeroFill*/, + RTVFSIOSTREAMOPS_VERSION, + }, + RTVFSFILEOPS_VERSION, + 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj), + rtVfsStdFile_SetMode, + rtVfsStdFile_SetTimes, + rtVfsStdFile_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtVfsStdFile_Seek, + rtVfsStdFile_QuerySize, + rtVfsStdFile_SetSize, + rtVfsStdFile_QueryMaxSize, + RTVFSFILEOPS_VERSION +}; + + +/** + * Internal worker for RTVfsFileFromRTFile and RTVfsFileOpenNormal. + * + * @returns IRPT status code. + * @param hFile The IPRT file handle. + * @param fOpen The RTFILE_O_XXX flags. + * @param fLeaveOpen Whether to leave it open or close it. + * @param phVfsFile Where to return the handle. + */ +static int rtVfsFileFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSFILE phVfsFile) +{ + PRTVFSSTDFILE pThis; + RTVFSFILE hVfsFile; + int rc = RTVfsNewFile(&g_rtVfsStdFileOps, sizeof(RTVFSSTDFILE), fOpen, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsFile, (void **)&pThis); + if (RT_FAILURE(rc)) + return rc; + + pThis->hFile = hFile; + pThis->fLeaveOpen = fLeaveOpen; + *phVfsFile = hVfsFile; + return VINF_SUCCESS; +} + + +RTDECL(int) RTVfsFileFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSFILE phVfsFile) +{ + /* + * Check the handle validity. + */ + RTFSOBJINFO ObjInfo; + int rc = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return rc; + + /* + * Set up some fake fOpen flags if necessary and create a VFS file handle. + */ + if (!fOpen) + fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN_CREATE; + + return rtVfsFileFromRTFile(hFile, fOpen, fLeaveOpen, phVfsFile); +} + + +RTDECL(int) RTVfsFileOpenNormal(const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile) +{ + /* + * Open the file the normal way and pass it to RTVfsFileFromRTFile. + */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFilename, fOpen); + if (RT_SUCCESS(rc)) + { + /* + * Create a VFS file handle. + */ + rc = rtVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, phVfsFile); + if (RT_FAILURE(rc)) + RTFileClose(hFile); + } + return rc; +} + + +RTDECL(int) RTVfsIoStrmFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos) +{ + RTVFSFILE hVfsFile; + int rc = RTVfsFileFromRTFile(hFile, fOpen, fLeaveOpen, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsIos = RTVfsFileToIoStream(hVfsFile); + RTVfsFileRelease(hVfsFile); + } + return rc; +} + + +RTDECL(int) RTVfsIoStrmOpenNormal(const char *pszFilename, uint64_t fOpen, PRTVFSIOSTREAM phVfsIos) +{ + RTVFSFILE hVfsFile; + int rc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsIos = RTVfsFileToIoStream(hVfsFile); + RTVfsFileRelease(hVfsFile); + } + return rc; +} + + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtVfsChainStdFile_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg); + + /* + * Basic checks. + */ + if (pElement->enmTypeIn != RTVFSOBJTYPE_INVALID) + return VERR_VFS_CHAIN_MUST_BE_FIRST_ELEMENT; + if ( pElement->enmType != RTVFSOBJTYPE_FILE + && pElement->enmType != RTVFSOBJTYPE_IO_STREAM) + return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS; + + /* + * Join common cause with the 'open' provider. + */ + return RTVfsChainValidateOpenFileOrIoStream(pSpec, pElement, poffError, pErrInfo); +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtVfsChainStdFile_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo); + AssertReturn(hPrevVfsObj == NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE); + + RTVFSFILE hVfsFile; + int rc = RTVfsFileOpenNormal(pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + if (*phVfsObj != NIL_RTVFSOBJ) + return VINF_SUCCESS; + rc = VERR_VFS_CHAIN_CAST_FAILED; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtVfsChainStdFile_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pReuseSpec); + if (strcmp(pElement->paArgs[0].psz, pReuseElement->paArgs[0].psz) == 0) + if (pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider) + return true; + return false; +} + + +/** VFS chain element 'file'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainStdFileReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "stdfile", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Open a real file, providing either a file or an I/O stream object. Initial element.\n" + "First argument is the filename path.\n" + "Second argument is access mode, optional: r, w, rw.\n" + "Third argument is open disposition, optional: create, create-replace, open, open-create, open-append, open-truncate.\n" + "Forth argument is file sharing, optional: nr, nw, nrw, d.", + /* pfnValidate = */ rtVfsChainStdFile_Validate, + /* pfnInstantiate = */ rtVfsChainStdFile_Instantiate, + /* pfnCanReuseElement = */ rtVfsChainStdFile_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainStdFileReg, rtVfsChainStdFileReg); + diff --git a/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp b/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp new file mode 100644 index 00000000..c823efcd --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp @@ -0,0 +1,317 @@ +/* $Id: vfsstdpipe.cpp $ */ +/** @file + * IPRT - Virtual File System, Standard Pipe I/O stream Implementation. + */ + +/* + * Copyright (C) 2010-2020 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 <iprt/vfs.h> +#include <iprt/vfslowlevel.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Private data of a standard pipe. + */ +typedef struct RTVFSSTDPIPE +{ + /** The pipe handle. */ + RTPIPE hPipe; + /** Whether to leave the handle open when the VFS handle is closed. */ + bool fLeaveOpen; + /** Set if primarily read, clear if write. */ + bool fReadPipe; + /** Fake stream position. */ + uint64_t offFakePos; +} RTVFSSTDPIPE; +/** Pointer to the private data of a standard pipe. */ +typedef RTVFSSTDPIPE *PRTVFSSTDPIPE; + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtVfsStdPipe_Close(void *pvThis) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + + int rc; + if (!pThis->fLeaveOpen) + rc = RTPipeClose(pThis->hPipe); + else + rc = VINF_SUCCESS; + pThis->hPipe = NIL_RTPIPE; + + return rc; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtVfsStdPipe_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + return RTPipeQueryInfo(pThis->hPipe, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} + */ +static DECLCALLBACK(int) rtVfsStdPipe_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + int rc; + AssertReturn(off < 0 || pThis->offFakePos == (uint64_t)off, VERR_SEEK_ON_DEVICE); + + NOREF(fBlocking); + if (pSgBuf->cSegs == 1) + { + if (fBlocking) + rc = RTPipeReadBlocking(pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead); + else + rc = RTPipeRead( pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead); + if (RT_SUCCESS(rc)) + pThis->offFakePos += pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg; + } + else + { + size_t cbSeg = 0; + size_t cbRead = 0; + size_t cbReadSeg = 0; + size_t *pcbReadSeg = pcbRead ? &cbReadSeg : NULL; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg; + cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + + cbReadSeg = cbSeg; + if (fBlocking) + rc = RTPipeReadBlocking(pThis->hPipe, pvSeg, cbSeg, pcbReadSeg); + else + rc = RTPipeRead( pThis->hPipe, pvSeg, cbSeg, pcbReadSeg); + if (RT_FAILURE(rc)) + break; + pThis->offFakePos += pcbRead ? cbReadSeg : cbSeg; + cbRead += cbReadSeg; + if (rc != VINF_SUCCESS) + break; + AssertBreakStmt(!pcbRead || cbReadSeg == cbSeg, rc = VINF_TRY_AGAIN); + } + + if (pcbRead) + *pcbRead = cbRead; + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} + */ +static DECLCALLBACK(int) rtVfsStdPipe_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + int rc; + AssertReturn(off < 0 || pThis->offFakePos == (uint64_t)off, VERR_SEEK_ON_DEVICE); + + if (pSgBuf->cSegs == 1) + { + if (fBlocking) + rc = RTPipeWriteBlocking(pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten); + else + rc = RTPipeWrite( pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten); + if (RT_SUCCESS(rc)) + pThis->offFakePos += pcbWritten ? *pcbWritten : pSgBuf->paSegs[0].cbSeg; + } + else + { + size_t cbWritten = 0; + size_t cbWrittenSeg; + size_t *pcbWrittenSeg = pcbWritten ? &cbWrittenSeg : NULL; + rc = VINF_SUCCESS; + + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg; + size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + + cbWrittenSeg = 0; + if (fBlocking) + rc = RTPipeWriteBlocking(pThis->hPipe, pvSeg, cbSeg, pcbWrittenSeg); + else + rc = RTPipeWrite( pThis->hPipe, pvSeg, cbSeg, pcbWrittenSeg); + if (RT_FAILURE(rc)) + break; + pThis->offFakePos += pcbWritten ? cbWrittenSeg : cbSeg; + if (pcbWritten) + { + cbWritten += cbWrittenSeg; + if (rc != VINF_SUCCESS) + break; + AssertStmt(cbWrittenSeg == cbSeg, rc = VINF_TRY_AGAIN); + } + else + AssertBreak(rc == VINF_SUCCESS); + } + + if (pcbWritten) + *pcbWritten = cbWritten; + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} + */ +static DECLCALLBACK(int) rtVfsStdPipe_Flush(void *pvThis) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + return RTPipeFlush(pThis->hPipe); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} + */ +static DECLCALLBACK(int) rtVfsStdPipe_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, + uint32_t *pfRetEvents) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + uint32_t const fPossibleEvt = pThis->fReadPipe ? RTPOLL_EVT_READ : RTPOLL_EVT_WRITE; + + int rc = RTPipeSelectOne(pThis->hPipe, cMillies); + if (RT_SUCCESS(rc)) + { + if (fEvents & fPossibleEvt) + *pfRetEvents = fPossibleEvt; + else + rc = RTVfsUtilDummyPollOne(fEvents, cMillies, fIntr, pfRetEvents); + } + else if ( rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED + && rc != VERR_TRY_AGAIN /* paranoia */) + { + *pfRetEvents = RTPOLL_EVT_ERROR; + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} + */ +static DECLCALLBACK(int) rtVfsStdPipe_Tell(void *pvThis, PRTFOFF poffActual) +{ + PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis; + *poffActual = pThis->offFakePos; + return VINF_SUCCESS; +} + + +/** + * Standard pipe operations. + */ +DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtVfsStdPipeOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_IO_STREAM, + "StdFile", + rtVfsStdPipe_Close, + rtVfsStdPipe_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSIOSTREAMOPS_VERSION, + 0, + rtVfsStdPipe_Read, + rtVfsStdPipe_Write, + rtVfsStdPipe_Flush, + rtVfsStdPipe_PollOne, + rtVfsStdPipe_Tell, + NULL /*rtVfsStdPipe_Skip*/, + NULL /*ZeroFill*/, + RTVFSIOSTREAMOPS_VERSION, +}; + + +/** + * Internal worker for RTVfsIosFromRTPipe and later some create API. + * + * @returns IRPT status code. + * @param hPipe The IPRT file handle. + * @param fOpen The RTFILE_O_XXX flags. + * @param fLeaveOpen Whether to leave it open or close it. + * @param phVfsFile Where to return the handle. + */ +static int rtVfsFileFromRTPipe(RTPIPE hPipe, uint64_t fOpen, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos) +{ + PRTVFSSTDPIPE pThis; + RTVFSIOSTREAM hVfsIos; + int rc = RTVfsNewIoStream(&g_rtVfsStdPipeOps, sizeof(RTVFSSTDPIPE), fOpen, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsIos, (void **)&pThis); + if (RT_FAILURE(rc)) + return rc; + + pThis->hPipe = hPipe; + pThis->fLeaveOpen = fLeaveOpen; + *phVfsIos = hVfsIos; + return VINF_SUCCESS; +} + + +RTDECL(int) RTVfsIoStrmFromRTPipe(RTPIPE hPipe, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos) +{ + /* + * Check the handle validity and read/write mode, then create a stream for it. + */ + RTFSOBJINFO ObjInfo; + int rc = RTPipeQueryInfo(hPipe, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + rc = rtVfsFileFromRTPipe(hPipe, + ObjInfo.Attr.fMode & RTFS_DOS_READONLY ? RTFILE_O_READ : RTFILE_O_WRITE, + fLeaveOpen, phVfsIos); + return rc; +} + +/** @todo Create pipe API? */ + |