From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Runtime/common/vfs/vfschain.cpp | 1861 ++++++++++++++++++++++++++++++ 1 file changed, 1861 insertions(+) create mode 100644 src/VBox/Runtime/common/vfs/vfschain.cpp (limited to 'src/VBox/Runtime/common/vfs/vfschain.cpp') diff --git a/src/VBox/Runtime/common/vfs/vfschain.cpp b/src/VBox/Runtime/common/vfs/vfschain.cpp new file mode 100644 index 00000000..e3811970 --- /dev/null +++ b/src/VBox/Runtime/common/vfs/vfschain.cpp @@ -0,0 +1,1861 @@ +/* $Id: vfschain.cpp $ */ +/** @file + * IPRT - Virtual File System, Chains. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + -- cgit v1.2.3