diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/HostServices/SharedFolders/mappings.cpp | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedFolders/mappings.cpp b/src/VBox/HostServices/SharedFolders/mappings.cpp new file mode 100644 index 00000000..6a5b80f0 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/mappings.cpp @@ -0,0 +1,1024 @@ +/* $Id: mappings.cpp $ */ +/** @file + * Shared Folders Service - Mappings support. + */ + +/* + * Copyright (C) 2006-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 <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "mappings.h" +#include "vbsfpath.h" +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/list.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <VBox/AssertGuest.h> + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern PVBOXHGCMSVCHELPERS g_pHelpers; /* service.cpp */ + + +/* Shared folders order in the saved state and in the g_FolderMapping can differ. + * So a translation array of root handle is needed. + */ + +static MAPPING g_FolderMapping[SHFL_MAX_MAPPINGS]; +static SHFLROOT g_aIndexFromRoot[SHFL_MAX_MAPPINGS]; +/**< Array running parallel to g_aIndexFromRoot and which entries are increased + * as an root handle is added or removed. + * + * This helps the guest figuring out that a mapping may have been reconfigured + * or that saved state has been restored. Entry reuse is very likely given that + * vbsfRootHandleAdd() always starts searching at the start for an unused entry. + */ +static uint32_t g_auRootHandleVersions[SHFL_MAX_MAPPINGS]; +/** Version number that is increased for every change made. + * This is used by the automount guest service to wait for changes. + * @note This does not need saving, the guest should be woken up and refresh + * its sate when restored. */ +static uint32_t volatile g_uFolderMappingsVersion = 0; + + +/** For recording async vbsfMappingsWaitForChanges calls. */ +typedef struct SHFLMAPPINGSWAIT +{ + RTLISTNODE ListEntry; /**< List entry. */ + PSHFLCLIENTDATA pClient; /**< The client that's waiting. */ + VBOXHGCMCALLHANDLE hCall; /**< The call handle to signal completion with. */ + PVBOXHGCMSVCPARM pParm; /**< The 32-bit unsigned parameter to stuff g_uFolderMappingsVersion into. */ +} SHFLMAPPINGSWAIT; +/** Pointer to async mappings change wait. */ +typedef SHFLMAPPINGSWAIT *PSHFLMAPPINGSWAIT; +/** List head for clients waiting on mapping changes (SHFLMAPPINGSWAIT). */ +static RTLISTANCHOR g_MappingsChangeWaiters; +/** Number of clients waiting on mapping changes. + * We use this to limit the number of waiting calls the clients can make. */ +static uint32_t g_cMappingChangeWaiters = 0; +static void vbsfMappingsWakeupAllWaiters(void); + + +void vbsfMappingInit(void) +{ + unsigned root; + + for (root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + g_aIndexFromRoot[root] = SHFL_ROOT_NIL; + } + + RTListInit(&g_MappingsChangeWaiters); +} + +/** + * Called before loading mappings from saved state to drop the root IDs. + */ +void vbsfMappingLoadingStart(void) +{ + for (SHFLROOT idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + g_aIndexFromRoot[idRoot] = SHFL_ROOT_NIL; + + for (SHFLROOT i = 0; i < RT_ELEMENTS(g_FolderMapping); i++) + g_FolderMapping[i].fLoadedRootId = false; +} + +/** + * Called when a mapping is loaded to restore the root ID and make sure it + * exists. + * + * @returns VBox status code. + */ +int vbsfMappingLoaded(const MAPPING *pLoadedMapping, SHFLROOT root) +{ + /* Mapping loaded from the saved state with the 'root' index. Which means + * the guest uses the 'root' as root handle for this folder. + * Check whether there is the same mapping in g_FolderMapping and + * update the g_aIndexFromRoot. + * + * Also update the mapping properties, which were lost: cMappings. + */ + if (root >= SHFL_MAX_MAPPINGS) + { + return VERR_INVALID_PARAMETER; + } + + SHFLROOT i; + for (i = 0; i < RT_ELEMENTS(g_FolderMapping); i++) + { + MAPPING *pMapping = &g_FolderMapping[i]; + + /* Equal? */ + if ( pLoadedMapping->fValid == pMapping->fValid + && ShflStringSizeOfBuffer(pLoadedMapping->pMapName) == ShflStringSizeOfBuffer(pMapping->pMapName) + && memcmp(pLoadedMapping->pMapName, pMapping->pMapName, ShflStringSizeOfBuffer(pMapping->pMapName)) == 0) + { + Log(("vbsfMappingLoaded: root=%u i=%u (was %u) (%ls)\n", + root, i, g_aIndexFromRoot[root], pLoadedMapping->pMapName->String.utf16)); + + if (!pMapping->fLoadedRootId) + { + /* First encounter. */ + pMapping->fLoadedRootId = true; + + /* Update the mapping properties. */ + pMapping->cMappings = pLoadedMapping->cMappings; + } + else + { + /* When pMapping->fLoadedRootId is already true it means that another HGCM client uses the same mapping. */ + Assert(pMapping->cMappings > 1); + } + + /* Actual index is i. Remember that when the guest uses 'root' it is actually 'i'. */ + AssertLogRelMsg(g_aIndexFromRoot[root] == SHFL_ROOT_NIL, + ("idRoot=%u: current %u ([%s]), new %u (%ls [%s])\n", + root, g_aIndexFromRoot[root], g_FolderMapping[g_aIndexFromRoot[root]].pszFolderName, + i, pLoadedMapping->pMapName->String.utf16, pLoadedMapping->pszFolderName)); + g_aIndexFromRoot[root] = i; + + /* The mapping is known to the host and is used by the guest. + * No need for a 'placeholder'. + */ + return VINF_SUCCESS; + } + } + + /* No corresponding mapping on the host but the guest still uses it. + * Add a 'placeholder' mapping. + */ + LogRel2(("SharedFolders: mapping a placeholder for '%ls' -> '%s'\n", + pLoadedMapping->pMapName->String.ucs2, pLoadedMapping->pszFolderName)); + return vbsfMappingsAdd(pLoadedMapping->pszFolderName, pLoadedMapping->pMapName, + pLoadedMapping->fWritable, pLoadedMapping->fAutoMount, pLoadedMapping->pAutoMountPoint, + pLoadedMapping->fSymlinksCreate, /* fMissing = */ true, /* fPlaceholder = */ true); +} + +/** + * Called after loading mappings from saved state to make sure every mapping has + * a root ID. + */ +void vbsfMappingLoadingDone(void) +{ + for (SHFLROOT iMapping = 0; iMapping < RT_ELEMENTS(g_FolderMapping); iMapping++) + if (g_FolderMapping[iMapping].fValid) + { + AssertLogRel(g_FolderMapping[iMapping].pMapName); + AssertLogRel(g_FolderMapping[iMapping].pszFolderName); + + SHFLROOT idRoot; + for (idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + if (g_aIndexFromRoot[idRoot] == iMapping) + break; + if (idRoot >= RT_ELEMENTS(g_aIndexFromRoot)) + { + for (idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + if (g_aIndexFromRoot[idRoot] == SHFL_ROOT_NIL) + break; + if (idRoot < RT_ELEMENTS(g_aIndexFromRoot)) + g_aIndexFromRoot[idRoot] = iMapping; + else + LogRel(("SharedFolders: Warning! No free root ID entry for mapping #%u: %ls [%s]\n", iMapping, + g_FolderMapping[iMapping].pMapName->String.ucs2, g_FolderMapping[iMapping].pszFolderName)); + } + } + + /* Log the root ID mappings: */ + if (LogRelIs2Enabled()) + for (SHFLROOT idRoot = 0; idRoot < RT_ELEMENTS(g_aIndexFromRoot); idRoot++) + { + SHFLROOT const iMapping = g_aIndexFromRoot[idRoot]; + if (iMapping != SHFL_ROOT_NIL) + LogRel2(("SharedFolders: idRoot %u: iMapping #%u: %ls [%s]\n", idRoot, iMapping, + g_FolderMapping[iMapping].pMapName->String.ucs2, g_FolderMapping[iMapping].pszFolderName)); + } +} + + +MAPPING *vbsfMappingGetByRoot(SHFLROOT root) +{ + if (root < RT_ELEMENTS(g_aIndexFromRoot)) + { + SHFLROOT iMapping = g_aIndexFromRoot[root]; + + if ( iMapping != SHFL_ROOT_NIL + && iMapping < RT_ELEMENTS(g_FolderMapping)) + { + return &g_FolderMapping[iMapping]; + } + } + + return NULL; +} + +static SHFLROOT vbsfMappingGetRootFromIndex(SHFLROOT iMapping) +{ + unsigned root; + + for (root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (iMapping == g_aIndexFromRoot[root]) + { + return root; + } + } + + return SHFL_ROOT_NIL; +} + +static MAPPING *vbsfMappingGetByName(PRTUTF16 pwszName, SHFLROOT *pRoot) +{ + for (unsigned i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if ( g_FolderMapping[i].fValid + && !g_FolderMapping[i].fPlaceholder) /* Don't allow mapping placeholders. */ + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pwszName)) + { + SHFLROOT root = vbsfMappingGetRootFromIndex(i); + + if (root != SHFL_ROOT_NIL) + { + if (pRoot) + { + *pRoot = root; + } + return &g_FolderMapping[i]; + } + AssertFailed(); + } + } + } + return NULL; +} + +static void vbsfRootHandleAdd(SHFLROOT iMapping) +{ + for (unsigned root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (g_aIndexFromRoot[root] == SHFL_ROOT_NIL) + { + g_aIndexFromRoot[root] = iMapping; + g_auRootHandleVersions[root] += 1; + return; + } + } + + AssertFailed(); +} + +static void vbsfRootHandleRemove(SHFLROOT iMapping) +{ + unsigned cFound = 0; + + for (unsigned root = 0; root < RT_ELEMENTS(g_aIndexFromRoot); root++) + { + if (g_aIndexFromRoot[root] == iMapping) + { + g_aIndexFromRoot[root] = SHFL_ROOT_NIL; + g_auRootHandleVersions[root] += 1; + Log(("vbsfRootHandleRemove: Removed root=%u (iMapping=%u)\n", root, iMapping)); + + /* Note! Do not stop here as g_aIndexFromRoot may (at least it could + prior to the introduction of fLoadedRootId) contain + duplicates after restoring save state. */ + cFound++; + } + } + + Assert(cFound > 0); RT_NOREF(cFound); +} + + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_ADD_MAPPING API. Located here as a form of API + * documentation. */ +void testMappingsAdd(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testMappingsAddBadParameters(hTest); + /* Add tests as required... */ +} +#endif +/* + * We are always executed from one specific HGCM thread. So thread safe. + */ +int vbsfMappingsAdd(const char *pszFolderName, PSHFLSTRING pMapName, bool fWritable, + bool fAutoMount, PSHFLSTRING pAutoMountPoint, bool fSymlinksCreate, bool fMissing, bool fPlaceholder) +{ + unsigned i; + + Assert(pszFolderName && pMapName); + + Log(("vbsfMappingsAdd %ls\n", pMapName->String.ucs2)); + + /* Check for duplicates, ignoring placeholders to give the GUI to change stuff at runtime. */ + /** @todo bird: Not entirely sure about ignoring placeholders, but you cannot + * trigger auto-umounting without ignoring them. */ + if (!fPlaceholder) + { + for (i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if ( g_FolderMapping[i].fValid + && !g_FolderMapping[i].fPlaceholder) + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pMapName->String.ucs2)) + { + AssertMsgFailed(("vbsfMappingsAdd: %ls mapping already exists!!\n", pMapName->String.ucs2)); + return VERR_ALREADY_EXISTS; + } + } + } + } + + for (i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if (g_FolderMapping[i].fValid == false) + { + /* Make sure the folder name is an absolute path, otherwise we're + likely to get into trouble with buffer sizes in vbsfPathGuestToHost. */ + char szAbsFolderName[RTPATH_MAX]; + int rc = vbsfPathAbs(NULL, pszFolderName, szAbsFolderName, sizeof(szAbsFolderName)); + AssertRCReturn(rc, rc); + + g_FolderMapping[i].pszFolderName = RTStrDup(szAbsFolderName); + g_FolderMapping[i].pMapName = ShflStringDup(pMapName); + g_FolderMapping[i].pAutoMountPoint = ShflStringDup(pAutoMountPoint); + if ( !g_FolderMapping[i].pszFolderName + || !g_FolderMapping[i].pMapName + || !g_FolderMapping[i].pAutoMountPoint) + { + RTStrFree(g_FolderMapping[i].pszFolderName); + RTMemFree(g_FolderMapping[i].pMapName); + RTMemFree(g_FolderMapping[i].pAutoMountPoint); + return VERR_NO_MEMORY; + } + + g_FolderMapping[i].fValid = true; + g_FolderMapping[i].cMappings = 0; + g_FolderMapping[i].fWritable = fWritable; + g_FolderMapping[i].fAutoMount = fAutoMount; + g_FolderMapping[i].fSymlinksCreate = fSymlinksCreate; + g_FolderMapping[i].fMissing = fMissing; + g_FolderMapping[i].fPlaceholder = fPlaceholder; + g_FolderMapping[i].fLoadedRootId = false; + + /* Check if the host file system is case sensitive */ + RTFSPROPERTIES prop; + prop.fCaseSensitive = false; /* Shut up MSC. */ + rc = RTFsQueryProperties(g_FolderMapping[i].pszFolderName, &prop); +#ifndef DEBUG_bird /* very annoying */ + AssertRC(rc); +#endif + g_FolderMapping[i].fHostCaseSensitive = RT_SUCCESS(rc) ? prop.fCaseSensitive : false; + vbsfRootHandleAdd(i); + vbsfMappingsWakeupAllWaiters(); + break; + } + } + if (i == SHFL_MAX_MAPPINGS) + { + AssertLogRelMsgFailed(("vbsfMappingsAdd: no more room to add mapping %s to %ls!!\n", pszFolderName, pMapName->String.ucs2)); + return VERR_TOO_MUCH_DATA; + } + + Log(("vbsfMappingsAdd: added mapping %s to %ls (slot %u, root %u)\n", + pszFolderName, pMapName->String.ucs2, i, vbsfMappingGetRootFromIndex(i))); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_REMOVE_MAPPING API. Located here as a form of API + * documentation. */ +void testMappingsRemove(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testMappingsRemoveBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfMappingsRemove(PSHFLSTRING pMapName) +{ + Assert(pMapName); + Log(("vbsfMappingsRemove %ls\n", pMapName->String.ucs2)); + + /* + * We must iterate thru the whole table as may have 0+ placeholder entries + * and 0-1 regular entries with the same name. Also, it is good to kick + * the guest automounter into action wrt to evicting placeholders. + */ + int rc = VERR_FILE_NOT_FOUND; + for (unsigned i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + if (g_FolderMapping[i].fValid == true) + { + if (!RTUtf16LocaleICmp(g_FolderMapping[i].pMapName->String.ucs2, pMapName->String.ucs2)) + { + if (g_FolderMapping[i].cMappings != 0) + { + LogRel2(("SharedFolders: removing '%ls' -> '%s'%s, which is still used by the guest\n", pMapName->String.ucs2, + g_FolderMapping[i].pszFolderName, g_FolderMapping[i].fPlaceholder ? " (again)" : "")); + g_FolderMapping[i].fMissing = true; + g_FolderMapping[i].fPlaceholder = true; + vbsfMappingsWakeupAllWaiters(); + rc = VINF_PERMISSION_DENIED; + } + else + { + /* pMapName can be the same as g_FolderMapping[i].pMapName when + * called from vbsfUnmapFolder, log it before deallocating the memory. */ + Log(("vbsfMappingsRemove: mapping %ls removed\n", pMapName->String.ucs2)); + bool fSame = g_FolderMapping[i].pMapName == pMapName; + + RTStrFree(g_FolderMapping[i].pszFolderName); + RTMemFree(g_FolderMapping[i].pMapName); + RTMemFree(g_FolderMapping[i].pAutoMountPoint); + g_FolderMapping[i].pszFolderName = NULL; + g_FolderMapping[i].pMapName = NULL; + g_FolderMapping[i].pAutoMountPoint = NULL; + g_FolderMapping[i].fValid = false; + vbsfRootHandleRemove(i); + vbsfMappingsWakeupAllWaiters(); + if (rc == VERR_FILE_NOT_FOUND) + rc = VINF_SUCCESS; + if (fSame) + break; + } + } + } + } + + return rc; +} + +const char* vbsfMappingsQueryHostRoot(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, NULL); + if (pFolderMapping->fMissing) + return NULL; + return pFolderMapping->pszFolderName; +} + +int vbsfMappingsQueryHostRootEx(SHFLROOT hRoot, const char **ppszRoot, uint32_t *pcbRootLen) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(hRoot); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + if (pFolderMapping->fMissing) + return VERR_NOT_FOUND; + if ( pFolderMapping->pszFolderName == NULL + || pFolderMapping->pszFolderName[0] == 0) + return VERR_NOT_FOUND; + *ppszRoot = pFolderMapping->pszFolderName; + *pcbRootLen = (uint32_t)strlen(pFolderMapping->pszFolderName); + return VINF_SUCCESS; +} + +bool vbsfIsGuestMappingCaseSensitive(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, false); + return pFolderMapping->fGuestCaseSensitive; +} + +bool vbsfIsHostMappingCaseSensitive(SHFLROOT root) +{ + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, false); + return pFolderMapping->fHostCaseSensitive; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_QUERY_MAPPINGS API. Located here as a form of API + * documentation (or should it better be inline in include/VBox/shflsvc.h?) */ +void testMappingsQuery(RTTEST hTest) +{ + /* The API should return all mappings if we provide enough buffers. */ + testMappingsQuerySimple(hTest); + /* If we provide too few buffers that should be signalled correctly. */ + testMappingsQueryTooFewBuffers(hTest); + /* The SHFL_MF_AUTOMOUNT flag means return only auto-mounted mappings. */ + testMappingsQueryAutoMount(hTest); + /* The mappings return array must have numberOfMappings entries. */ + testMappingsQueryArrayWrongSize(hTest); +} +#endif +/** + * @note If pMappings / *pcMappings is smaller than the actual amount of + * mappings that *could* have been returned *pcMappings contains the + * required buffer size so that the caller can retry the operation if + * wanted. + */ +int vbsfMappingsQuery(PSHFLCLIENTDATA pClient, bool fOnlyAutoMounts, PSHFLMAPPING pMappings, uint32_t *pcMappings) +{ + LogFlow(("vbsfMappingsQuery: pClient = %p, pMappings = %p, pcMappings = %p, *pcMappings = %d\n", + pClient, pMappings, pcMappings, *pcMappings)); + + uint32_t const cMaxMappings = *pcMappings; + uint32_t idx = 0; + for (uint32_t i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + MAPPING *pFolderMapping = vbsfMappingGetByRoot(i); + if ( pFolderMapping != NULL + && pFolderMapping->fValid + && ( !fOnlyAutoMounts + || (pFolderMapping->fAutoMount && !pFolderMapping->fPlaceholder)) ) + { + if (idx < cMaxMappings) + { + pMappings[idx].u32Status = SHFL_MS_NEW; + pMappings[idx].root = i; + } + idx++; + } + } + + /* Return actual number of mappings, regardless whether the handed in + * mapping buffer was big enough. */ + /** @todo r=bird: This is non-standard interface behaviour. We return + * VERR_BUFFER_OVERFLOW or at least a VINF_BUFFER_OVERFLOW here. + * + * Guess this goes well along with ORing SHFL_MF_AUTOMOUNT into + * pClient->fu32Flags rather than passing it as fOnlyAutoMounts... + * Not amused by this. */ + *pcMappings = idx; + + RT_NOREF_PV(pClient); + LogFlow(("vbsfMappingsQuery: returns VINF_SUCCESS (idx=%u, cMaxMappings=%u)\n", idx, cMaxMappings)); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_QUERY_MAP_NAME API. Located here as a form of API + * documentation. */ +void testMappingsQueryName(RTTEST hTest) +{ + /* If we query an valid mapping it should be returned. */ + testMappingsQueryNameValid(hTest); + /* If we query an invalid mapping that should be signalled. */ + testMappingsQueryNameInvalid(hTest); + /* If we pass in a bad string buffer that should be detected. */ + testMappingsQueryNameBadBuffer(hTest); +} +#endif +int vbsfMappingsQueryName(PSHFLCLIENTDATA pClient, SHFLROOT root, SHFLSTRING *pString) +{ + LogFlow(("vbsfMappingsQuery: pClient = %p, root = %d, *pString = %p\n", pClient, root, pString)); + + int rc; + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping) + { + if (pFolderMapping->fValid) + { + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + rc = ShflStringCopyUtf16BufAsUtf8(pString, pFolderMapping->pMapName); + else + { + /* Not using ShlfStringCopy here as behaviour shouldn't change... */ + if (pString->u16Size < pFolderMapping->pMapName->u16Size) + { + Log(("vbsfMappingsQuery: passed string too short (%d < %d bytes)!\n", + pString->u16Size, pFolderMapping->pMapName->u16Size)); + rc = VERR_INVALID_PARAMETER; + } + else + { + pString->u16Length = pFolderMapping->pMapName->u16Length; + memcpy(pString->String.ucs2, pFolderMapping->pMapName->String.ucs2, + pFolderMapping->pMapName->u16Size); + rc = VINF_SUCCESS; + } + } + } + else + rc = VERR_FILE_NOT_FOUND; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlow(("vbsfMappingsQuery:Name return rc = %Rrc\n", rc)); + return rc; +} + +/** Queries fWritable flag for the given root. Returns error if the root is not accessible. + */ +int vbsfMappingsQueryWritable(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fWritable) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryWritable: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if ( pFolderMapping->fValid + && !pFolderMapping->fMissing) + *fWritable = pFolderMapping->fWritable; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQuery:Writable return rc = %Rrc\n", rc)); + + return rc; +} + +int vbsfMappingsQueryAutoMount(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fAutoMount) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryAutoMount: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if (pFolderMapping->fValid == true) + *fAutoMount = pFolderMapping->fAutoMount; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQueryAutoMount:Writable return rc = %Rrc\n", rc)); + + return rc; +} + +int vbsfMappingsQuerySymlinksCreate(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fSymlinksCreate) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + LogFlow(("vbsfMappingsQueryAutoMount: pClient = %p, root = %d\n", pClient, root)); + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + AssertReturn(pFolderMapping, VERR_INVALID_PARAMETER); + + if (pFolderMapping->fValid == true) + *fSymlinksCreate = pFolderMapping->fSymlinksCreate; + else + rc = VERR_FILE_NOT_FOUND; + + LogFlow(("vbsfMappingsQueryAutoMount:SymlinksCreate return rc = %Rrc\n", rc)); + + return rc; +} + +/** + * Implements SHFL_FN_QUERY_MAP_INFO. + * @since VBox 6.0 + */ +int vbsfMappingsQueryInfo(PSHFLCLIENTDATA pClient, SHFLROOT root, PSHFLSTRING pNameBuf, PSHFLSTRING pMntPtBuf, + uint64_t *pfFlags, uint32_t *puVersion) +{ + LogFlow(("vbsfMappingsQueryInfo: pClient=%p root=%d\n", pClient, root)); + + /* Resolve the root handle. */ + int rc; + PMAPPING pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping) + { + if (pFolderMapping->fValid) + { + /* + * Produce the output. + */ + *puVersion = g_auRootHandleVersions[root]; + + *pfFlags = 0; + if (pFolderMapping->fWritable) + *pfFlags |= SHFL_MIF_WRITABLE; + if (pFolderMapping->fAutoMount) + *pfFlags |= SHFL_MIF_AUTO_MOUNT; + if (pFolderMapping->fHostCaseSensitive) + *pfFlags |= SHFL_MIF_HOST_ICASE; + if (pFolderMapping->fGuestCaseSensitive) + *pfFlags |= SHFL_MIF_GUEST_ICASE; + if (pFolderMapping->fSymlinksCreate) + *pfFlags |= SHFL_MIF_SYMLINK_CREATION; + + int rc2; + if (pClient->fu32Flags & SHFL_CF_UTF8) + { + rc = ShflStringCopyUtf16BufAsUtf8(pNameBuf, pFolderMapping->pMapName); + rc2 = ShflStringCopyUtf16BufAsUtf8(pMntPtBuf, pFolderMapping->pAutoMountPoint); + } + else + { + rc = ShflStringCopy(pNameBuf, pFolderMapping->pMapName, sizeof(RTUTF16)); + rc2 = ShflStringCopy(pMntPtBuf, pFolderMapping->pAutoMountPoint, sizeof(RTUTF16)); + } + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + rc = VERR_FILE_NOT_FOUND; + } + else + rc = VERR_INVALID_PARAMETER; + LogFlow(("vbsfMappingsQueryInfo: returns %Rrc\n", rc)); + return rc; +} + + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_MAP_FOLDER API. Located here as a form of API + * documentation. */ +void testMapFolder(RTTEST hTest) +{ + /* If we try to map a valid name we should get the root. */ + testMapFolderValid(hTest); + /* If we try to map a valid name we should get VERR_FILE_NOT_FOUND. */ + testMapFolderInvalid(hTest); + /* If we map a folder twice we can unmap it twice. + * Currently unmapping too often is only asserted but not signalled. */ + testMapFolderTwice(hTest); + /* The delimiter should be converted in e.g. file delete operations. */ + testMapFolderDelimiter(hTest); + /* Test case sensitive mapping by opening a file with the wrong case. */ + testMapFolderCaseSensitive(hTest); + /* Test case insensitive mapping by opening a file with the wrong case. */ + testMapFolderCaseInsensitive(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testMapFolderBadParameters(hTest); +} +#endif +int vbsfMapFolder(PSHFLCLIENTDATA pClient, PSHFLSTRING pszMapName, + RTUTF16 wcDelimiter, bool fCaseSensitive, SHFLROOT *pRoot) +{ + MAPPING *pFolderMapping = NULL; + + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + Log(("vbsfMapFolder %s\n", pszMapName->String.utf8)); + } + else + { + Log(("vbsfMapFolder %ls\n", pszMapName->String.ucs2)); + } + + AssertMsgReturn(wcDelimiter == '/' || wcDelimiter == '\\', + ("Invalid path delimiter: %#x\n", wcDelimiter), + VERR_INVALID_PARAMETER); + if (pClient->PathDelimiter == 0) + { + pClient->PathDelimiter = wcDelimiter; + } + else + { + AssertMsgReturn(wcDelimiter == pClient->PathDelimiter, + ("wcDelimiter=%#x PathDelimiter=%#x", wcDelimiter, pClient->PathDelimiter), + VERR_INVALID_PARAMETER); + } + + SHFLROOT RootTmp; + if (!pRoot) + pRoot = &RootTmp; + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + int rc; + PRTUTF16 utf16Name; + + rc = RTStrToUtf16((const char *) pszMapName->String.utf8, &utf16Name); + if (RT_FAILURE (rc)) + return rc; + + pFolderMapping = vbsfMappingGetByName(utf16Name, pRoot); + RTUtf16Free(utf16Name); + } + else + { + pFolderMapping = vbsfMappingGetByName(pszMapName->String.ucs2, pRoot); + } + + if (!pFolderMapping) + { + return VERR_FILE_NOT_FOUND; + } + + /* + * Check for reference count overflows and settings compatibility. + * For paranoid reasons, we don't allow modifying the case sensitivity + * setting while there are other mappings of a folder. + */ + AssertLogRelReturn(*pRoot < RT_ELEMENTS(pClient->acMappings), VERR_INTERNAL_ERROR); + AssertLogRelReturn(!pClient->fHasMappingCounts || pClient->acMappings[*pRoot] < _32K, VERR_TOO_MANY_OPENS); + ASSERT_GUEST_LOGREL_MSG_RETURN( pFolderMapping->cMappings == 0 + || pFolderMapping->fGuestCaseSensitive == fCaseSensitive, + ("Incompatible case sensitivity setting: %s: %u mappings, %ssenitive, requested %ssenitive!\n", + pFolderMapping->pszFolderName, pFolderMapping->cMappings, + pFolderMapping->fGuestCaseSensitive ? "" : "in", fCaseSensitive ? "" : "in"), + VERR_INCOMPATIBLE_CONFIG); + + /* + * Go ahead and map it. + */ + if (pClient->fHasMappingCounts) + pClient->acMappings[*pRoot] += 1; + pFolderMapping->cMappings++; + pFolderMapping->fGuestCaseSensitive = fCaseSensitive; + Log(("vbsfMmapFolder (cMappings=%u, acMappings[%u]=%u)\n", pFolderMapping->cMappings, *pRoot, pClient->acMappings[*pRoot])); + return VINF_SUCCESS; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_UNMAP_FOLDER API. Located here as a form of API + * documentation. */ +void testUnmapFolder(RTTEST hTest) +{ + /* Unmapping a mapped folder should succeed. + * If the folder is not mapped this is only asserted, not signalled. */ + testUnmapFolderValid(hTest); + /* Unmapping a non-existant root should fail. */ + testUnmapFolderInvalid(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testUnmapFolderBadParameters(hTest); +} +#endif +int vbsfUnmapFolder(PSHFLCLIENTDATA pClient, SHFLROOT root) +{ + RT_NOREF1(pClient); + int rc = VINF_SUCCESS; + + MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); + if (pFolderMapping == NULL) + { + AssertFailed(); + return VERR_FILE_NOT_FOUND; + } + Assert(pFolderMapping->fValid == true && pFolderMapping->cMappings > 0); + + AssertLogRelReturn(root < RT_ELEMENTS(pClient->acMappings), VERR_INTERNAL_ERROR); + AssertLogRelReturn(!pClient->fHasMappingCounts || pClient->acMappings[root] > 0, VERR_INVALID_HANDLE); + + if (pClient->fHasMappingCounts) + pClient->acMappings[root] -= 1; + + if (pFolderMapping->cMappings > 0) + pFolderMapping->cMappings--; + + uint32_t const cMappings = pFolderMapping->cMappings; + if ( cMappings == 0 + && pFolderMapping->fPlaceholder) + { + /* Automatically remove, it is not used by the guest anymore. */ + Assert(pFolderMapping->fMissing); + LogRel2(("SharedFolders: unmapping placeholder '%ls' -> '%s'\n", + pFolderMapping->pMapName->String.ucs2, pFolderMapping->pszFolderName)); + vbsfMappingsRemove(pFolderMapping->pMapName); + } + + Log(("vbsfUnmapFolder (cMappings=%u, acMappings[%u]=%u)\n", cMappings, root, pClient->acMappings[root])); + return rc; +} + +/** + * SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES implementation. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on change. + * @retval VINF_TRY_AGAIN on resume. + * @retval VINF_HGCM_ASYNC_EXECUTE if waiting. + * @retval VERR_CANCELLED if cancelled. + * @retval VERR_OUT_OF_RESOURCES if there are too many pending waits. + * + * @param pClient The calling client. + * @param hCall The call handle. + * @param pParm The parameter (32-bit). + * @param fRestored Set if this is a call restored & resubmitted from saved + * state. + * @since VBox 6.0 + */ +int vbsfMappingsWaitForChanges(PSHFLCLIENTDATA pClient, VBOXHGCMCALLHANDLE hCall, PVBOXHGCMSVCPARM pParm, bool fRestored) +{ + /* + * Return immediately if the fodler mappings have changed since last call + * or if we got restored from saved state (adding of global folders, etc). + */ + uint32_t uCurVersion = g_uFolderMappingsVersion; + if ( pParm->u.uint32 != uCurVersion + || fRestored + || (pClient->fu32Flags & SHFL_CF_CANCEL_NEXT_WAIT) ) + { + int rc = VINF_SUCCESS; + if (pClient->fu32Flags & SHFL_CF_CANCEL_NEXT_WAIT) + { + pClient->fu32Flags &= ~SHFL_CF_CANCEL_NEXT_WAIT; + rc = VERR_CANCELLED; + } + else if (fRestored) + { + rc = VINF_TRY_AGAIN; + if (pParm->u.uint32 == uCurVersion) + uCurVersion = uCurVersion != UINT32_C(0x55555555) ? UINT32_C(0x55555555) : UINT32_C(0x99999999); + } + Log(("vbsfMappingsWaitForChanges: Version %#x -> %#x, returning %Rrc immediately.\n", pParm->u.uint32, uCurVersion, rc)); + pParm->u.uint32 = uCurVersion; + return rc; + } + + /* + * Setup a wait if we can. + */ + if (g_cMappingChangeWaiters < 64) + { + PSHFLMAPPINGSWAIT pWait = (PSHFLMAPPINGSWAIT)RTMemAlloc(sizeof(*pWait)); + if (pWait) + { + pWait->pClient = pClient; + pWait->hCall = hCall; + pWait->pParm = pParm; + + RTListAppend(&g_MappingsChangeWaiters, &pWait->ListEntry); + g_cMappingChangeWaiters += 1; + return VINF_HGCM_ASYNC_EXECUTE; + } + return VERR_NO_MEMORY; + } + LogRelMax(32, ("vbsfMappingsWaitForChanges: Too many threads waiting for changes!\n")); + return VERR_OUT_OF_RESOURCES; +} + +/** + * SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS implementation. + * + * @returns VINF_SUCCESS + * @param pClient The calling client to cancel all waits for. + * @since VBox 6.0 + */ +int vbsfMappingsCancelChangesWaits(PSHFLCLIENTDATA pClient) +{ + uint32_t const uCurVersion = g_uFolderMappingsVersion; + + PSHFLMAPPINGSWAIT pCur, pNext; + RTListForEachSafe(&g_MappingsChangeWaiters, pCur, pNext, SHFLMAPPINGSWAIT, ListEntry) + { + if (pCur->pClient == pClient) + { + RTListNodeRemove(&pCur->ListEntry); + pCur->pParm->u.uint32 = uCurVersion; + g_pHelpers->pfnCallComplete(pCur->hCall, VERR_CANCELLED); + RTMemFree(pCur); + } + } + + /* Set a flag to make sure the next SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES doesn't block. + This should help deal with races between this call and a thread about to do a wait. */ + pClient->fu32Flags |= SHFL_CF_CANCEL_NEXT_WAIT; + + return VINF_SUCCESS; +} + +/** + * Wakes up all clients waiting on + */ +static void vbsfMappingsWakeupAllWaiters(void) +{ + uint32_t const uCurVersion = ++g_uFolderMappingsVersion; + + PSHFLMAPPINGSWAIT pCur, pNext; + RTListForEachSafe(&g_MappingsChangeWaiters, pCur, pNext, SHFLMAPPINGSWAIT, ListEntry) + { + RTListNodeRemove(&pCur->ListEntry); + pCur->pParm->u.uint32 = uCurVersion; + g_pHelpers->pfnCallComplete(pCur->hCall, VERR_CANCELLED); + RTMemFree(pCur); + } +} + |