From 16f504a9dca3fe3b70568f67b7d41241ae485288 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sun, 7 Apr 2024 18:49:04 +0200
Subject: Adding upstream version 7.0.6-dfsg.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 src/VBox/HostServices/SharedFolders/mappings.cpp | 1024 ++++++++++++++++++++++
 1 file changed, 1024 insertions(+)
 create mode 100644 src/VBox/HostServices/SharedFolders/mappings.cpp

(limited to 'src/VBox/HostServices/SharedFolders/mappings.cpp')

diff --git a/src/VBox/HostServices/SharedFolders/mappings.cpp b/src/VBox/HostServices/SharedFolders/mappings.cpp
new file mode 100644
index 00000000..f55f5835
--- /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-2022 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);
+    }
+}
+
-- 
cgit v1.2.3