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/HostServices/SharedFolders/Makefile.kmk | 62 + .../SharedFolders/VBoxSharedFoldersSvc.cpp | 1971 ++++++++++++++ .../SharedFolders/VBoxSharedFoldersSvc.rc | 61 + src/VBox/HostServices/SharedFolders/mappings.cpp | 1024 ++++++++ src/VBox/HostServices/SharedFolders/mappings.h | 92 + src/VBox/HostServices/SharedFolders/shfl.h | 89 + src/VBox/HostServices/SharedFolders/shflhandle.cpp | 236 ++ src/VBox/HostServices/SharedFolders/shflhandle.h | 91 + .../SharedFolders/testcase/Makefile.kmk | 104 + .../testcase/tstSharedFolderService.cpp | 1368 ++++++++++ .../testcase/tstSharedFolderService.h | 140 + .../SharedFolders/testcase/tstShflCase.cpp | 452 ++++ .../SharedFolders/testcase/tstShflSizes.cpp | 144 ++ src/VBox/HostServices/SharedFolders/teststubs.h | 104 + src/VBox/HostServices/SharedFolders/vbsf.cpp | 2690 ++++++++++++++++++++ src/VBox/HostServices/SharedFolders/vbsf.h | 68 + src/VBox/HostServices/SharedFolders/vbsfpath.cpp | 709 ++++++ src/VBox/HostServices/SharedFolders/vbsfpath.h | 80 + .../HostServices/SharedFolders/vbsfpathabs.cpp | 200 ++ 19 files changed, 9685 insertions(+) create mode 100644 src/VBox/HostServices/SharedFolders/Makefile.kmk create mode 100644 src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp create mode 100644 src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc create mode 100644 src/VBox/HostServices/SharedFolders/mappings.cpp create mode 100644 src/VBox/HostServices/SharedFolders/mappings.h create mode 100644 src/VBox/HostServices/SharedFolders/shfl.h create mode 100644 src/VBox/HostServices/SharedFolders/shflhandle.cpp create mode 100644 src/VBox/HostServices/SharedFolders/shflhandle.h create mode 100644 src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk create mode 100644 src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp create mode 100644 src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h create mode 100644 src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp create mode 100644 src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp create mode 100644 src/VBox/HostServices/SharedFolders/teststubs.h create mode 100644 src/VBox/HostServices/SharedFolders/vbsf.cpp create mode 100644 src/VBox/HostServices/SharedFolders/vbsf.h create mode 100644 src/VBox/HostServices/SharedFolders/vbsfpath.cpp create mode 100644 src/VBox/HostServices/SharedFolders/vbsfpath.h create mode 100644 src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp (limited to 'src/VBox/HostServices/SharedFolders') diff --git a/src/VBox/HostServices/SharedFolders/Makefile.kmk b/src/VBox/HostServices/SharedFolders/Makefile.kmk new file mode 100644 index 00000000..fc0ff6c5 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/Makefile.kmk @@ -0,0 +1,62 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Folders Host Service. +# + +# +# 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 . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The shared folder service DLL. +# +DLLS += VBoxSharedFolders +VBoxSharedFolders_TEMPLATE = VBoxR3Dll +VBoxSharedFolders_NAME.os2 = VBoxSFld +VBoxSharedFolders_DEFS = VBOX_WITH_HGCM RTSHFL +VBoxSharedFolders_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxSharedFolders_LDFLAGS.darwin = \ + -framework Carbon \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedFolders.dylib + +VBoxSharedFolders_SOURCES = \ + VBoxSharedFoldersSvc.cpp \ + shflhandle.cpp \ + vbsf.cpp \ + vbsfpath.cpp \ + vbsfpathabs.cpp \ + mappings.cpp +VBoxSharedFolders_SOURCES.win = \ + VBoxSharedFoldersSvc.rc + +VBoxSharedFolders_LIBS = \ + $(LIB_RUNTIME) + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp new file mode 100644 index 00000000..3486709d --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp @@ -0,0 +1,1971 @@ +/* $Id: VBoxSharedFoldersSvc.cpp $ */ +/** @file + * Shared Folders - Host service entry points. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include + +#include "shfl.h" +#include "mappings.h" +#include "shflhandle.h" +#include "vbsf.h" +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16 2 +#define SHFL_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT 3 +#define SHFL_SAVED_STATE_VERSION_PRE_ERROR_STYLE 4 +#define SHFL_SAVED_STATE_VERSION 5 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +PVBOXHGCMSVCHELPERS g_pHelpers; +static PPDMLED g_pStatusLed = NULL; + +/** @name Shared folder statistics. + * @{ */ +static STAMPROFILE g_StatQueryMappings; +static STAMPROFILE g_StatQueryMappingsFail; +static STAMPROFILE g_StatQueryMapName; +static STAMPROFILE g_StatCreate; +static STAMPROFILE g_StatCreateFail; +static STAMPROFILE g_StatLookup; +static STAMPROFILE g_StatLookupFail; +static STAMPROFILE g_StatClose; +static STAMPROFILE g_StatCloseFail; +static STAMPROFILE g_StatRead; +static STAMPROFILE g_StatReadFail; +static STAMPROFILE g_StatWrite; +static STAMPROFILE g_StatWriteFail; +static STAMPROFILE g_StatLock; +static STAMPROFILE g_StatLockFail; +static STAMPROFILE g_StatList; +static STAMPROFILE g_StatListFail; +static STAMPROFILE g_StatReadLink; +static STAMPROFILE g_StatReadLinkFail; +static STAMPROFILE g_StatMapFolderOld; +static STAMPROFILE g_StatMapFolder; +static STAMPROFILE g_StatMapFolderFail; +static STAMPROFILE g_StatUnmapFolder; +static STAMPROFILE g_StatUnmapFolderFail; +static STAMPROFILE g_StatInformationFail; +static STAMPROFILE g_StatInformationSetFile; +static STAMPROFILE g_StatInformationSetFileFail; +static STAMPROFILE g_StatInformationSetSize; +static STAMPROFILE g_StatInformationSetSizeFail; +static STAMPROFILE g_StatInformationGetFile; +static STAMPROFILE g_StatInformationGetFileFail; +static STAMPROFILE g_StatInformationGetVolume; +static STAMPROFILE g_StatInformationGetVolumeFail; +static STAMPROFILE g_StatRemove; +static STAMPROFILE g_StatRemoveFail; +static STAMPROFILE g_StatCloseAndRemove; +static STAMPROFILE g_StatCloseAndRemoveFail; +static STAMPROFILE g_StatRename; +static STAMPROFILE g_StatRenameFail; +static STAMPROFILE g_StatFlush; +static STAMPROFILE g_StatFlushFail; +static STAMPROFILE g_StatSetErrorStyle; +static STAMPROFILE g_StatSetUtf8; +static STAMPROFILE g_StatSetFileSize; +static STAMPROFILE g_StatSetFileSizeFail; +static STAMPROFILE g_StatSymlink; +static STAMPROFILE g_StatSymlinkFail; +static STAMPROFILE g_StatSetSymlinks; +static STAMPROFILE g_StatQueryMapInfo; +static STAMPROFILE g_StatQueryFeatures; +static STAMPROFILE g_StatCopyFile; +static STAMPROFILE g_StatCopyFileFail; +static STAMPROFILE g_StatCopyFilePart; +static STAMPROFILE g_StatCopyFilePartFail; +static STAMPROFILE g_StatWaitForMappingsChanges; +static STAMPROFILE g_StatWaitForMappingsChangesFail; +static STAMPROFILE g_StatCancelMappingsChangesWait; +static STAMPROFILE g_StatUnknown; +static STAMPROFILE g_StatMsgStage1; +/** @} */ + + +/** @page pg_shfl_svc Shared Folders Host Service + * + * Shared Folders map a host file system to guest logical filesystem. + * A mapping represents 'host name'<->'guest name' translation and a root + * identifier to be used to access this mapping. + * Examples: "C:\WINNT"<->"F:", "C:\WINNT\System32"<->"/mnt/host/system32". + * + * Therefore, host name and guest name are strings interpreted + * only by host service and guest client respectively. Host name is + * passed to guest only for informational purpose. Guest may for example + * display the string or construct volume label out of the string. + * + * Root identifiers are unique for whole guest life, + * that is until next guest reset/fresh start. + * 32 bit value incremented for each new mapping is used. + * + * Mapping strings are taken from VM XML configuration on VM startup. + * The service DLL takes mappings during initialization. There is + * also API for changing mappings at runtime. + * + * Current mappings and root identifiers are saved when VM is saved. + * + * Guest may use any of these mappings. Full path information + * about an object on a mapping consists of the root identifier and + * a full path of object. + * + * Guest IFS connects to the service and calls SHFL_FN_QUERY_MAP + * function which returns current mappings. For guest convenience, + * removed mappings also returned with REMOVED flag and new mappings + * are marked with NEW flag. + * + * To access host file system guest just forwards file system calls + * to the service, and specifies full paths or handles for objects. + * + * + */ + + + +static DECLCALLBACK(int) svcUnload (void *) +{ + int rc = VINF_SUCCESS; + + Log(("svcUnload\n")); + vbsfFreeHandleTable(); + + if (g_pHelpers) + HGCMSvcHlpStamDeregister(g_pHelpers, "/HGCM/VBoxSharedFolders/*"); + return rc; +} + +static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + RT_NOREF(u32ClientID, fRequestor, fRestoring); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + Log(("SharedFolders host service: connected, u32ClientID = %u\n", u32ClientID)); + + pClient->fHasMappingCounts = true; + pClient->enmErrorStyle = SHFLERRORSTYLE_NATIVE; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect (void *, uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF1(u32ClientID); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + /* When a client disconnects, make sure that outstanding change waits are being canceled. + * + * Usually this will be done actively by VBoxService on the guest side when shutting down, + * but the VM could be reset without having VBoxService the chance of cancelling those waits. + * + * This in turn will eat up the call completion handle restrictions on the HGCM host side, throwing assertions. */ + int rc = vbsfMappingsCancelChangesWaits(pClient); + + Log(("SharedFolders host service: disconnected, u32ClientID = %u, rc = %Rrc\n", u32ClientID, rc)); + + vbsfDisconnect(pClient); + return rc; +} + +/** @note We only save as much state as required to access the shared folder again after restore. + * All I/O requests pending at the time of saving will never be completed or result in errors. + * (file handles no longer valid etc) + * This works as designed at the moment. A full state save would be difficult and not always possible + * as the contents of a shared folder might change in between save and restore. + */ +static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ +#ifndef UNITTEST /* Read this as not yet tested */ + RT_NOREF1(u32ClientID); + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + Log(("SharedFolders host service: saving state, u32ClientID = %u\n", u32ClientID)); + + int rc = pVMM->pfnSSMR3PutU32(pSSM, SHFL_SAVED_STATE_VERSION); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutU32(pSSM, SHFL_MAX_MAPPINGS); + AssertRCReturn(rc, rc); + + /* Save client structure length & contents */ + rc = pVMM->pfnSSMR3PutU32(pSSM, sizeof(*pClient)); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutMem(pSSM, pClient, sizeof(*pClient)); + AssertRCReturn(rc, rc); + + /* Save all the active mappings. */ + for (int i=0;ipfnSSMR3PutU32(pSSM, pFolderMapping? pFolderMapping->cMappings: 0); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping? pFolderMapping->fValid: false); + AssertRCReturn(rc, rc); + + if (pFolderMapping && pFolderMapping->fValid) + { + uint32_t len = (uint32_t)strlen(pFolderMapping->pszFolderName); + pVMM->pfnSSMR3PutU32(pSSM, len); + pVMM->pfnSSMR3PutStrZ(pSSM, pFolderMapping->pszFolderName); + + len = ShflStringSizeOfBuffer(pFolderMapping->pMapName); + pVMM->pfnSSMR3PutU32(pSSM, len); + pVMM->pfnSSMR3PutMem(pSSM, pFolderMapping->pMapName, len); + + pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping->fHostCaseSensitive); + + pVMM->pfnSSMR3PutBool(pSSM, pFolderMapping->fGuestCaseSensitive); + + len = ShflStringSizeOfBuffer(pFolderMapping->pAutoMountPoint); + pVMM->pfnSSMR3PutU32(pSSM, len); + rc = pVMM->pfnSSMR3PutMem(pSSM, pFolderMapping->pAutoMountPoint, len); + AssertRCReturn(rc, rc); + } + } + +#else + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); +#endif + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ +#ifndef UNITTEST /* Read this as not yet tested */ + RT_NOREF(u32ClientID, uVersion); + uint32_t nrMappings; + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + uint32_t len; + + Log(("SharedFolders host service: loading state, u32ClientID = %u\n", u32ClientID)); + + uint32_t uShfVersion = 0; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &uShfVersion); + AssertRCReturn(rc, rc); + + if ( uShfVersion > SHFL_SAVED_STATE_VERSION + || uShfVersion < SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION, RT_SRC_POS, + "Unknown shared folders state version %u!", uShfVersion); + + rc = pVMM->pfnSSMR3GetU32(pSSM, &nrMappings); + AssertRCReturn(rc, rc); + if (nrMappings != SHFL_MAX_MAPPINGS) + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + + /* Restore the client data (flags + path delimiter + mapping counts (new) at the moment) */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &len); + AssertRCReturn(rc, rc); + + if (len == RT_UOFFSETOF(SHFLCLIENTDATA, acMappings)) + pClient->fHasMappingCounts = false; + else if (len != sizeof(*pClient)) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Saved SHFLCLIENTDATA size %u differs from current %u!", len, sizeof(*pClient)); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pClient, len); + AssertRCReturn(rc, rc); + + /* For older saved state, use the default native error style, otherwise + check that the restored value makes sense to us. */ + if (uShfVersion <= SHFL_SAVED_STATE_VERSION_PRE_ERROR_STYLE) + pClient->enmErrorStyle = SHFLERRORSTYLE_NATIVE; + else if ( pClient->enmErrorStyle <= kShflErrorStyle_Invalid + || pClient->enmErrorStyle >= kShflErrorStyle_End) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Saved SHFLCLIENTDATA enmErrorStyle value %d is not known/valid!", pClient->enmErrorStyle); + + /* Drop the root IDs of all configured mappings before restoring: */ + vbsfMappingLoadingStart(); + + /* We don't actually (fully) restore the state; we simply check if the current state is as we it expect it to be. */ + for (SHFLROOT i = 0; i < SHFL_MAX_MAPPINGS; i++) + { + /* Load the saved mapping description and try to find it in the mappings. */ + MAPPING mapping; + RT_ZERO(mapping); + + /* restore the folder mapping counter. */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &mapping.cMappings); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fValid); + AssertRCReturn(rc, rc); + + if (mapping.fValid) + { + /* Load the host path name. */ + uint32_t cb; + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + + char *pszFolderName; + if (uShfVersion == SHFL_SAVED_STATE_VERSION_FOLDERNAME_UTF16) /* (See version range check above.) */ + { + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad folder name size: %#x", cb)); + PSHFLSTRING pFolderName = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pFolderName != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pFolderName, cb); + AssertRCReturn(rc, rc); + AssertReturn(pFolderName->u16Size < cb && pFolderName->u16Length < pFolderName->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad folder name string: %#x/%#x cb=%#x", + pFolderName->u16Size, pFolderName->u16Length, cb)); + + rc = RTUtf16ToUtf8(pFolderName->String.ucs2, &pszFolderName); + RTMemFree(pFolderName); + AssertRCReturn(rc, rc); + } + else + { + pszFolderName = (char *)RTStrAlloc(cb + 1); + AssertReturn(pszFolderName, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetStrZ(pSSM, pszFolderName, cb + 1); + AssertRCReturn(rc, rc); + mapping.pszFolderName = pszFolderName; + } + + /* Load the map name. */ + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad map name size: %#x", cb)); + + PSHFLSTRING pMapName = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pMapName != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pMapName, cb); + AssertRCReturn(rc, rc); + AssertReturn(pMapName->u16Size < cb && pMapName->u16Length < pMapName->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad map name string: %#x/%#x cb=%#x", + pMapName->u16Size, pMapName->u16Length, cb)); + + /* Load case sensitivity config. */ + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fHostCaseSensitive); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &mapping.fGuestCaseSensitive); + AssertRCReturn(rc, rc); + + /* Load the auto mount point. */ + PSHFLSTRING pAutoMountPoint; + if (uShfVersion > SHFL_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT) + { + rc = pVMM->pfnSSMR3GetU32(pSSM, &cb); + AssertRCReturn(rc, rc); + AssertReturn(cb > SHFLSTRING_HEADER_SIZE && cb <= UINT16_MAX + SHFLSTRING_HEADER_SIZE && !(cb & 1), + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad auto mount point size: %#x", cb)); + + pAutoMountPoint = (PSHFLSTRING)RTMemAlloc(cb); + AssertReturn(pAutoMountPoint != NULL, VERR_NO_MEMORY); + + rc = pVMM->pfnSSMR3GetMem(pSSM, pAutoMountPoint, cb); + AssertRCReturn(rc, rc); + AssertReturn(pAutoMountPoint->u16Size < cb && pAutoMountPoint->u16Length < pAutoMountPoint->u16Size, + pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "Bad auto mount point string: %#x/%#x cb=%#x", + pAutoMountPoint->u16Size, pAutoMountPoint->u16Length, cb)); + + } + else + { + pAutoMountPoint = ShflStringDupUtf8(""); + AssertReturn(pAutoMountPoint, VERR_NO_MEMORY); + } + + mapping.pszFolderName = pszFolderName; + mapping.pMapName = pMapName; + mapping.pAutoMountPoint = pAutoMountPoint; + + /* 'i' is the root handle of the saved mapping. */ + rc = vbsfMappingLoaded(&mapping, i); + if (RT_FAILURE(rc)) + { + LogRel(("SharedFolders host service: %Rrc loading %d [%ls] -> [%s]\n", + rc, i, pMapName->String.ucs2, pszFolderName)); + } + + RTMemFree(pAutoMountPoint); + RTMemFree(pMapName); + RTStrFree(pszFolderName); + + AssertRCReturn(rc, rc); + } + } + + /* Make sure all mappings have root IDs (global folders changes, VM + config changes (paranoia)): */ + vbsfMappingLoadingDone(); + + Log(("SharedFolders host service: successfully loaded state\n")); +#else + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); +#endif + return VINF_SUCCESS; +} + +static DECLCALLBACK(void) svcCall (void *, VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + RT_NOREF(u32ClientID, tsArrival); +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS + uint64_t tsStart; + STAM_GET_TS(tsStart); + STAM_REL_PROFILE_ADD_PERIOD(&g_StatMsgStage1, tsStart - tsArrival); +#endif + Log(("SharedFolders host service: svcCall: u32ClientID = %u, fn = %u, cParms = %u, pparms = %p\n", u32ClientID, u32Function, cParms, paParms)); + + SHFLCLIENTDATA *pClient = (SHFLCLIENTDATA *)pvClient; + + bool fAsynchronousProcessing = false; + +#ifdef LOG_ENABLED + for (uint32_t i = 0; i < cParms; i++) + { + /** @todo parameters other than 32 bit */ + Log((" pparms[%d]: type %u, value %u\n", i, paParms[i].type, paParms[i].u.uint32)); + } +#endif + + int rc = VINF_SUCCESS; + PSTAMPROFILE pStat, pStatFail; + switch (u32Function) + { + case SHFL_FN_QUERY_MAPPINGS: + { + pStat = &g_StatQueryMappings; + pStatFail = &g_StatQueryMappingsFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_QUERY_MAPPINGS\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_QUERY_MAPPINGS) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* numberOfMappings */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* mappings */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + uint32_t fu32Flags = paParms[0].u.uint32; + uint32_t cMappings = paParms[1].u.uint32; + SHFLMAPPING *pMappings = (SHFLMAPPING *)paParms[2].u.pointer.addr; + uint32_t cbMappings = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if ( (fu32Flags & ~SHFL_MF_MASK) != 0 + || cbMappings / sizeof (SHFLMAPPING) != cMappings + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + if (fu32Flags & SHFL_MF_UTF8) + pClient->fu32Flags |= SHFL_CF_UTF8; + /// @todo r=bird: Someone please explain this amusing code (r63916): + //if (fu32Flags & SHFL_MF_AUTOMOUNT) + // pClient->fu32Flags |= SHFL_MF_AUTOMOUNT; + // + //rc = vbsfMappingsQuery(pClient, pMappings, &cMappings); + + rc = vbsfMappingsQuery(pClient, RT_BOOL(fu32Flags & SHFL_MF_AUTOMOUNT), pMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + /* Report that there are more mappings to get if + * handed in buffer is too small. */ + if (paParms[1].u.uint32 < cMappings) + rc = VINF_BUFFER_OVERFLOW; + + /* Update parameters. */ + paParms[1].u.uint32 = cMappings; + } + } + } + + + } break; + + case SHFL_FN_QUERY_MAP_NAME: + { + pStatFail = pStat = &g_StatQueryMapName; + Log(("SharedFolders host service: svcCall: SHFL_FN_QUERY_MAP_NAME\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_QUERY_MAP_NAME) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* Root. */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* Name. */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pString = (SHFLSTRING *)paParms[1].u.pointer.addr; + + /* Verify parameters values. */ + if (!ShflStringIsValidOut(pString, paParms[1].u.pointer.size)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMappingsQueryName(pClient, root, pString); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* None. */ + } + } + } + + } break; + + case SHFL_FN_CREATE: + { + pStat = &g_StatCreate; + pStatFail = &g_StatCreateFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CREATE\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_CREATE) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* parms */ + ) + { + Log(("SharedFolders host service: Invalid parameters types\n")); + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t cbPath = paParms[1].u.pointer.size; + SHFLCREATEPARMS *pParms = (SHFLCREATEPARMS *)paParms[2].u.pointer.addr; + uint32_t cbParms = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pPath, cbPath, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || (cbParms != sizeof (SHFLCREATEPARMS)) + ) + { + AssertMsgFailed (("Invalid parameters cbPath or cbParms (%x, %x - expected >=%x, %x)\n", + cbPath, cbParms, sizeof(SHFLSTRING), sizeof (SHFLCREATEPARMS))); + rc = VERR_INVALID_PARAMETER; + } + else + { + if (pParms->CreateFlags & SHFL_CF_LOOKUP) + { + pStat = &g_StatLookup; + pStatFail = &g_StatLookupFail; + } + + /* Execute the function. */ + rc = vbsfCreate (pClient, root, pPath, cbPath, pParms); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + } + + case SHFL_FN_CLOSE: + { + pStat = &g_StatClose; + pStatFail = &g_StatCloseFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CLOSE\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_CLOSE) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else + { + /* Execute the function. */ + rc = vbsfClose (pClient, root, Handle); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + + } + + /* Read object content. */ + case SHFL_FN_READ: + { + pStat = &g_StatRead; + pStatFail = &g_StatReadFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_READ\n")); + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_READ, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* offset */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* count */ + ASSERT_GUEST_STMT_BREAK( paParms[4].type == VBOX_HGCM_SVC_PARM_PTR + || paParms[4].type == VBOX_HGCM_SVC_PARM_PAGES, rc = VERR_WRONG_PARAMETER_TYPE); /* buffer */ + + /* Fetch parameters. */ + SHFLROOT const idRoot = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE const hFile = paParms[1].u.uint64; + uint64_t const offFile = paParms[2].u.uint64; + uint32_t cbRead = paParms[3].u.uint32; + + /* Verify parameters values. */ + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_ROOT, rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_NIL, rc = VERR_INVALID_HANDLE); + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + ASSERT_GUEST_STMT_BREAK(cbRead <= paParms[4].u.pointer.size, rc = VERR_INVALID_HANDLE); + else + ASSERT_GUEST_STMT_BREAK(cbRead <= paParms[4].u.Pages.cb, rc = VERR_OUT_OF_RANGE); + + /* Execute the function. */ + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fReading = g_pStatusLed->Actual.s.fReading = 1; + } + + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + rc = vbsfRead(pClient, idRoot, hFile, offFile, &cbRead, (uint8_t *)paParms[4].u.pointer.addr); + else + rc = vbsfReadPages(pClient, idRoot, hFile, offFile, &cbRead, &paParms[4].u.Pages); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fReading = 0; + + /* Update parameters.*/ + paParms[3].u.uint32 = RT_SUCCESS(rc) ? cbRead : 0 /* nothing read */; + break; + } + + /* Write new object content. */ + case SHFL_FN_WRITE: + { + pStat = &g_StatWrite; + pStatFail = &g_StatWriteFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_WRITE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_WRITE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* offset */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* count */ + ASSERT_GUEST_STMT_BREAK( paParms[4].type == VBOX_HGCM_SVC_PARM_PTR + || paParms[4].type == VBOX_HGCM_SVC_PARM_PAGES, rc = VERR_WRONG_PARAMETER_TYPE); /* buffer */ + /* Fetch parameters. */ + SHFLROOT const idRoot = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE const hFile = paParms[1].u.uint64; + uint64_t offFile = paParms[2].u.uint64; + uint32_t cbWrite = paParms[3].u.uint32; + + /* Verify parameters values. */ + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_ROOT, rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(hFile != SHFL_HANDLE_NIL, rc = VERR_INVALID_HANDLE); + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + ASSERT_GUEST_STMT_BREAK(cbWrite <= paParms[4].u.pointer.size, rc = VERR_INVALID_HANDLE); + else + ASSERT_GUEST_STMT_BREAK(cbWrite <= paParms[4].u.Pages.cb, rc = VERR_OUT_OF_RANGE); + + /* Execute the function. */ + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fWriting = g_pStatusLed->Actual.s.fWriting = 1; + } + + if (paParms[4].type == VBOX_HGCM_SVC_PARM_PTR) + rc = vbsfWrite(pClient, idRoot, hFile, &offFile, &cbWrite, (uint8_t *)paParms[4].u.pointer.addr); + else + rc = vbsfWritePages(pClient, idRoot, hFile, &offFile, &cbWrite, &paParms[4].u.Pages); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fWriting = 0; + + /* Update parameters.*/ + if (RT_SUCCESS(rc)) + { + paParms[3].u.uint32 = cbWrite; + paParms[2].u.uint64 = offFile; + } + else + paParms[3].u.uint32 = 0; + break; + } + + /* Lock/unlock a range in the object. */ + case SHFL_FN_LOCK: + pStat = &g_StatLock; + pStatFail = &g_StatLockFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_LOCK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_LOCK) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_64BIT /* offset */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_64BIT /* length */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint64_t offset = paParms[2].u.uint64; + uint64_t length = paParms[3].u.uint64; + uint32_t flags = paParms[4].u.uint32; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else if (flags & SHFL_LOCK_WAIT) + { + /** @todo This should be properly implemented by the shared folders service. + * The service thread must never block. If an operation requires + * blocking, it must be processed by another thread and when it is + * completed, the another thread must call + * + * g_pHelpers->pfnCallComplete (callHandle, rc); + * + * The operation is async. + * fAsynchronousProcessing = true; + */ + + /* Here the operation must be posted to another thread. At the moment it is not implemented. + * Until it is implemented, try to perform the operation without waiting. + */ + flags &= ~SHFL_LOCK_WAIT; + + /* Execute the function. */ + if ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + rc = vbsfUnlock(pClient, root, Handle, offset, length, flags); + else + rc = vbsfLock(pClient, root, Handle, offset, length, flags); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* none */ + } + } + else + { + /* Execute the function. */ + if ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + rc = vbsfUnlock(pClient, root, Handle, offset, length, flags); + else + rc = vbsfLock(pClient, root, Handle, offset, length, flags); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* none */ + } + } + } + break; + + /* List object content. */ + case SHFL_FN_LIST: + { + pStat = &g_StatList; + pStatFail = &g_StatListFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_LIST\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_LIST) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* cb */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_PTR /* pPath */ + || paParms[5].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + || paParms[6].type != VBOX_HGCM_SVC_PARM_32BIT /* resumePoint */ + || paParms[7].type != VBOX_HGCM_SVC_PARM_32BIT /* cFiles (out) */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint32_t flags = paParms[2].u.uint32; + uint32_t length = paParms[3].u.uint32; + SHFLSTRING *pPath = (paParms[4].u.pointer.size == 0) ? 0 : (SHFLSTRING *)paParms[4].u.pointer.addr; + uint8_t *pBuffer = (uint8_t *)paParms[5].u.pointer.addr; + uint32_t resumePoint = paParms[6].u.uint32; + uint32_t cFiles = 0; + + /* Verify parameters values. */ + if ( (length < sizeof (SHFLDIRINFO)) + || length > paParms[5].u.pointer.size + || !ShflStringIsValidOrNullIn(pPath, paParms[4].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + if (g_pStatusLed) + { + Assert(g_pStatusLed->u32Magic == PDMLED_MAGIC); + g_pStatusLed->Asserted.s.fReading = g_pStatusLed->Actual.s.fReading = 1; + } + + /* Execute the function. */ + rc = vbsfDirList (pClient, root, Handle, pPath, flags, &length, pBuffer, &resumePoint, &cFiles); + + if (g_pStatusLed) + g_pStatusLed->Actual.s.fReading = 0; + + if (rc == VERR_NO_MORE_FILES && cFiles != 0) + rc = VINF_SUCCESS; /* Successfully return these files. */ + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[3].u.uint32 = length; + paParms[6].u.uint32 = resumePoint; + paParms[7].u.uint32 = cFiles; + } + else + { + paParms[3].u.uint32 = 0; /* nothing read */ + paParms[6].u.uint32 = 0; + paParms[7].u.uint32 = cFiles; + } + } + } + break; + } + + /* Read symlink destination */ + case SHFL_FN_READLINK: + { + pStat = &g_StatReadLink; + pStatFail = &g_StatReadLinkFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_READLINK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_READLINK) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t cbPath = paParms[1].u.pointer.size; + uint8_t *pBuffer = (uint8_t *)paParms[2].u.pointer.addr; + uint32_t cbBuffer = paParms[2].u.pointer.size; + + /* Verify parameters values. */ + if (!ShflStringIsValidOrNullIn(pPath, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfReadLink (pClient, root, pPath, cbPath, pBuffer, cbBuffer); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + + break; + } + + /* Legacy interface */ + case SHFL_FN_MAP_FOLDER_OLD: + { + pStatFail = pStat = &g_StatMapFolderOld; + Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER_OLD\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_MAP_FOLDER_OLD) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* delimiter */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PSHFLSTRING pszMapName = (PSHFLSTRING)paParms[0].u.pointer.addr; + SHFLROOT root = (SHFLROOT)paParms[1].u.uint32; + RTUTF16 delimiter = (RTUTF16)paParms[2].u.uint32; + + /* Verify parameters values. */ + if (!ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMapFolder (pClient, pszMapName, delimiter, false, &root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[1].u.uint32 = root; + } + } + } + break; + } + + case SHFL_FN_MAP_FOLDER: + { + pStat = &g_StatMapFolder; + pStatFail = &g_StatMapFolderFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER\n")); + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + Log(("SharedFolders host service: request to map folder '%s'\n", + ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf8)); + else + Log(("SharedFolders host service: request to map folder '%ls'\n", + ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.ucs2)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_MAP_FOLDER) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* delimiter */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* fCaseSensitive */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PSHFLSTRING pszMapName = (PSHFLSTRING)paParms[0].u.pointer.addr; + SHFLROOT root = (SHFLROOT)paParms[1].u.uint32; + RTUTF16 delimiter = (RTUTF16)paParms[2].u.uint32; + bool fCaseSensitive = !!paParms[3].u.uint32; + + /* Verify parameters values. */ + if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8))) + { + rc = VINF_SUCCESS; + } + else + { + rc = VERR_INVALID_PARAMETER; + + /* Fudge for windows GAs getting the length wrong by one char. */ + if ( !(pClient->fu32Flags & SHFL_CF_UTF8) + && paParms[0].u.pointer.size >= sizeof(SHFLSTRING) + && pszMapName->u16Length >= 2 + && pszMapName->String.ucs2[pszMapName->u16Length / 2 - 1] == 0x0000) + { + pszMapName->u16Length -= 2; + if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) + rc = VINF_SUCCESS; + else + pszMapName->u16Length += 2; + } + } + + /* Execute the function. */ + if (RT_SUCCESS(rc)) + rc = vbsfMapFolder (pClient, pszMapName, delimiter, fCaseSensitive, &root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[1].u.uint32 = root; + } + } + Log(("SharedFolders host service: map operation result %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + Log(("SharedFolders host service: mapped to handle %d\n", paParms[1].u.uint32)); + break; + } + + case SHFL_FN_UNMAP_FOLDER: + { + pStat = &g_StatUnmapFolder; + pStatFail = &g_StatUnmapFolderFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_UNMAP_FOLDER\n")); + Log(("SharedFolders host service: request to unmap folder handle %u\n", + paParms[0].u.uint32)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_UNMAP_FOLDER) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + + /* Execute the function. */ + rc = vbsfUnmapFolder (pClient, root); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + /* nothing */ + } + } + Log(("SharedFolders host service: unmap operation result %Rrc\n", rc)); + break; + } + + /* Query/set object information. */ + case SHFL_FN_INFORMATION: + { + pStatFail = pStat = &g_StatInformationFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_INFORMATION\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_INFORMATION) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* cb */ + || paParms[4].type != VBOX_HGCM_SVC_PARM_PTR /* buffer */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + uint32_t flags = paParms[2].u.uint32; + uint32_t length = paParms[3].u.uint32; + uint8_t *pBuffer = (uint8_t *)paParms[4].u.pointer.addr; + + /* Verify parameters values. */ + if (length > paParms[4].u.pointer.size) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + if (flags & SHFL_INFO_SET) + { + rc = vbsfSetFSInfo (pClient, root, Handle, flags, &length, pBuffer); + + if (flags & SHFL_INFO_FILE) + { + pStat = &g_StatInformationSetFile; + pStatFail = &g_StatInformationSetFileFail; + } + else if (flags & SHFL_INFO_SIZE) + { + pStat = &g_StatInformationSetSize; + pStatFail = &g_StatInformationSetSizeFail; + } + } + else /* SHFL_INFO_GET */ + { + rc = vbsfQueryFSInfo (pClient, root, Handle, flags, &length, pBuffer); + + if (flags & SHFL_INFO_FILE) + { + pStat = &g_StatInformationGetFile; + pStatFail = &g_StatInformationGetFileFail; + } + else if (flags & SHFL_INFO_VOLUME) + { + pStat = &g_StatInformationGetVolume; + pStatFail = &g_StatInformationGetVolumeFail; + } + } + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + paParms[3].u.uint32 = length; + } + else + { + paParms[3].u.uint32 = 0; /* nothing read */ + } + } + } + break; + } + + /* Remove or rename object */ + case SHFL_FN_REMOVE: + { + pStat = &g_StatRemove; + pStatFail = &g_StatRemoveFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_REMOVE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_REMOVE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* path */ + PCSHFLSTRING pStrPath = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPath, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* flags */ + uint32_t const fFlags = paParms[2].u.uint32; + ASSERT_GUEST_STMT_BREAK(!(fFlags & ~(SHFL_REMOVE_FILE | SHFL_REMOVE_DIR | SHFL_REMOVE_SYMLINK)), + rc = VERR_INVALID_FLAGS); + + /* Execute the function. */ + rc = vbsfRemove(pClient, paParms[0].u.uint32, pStrPath, paParms[1].u.pointer.size, fFlags, SHFL_HANDLE_NIL); + break; + } + + case SHFL_FN_CLOSE_AND_REMOVE: + { + pStat = &g_StatCloseAndRemove; + pStatFail = &g_StatCloseAndRemoveFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_CLOSE_AND_REMOVE\n")); + + /* Verify parameter count and types. */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_CLOSE_AND_REMOVE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* path */ + PCSHFLSTRING pStrPath = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPath, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* flags */ + uint32_t const fFlags = paParms[2].u.uint32; + ASSERT_GUEST_STMT_BREAK(!(fFlags & ~(SHFL_REMOVE_FILE | SHFL_REMOVE_DIR | SHFL_REMOVE_SYMLINK)), + rc = VERR_INVALID_FLAGS); + SHFLHANDLE const hToClose = paParms[3].u.uint64; + ASSERT_GUEST_STMT_BREAK(hToClose != SHFL_HANDLE_ROOT, rc = VERR_INVALID_HANDLE); + + /* Execute the function. */ + rc = vbsfRemove(pClient, paParms[0].u.uint32, pStrPath, paParms[1].u.pointer.size, fFlags, hToClose); + break; + } + + case SHFL_FN_RENAME: + { + pStat = &g_StatRename; + pStatFail = &g_StatRenameFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_RENAME\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_RENAME) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* src */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* dest */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_32BIT /* flags */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pSrc = (SHFLSTRING *)paParms[1].u.pointer.addr; + SHFLSTRING *pDest = (SHFLSTRING *)paParms[2].u.pointer.addr; + uint32_t flags = paParms[3].u.uint32; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pSrc, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || !ShflStringIsValidIn(pDest, paParms[2].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfRename (pClient, root, pSrc, pDest, flags); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + break; + } + + case SHFL_FN_FLUSH: + { + pStat = &g_StatFlush; + pStatFail = &g_StatFlushFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_FLUSH\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_FLUSH) + { + rc = VERR_INVALID_PARAMETER; + } + else + if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_64BIT /* handle */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLHANDLE Handle = paParms[1].u.uint64; + + /* Verify parameters values. */ + if (Handle == SHFL_HANDLE_ROOT) + { + rc = VERR_INVALID_PARAMETER; + } + else + if (Handle == SHFL_HANDLE_NIL) + { + AssertMsgFailed(("Invalid handle!\n")); + rc = VERR_INVALID_HANDLE; + } + else + { + /* Execute the function. */ + + rc = vbsfFlush (pClient, root, Handle); + + if (RT_SUCCESS(rc)) + { + /* Nothing to do */ + } + } + } + } break; + + case SHFL_FN_SET_UTF8: + { + pStatFail = pStat = &g_StatSetUtf8; + + pClient->fu32Flags |= SHFL_CF_UTF8; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_SYMLINK: + { + pStat = &g_StatSymlink; + pStatFail = &g_StatSymlinkFail; + Log(("SharedFolders host service: svnCall: SHFL_FN_SYMLINK\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_SYMLINK) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* root */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* newPath */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_PTR /* oldPath */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* info */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLROOT root = (SHFLROOT)paParms[0].u.uint32; + SHFLSTRING *pNewPath = (SHFLSTRING *)paParms[1].u.pointer.addr; + SHFLSTRING *pOldPath = (SHFLSTRING *)paParms[2].u.pointer.addr; + SHFLFSOBJINFO *pInfo = (SHFLFSOBJINFO *)paParms[3].u.pointer.addr; + uint32_t cbInfo = paParms[3].u.pointer.size; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pNewPath, paParms[1].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || !ShflStringIsValidIn(pOldPath, paParms[2].u.pointer.size, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) + || (cbInfo != sizeof(SHFLFSOBJINFO)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfSymlink (pClient, root, pNewPath, pOldPath, pInfo); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + } + break; + + case SHFL_FN_SET_SYMLINKS: + { + pStatFail = pStat = &g_StatSetSymlinks; + + pClient->fu32Flags |= SHFL_CF_SYMLINKS; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_QUERY_MAP_INFO: + { + pStatFail = pStat = &g_StatQueryMapInfo; + Log(("SharedFolders host service: svnCall: SHFL_FN_QUERY_MAP_INFO\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_QUERY_MAP_INFO); + ASSERT_GUEST_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); /* root */ + ASSERT_GUEST_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR); /* name */ + PSHFLSTRING pNameBuf = (PSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_BREAK(ShflStringIsValidOut(pNameBuf, paParms[1].u.pointer.size)); + ASSERT_GUEST_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_PTR); /* mountPoint */ + PSHFLSTRING pMntPtBuf = (PSHFLSTRING)paParms[2].u.pointer.addr; + ASSERT_GUEST_BREAK(ShflStringIsValidOut(pMntPtBuf, paParms[2].u.pointer.size)); + ASSERT_GUEST_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_64BIT); /* flags */ + ASSERT_GUEST_BREAK(!(paParms[3].u.uint64 & ~(SHFL_MIQF_DRIVE_LETTER | SHFL_MIQF_PATH))); /* flags */ + ASSERT_GUEST_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_32BIT); /* version */ + + /* Execute the function: */ + rc = vbsfMappingsQueryInfo(pClient, paParms[0].u.uint32, pNameBuf, pMntPtBuf, + &paParms[3].u.uint64, &paParms[4].u.uint32); + break; + } + + case SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES: + { + pStat = &g_StatWaitForMappingsChanges; + pStatFail = &g_StatWaitForMappingsChangesFail; + Log(("SharedFolders host service: svnCall: SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_WAIT_FOR_MAPPINGS_CHANGES); + ASSERT_GUEST_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); /* uFolderMappingsVersion */ + + /* Execute the function: */ + rc = vbsfMappingsWaitForChanges(pClient, callHandle, paParms, g_pHelpers->pfnIsCallRestored(callHandle)); + fAsynchronousProcessing = rc == VINF_HGCM_ASYNC_EXECUTE; + break; + } + + case SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS: + { + pStatFail = pStat = &g_StatCancelMappingsChangesWait; + Log(("SharedFolders host service: svnCall: SHFL_FN_CANCEL_WAIT_FOR_CHANGES\n")); + + /* Validate input: */ + rc = VERR_INVALID_PARAMETER; + ASSERT_GUEST_BREAK(cParms == SHFL_CPARMS_CANCEL_MAPPINGS_CHANGES_WAITS); + + /* Execute the function: */ + rc = vbsfMappingsCancelChangesWaits(pClient); + break; + } + + case SHFL_FN_SET_FILE_SIZE: + { + pStat = &g_StatSetFileSize; + pStatFail = &g_StatSetFileSizeFail; + Log(("SharedFolders host service: svcCall: SHFL_FN_SET_FILE_SIZE\n")); + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_SET_FILE_SIZE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* id32Root */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64Handle */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* cb64NewSize */ + + /* Execute the function: */ + rc = vbsfSetFileSize(pClient, paParms[0].u.uint32, paParms[1].u.uint64, paParms[2].u.uint64); + break; + } + + case SHFL_FN_QUERY_FEATURES: + { + pStat = pStatFail = &g_StatQueryFeatures; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_QUERY_FEATURES, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f64Features */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u32LastFunction */ + + /* Execute the function: */ + paParms[0].u.uint64 = SHFL_FEATURE_WRITE_UPDATES_OFFSET; + paParms[1].u.uint32 = SHFL_FN_LAST; + rc = VINF_SUCCESS; + break; + } + + case SHFL_FN_COPY_FILE: + { + pStat = &g_StatCopyFile; + pStatFail = &g_StatCopyFileFail; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_COPY_FILE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* pStrPathSrc */ + PCSHFLSTRING pStrPathSrc = (PCSHFLSTRING)paParms[1].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPathSrc, paParms[1].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootDst */ + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* pStrPathDst */ + PCSHFLSTRING pStrPathDst = (PCSHFLSTRING)paParms[3].u.pointer.addr; + ASSERT_GUEST_STMT_BREAK(ShflStringIsValidIn(pStrPathDst, paParms[3].u.pointer.size, + RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_STMT_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f32Flags */ + ASSERT_GUEST_STMT_BREAK(paParms[4].u.uint32 == 0, rc = VERR_INVALID_FLAGS); + + /* Execute the function: */ + rc = vbsfCopyFile(pClient, paParms[0].u.uint32, pStrPathSrc, paParms[2].u.uint64, pStrPathDst, paParms[3].u.uint32); + break; + } + + + case SHFL_FN_COPY_FILE_PART: + { + pStat = &g_StatCopyFilePart; + pStatFail = &g_StatCopyFilePartFail; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_COPY_FILE_PART, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64HandleSrc */ + ASSERT_GUEST_STMT_BREAK(paParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* off64Src */ + ASSERT_GUEST_STMT_BREAK((int64_t)paParms[2].u.uint64 >= 0, rc = VERR_NEGATIVE_SEEK); + ASSERT_GUEST_STMT_BREAK(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* i32RootDst */ + ASSERT_GUEST_STMT_BREAK(paParms[4].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u64HandleDst */ + ASSERT_GUEST_STMT_BREAK(paParms[5].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* off64Dst */ + ASSERT_GUEST_STMT_BREAK((int64_t)paParms[5].u.uint64 >= 0, rc = VERR_NEGATIVE_SEEK); + ASSERT_GUEST_STMT_BREAK(paParms[6].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* cb64ToCopy */ + ASSERT_GUEST_STMT_BREAK(paParms[6].u.uint64 < _1E, rc = VERR_OUT_OF_RANGE); + ASSERT_GUEST_STMT_BREAK(paParms[7].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* f32Flags */ + ASSERT_GUEST_STMT_BREAK(paParms[7].u.uint32 == 0, rc = VERR_INVALID_FLAGS); + + /* Execute the function: */ + rc = vbsfCopyFilePart(pClient, + paParms[0].u.uint32, paParms[1].u.uint64, paParms[2].u.uint64, + paParms[3].u.uint32, paParms[4].u.uint64, paParms[5].u.uint64, + &paParms[6].u.uint64, paParms[7].u.uint64); + break; + } + + case SHFL_FN_SET_ERROR_STYLE: + { + pStatFail = pStat = &g_StatSetErrorStyle; + + /* Validate input: */ + ASSERT_GUEST_STMT_BREAK(cParms == SHFL_CPARMS_SET_ERROR_STYLE, rc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_STMT_BREAK(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* enm32Style */ + ASSERT_GUEST_STMT_BREAK( paParms[0].u.uint32 > (uint32_t)kShflErrorStyle_Invalid + && paParms[0].u.uint32 < (uint32_t)kShflErrorStyle_End, rc = VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_STMT_BREAK(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* u32Reserved */ + ASSERT_GUEST_STMT_BREAK(paParms[1].u.uint32 == 0, rc = VERR_WRONG_PARAMETER_TYPE); + + /* Do the work: */ + pClient->enmErrorStyle = (uint8_t)paParms[0].u.uint32; + rc = VINF_SUCCESS; + break; + } + + default: + { + pStatFail = pStat = &g_StatUnknown; + rc = VERR_NOT_IMPLEMENTED; + break; + } + } + + LogFlow(("SharedFolders host service: svcCall: rc=%Rrc\n", rc)); + + if ( !fAsynchronousProcessing + || RT_FAILURE (rc)) + { + /* Complete the operation if it was unsuccessful or + * it was processed synchronously. + */ + g_pHelpers->pfnCallComplete (callHandle, rc); + } + +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS + /* Statistics: */ + uint64_t cTicks; + STAM_GET_TS(cTicks); + cTicks -= tsStart; + if (RT_SUCCESS(rc)) + STAM_REL_PROFILE_ADD_PERIOD(pStat, cTicks); + else + STAM_REL_PROFILE_ADD_PERIOD(pStatFail, cTicks); +#endif + + LogFlow(("\n")); /* Add a new line to differentiate between calls more easily. */ +} + +/* + * We differentiate between a function handler for the guest (svcCall) and one + * for the host. The guest is not allowed to add or remove mappings for obvious + * security reasons. + */ +static DECLCALLBACK(int) svcHostCall (void *, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + + Log(("svcHostCall: fn = %d, cParms = %d, pparms = %d\n", u32Function, cParms, paParms)); + +#ifdef DEBUG + uint32_t i; + + for (i = 0; i < cParms; i++) + { + /** @todo parameters other than 32 bit */ + Log((" pparms[%d]: type %d value %d\n", i, paParms[i].type, paParms[i].u.uint32)); + } +#endif + + switch (u32Function) + { + case SHFL_FN_ADD_MAPPING: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_ADD_MAPPING\n")); + LogRel(("SharedFolders host service: Adding host mapping\n")); + /* Verify parameter count and types. */ + if ( (cParms != SHFL_CPARMS_ADD_MAPPING) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* host folder path */ + || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* map name */ + || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* fFlags */ + || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /* auto mount point */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLSTRING *pHostPath = (SHFLSTRING *)paParms[0].u.pointer.addr; + SHFLSTRING *pMapName = (SHFLSTRING *)paParms[1].u.pointer.addr; + uint32_t fFlags = paParms[2].u.uint32; + SHFLSTRING *pAutoMountPoint = (SHFLSTRING *)paParms[3].u.pointer.addr; + + /* Verify parameters values. */ + if ( !ShflStringIsValidIn(pHostPath, paParms[0].u.pointer.size, false /*fUtf8Not16*/) + || !ShflStringIsValidIn(pMapName, paParms[1].u.pointer.size, false /*fUtf8Not16*/) + || !ShflStringIsValidIn(pAutoMountPoint, paParms[3].u.pointer.size, false /*fUtf8Not16*/) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + LogRel((" Host path '%ls', map name '%ls', %s, automount=%s, automntpnt=%ls, create_symlinks=%s, missing=%s\n", + pHostPath->String.utf16, pMapName->String.utf16, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_WRITABLE) ? "writable" : "read-only", + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_AUTOMOUNT) ? "true" : "false", + pAutoMountPoint->String.utf16, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_CREATE_SYMLINKS) ? "true" : "false", + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_MISSING) ? "true" : "false")); + + char *pszHostPath; + rc = RTUtf16ToUtf8(pHostPath->String.ucs2, &pszHostPath); + if (RT_SUCCESS(rc)) + { + /* Execute the function. */ + rc = vbsfMappingsAdd(pszHostPath, pMapName, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_WRITABLE), + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_AUTOMOUNT), + pAutoMountPoint, + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_CREATE_SYMLINKS), + RT_BOOL(fFlags & SHFL_ADD_MAPPING_F_MISSING), + /* fPlaceholder = */ false); + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + RTStrFree(pszHostPath); + } + } + } + if (RT_FAILURE(rc)) + LogRel(("SharedFolders host service: Adding host mapping failed with rc=%Rrc\n", rc)); + break; + } + + case SHFL_FN_REMOVE_MAPPING: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_REMOVE_MAPPING\n")); + LogRel(("SharedFolders host service: Removing host mapping '%ls'\n", + ((SHFLSTRING *)paParms[0].u.pointer.addr)->String.ucs2)); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_REMOVE_MAPPING) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* folder name */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + SHFLSTRING *pString = (SHFLSTRING *)paParms[0].u.pointer.addr; + + /* Verify parameters values. */ + if (!ShflStringIsValidIn(pString, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + rc = vbsfMappingsRemove (pString); + + if (RT_SUCCESS(rc)) + { + /* Update parameters.*/ + ; /* none */ + } + } + } + if (RT_FAILURE(rc)) + LogRel(("SharedFolders host service: Removing host mapping failed with rc=%Rrc\n", rc)); + break; + } + + case SHFL_FN_SET_STATUS_LED: + { + Log(("SharedFolders host service: svcCall: SHFL_FN_SET_STATUS_LED\n")); + + /* Verify parameter count and types. */ + if (cParms != SHFL_CPARMS_SET_STATUS_LED) + { + rc = VERR_INVALID_PARAMETER; + } + else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_PTR /* folder name */ + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Fetch parameters. */ + PPDMLED pLed = (PPDMLED)paParms[0].u.pointer.addr; + uint32_t cbLed = paParms[0].u.pointer.size; + + /* Verify parameters values. */ + if ( (cbLed != sizeof (PDMLED)) + ) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Execute the function. */ + g_pStatusLed = pLed; + rc = VINF_SUCCESS; + } + } + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlow(("SharedFolders host service: svcHostCall ended with rc=%Rrc\n", rc)); + return rc; +} + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable) +{ + int rc = VINF_SUCCESS; + + Log(("SharedFolders host service: VBoxHGCMSvcLoad: ptable = %p\n", ptable)); + + if (!RT_VALID_PTR(ptable)) + { + LogRelFunc(("SharedFolders host service: Bad value of ptable (%p)\n", ptable)); + rc = VERR_INVALID_PARAMETER; + } + else + { + Log(("SharedFolders host service: VBoxHGCMSvcLoad: ptable->cbSize = %u, ptable->u32Version = 0x%08X\n", + ptable->cbSize, ptable->u32Version)); + + if ( ptable->cbSize != sizeof (VBOXHGCMSVCFNTABLE) + || ptable->u32Version != VBOX_HGCM_SVC_VERSION) + { + LogRelFunc(("SharedFolders host service: Version mismatch while loading: ptable->cbSize = %u (should be %u), ptable->u32Version = 0x%08X (should be 0x%08X)\n", + ptable->cbSize, sizeof (VBOXHGCMSVCFNTABLE), ptable->u32Version, VBOX_HGCM_SVC_VERSION)); + rc = VERR_VERSION_MISMATCH; + } + else + { + g_pHelpers = ptable->pHelpers; + + ptable->cbClient = sizeof (SHFLCLIENTDATA); + + /* Map legacy clients to the kernel category. */ + ptable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL; + + /* Only 64K pending calls per kernel client, root gets 16K and regular users 1K. */ + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] = _64K; + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] = _16K; + ptable->acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER] = _1K; + + /* Reduce the number of clients to SHFL_MAX_MAPPINGS + 2 in each category, + so the increased calls-per-client value causes less trouble. + ((64 + 2) * 3 * 65536 = 12 976 128) */ + for (uintptr_t i = 0; i < RT_ELEMENTS(ptable->acMaxClients); i++) + ptable->acMaxClients[i] = SHFL_MAX_MAPPINGS + 2; + + ptable->pfnUnload = svcUnload; + ptable->pfnConnect = svcConnect; + ptable->pfnDisconnect = svcDisconnect; + ptable->pfnCall = svcCall; + ptable->pfnHostCall = svcHostCall; + ptable->pfnSaveState = svcSaveState; + ptable->pfnLoadState = svcLoadState; + ptable->pfnNotify = NULL; + ptable->pvService = NULL; + } + + /* Init handle table */ + rc = vbsfInitHandleTable(); + AssertRC(rc); + + vbsfMappingInit(); + + /* Finally, register statistics if everything went well: */ + if (RT_SUCCESS(rc)) + { + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMappings, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAPPINGS successes", "/HGCM/VBoxSharedFolders/FnQueryMappings"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMappingsFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAPPINGS failures", "/HGCM/VBoxSharedFolders/FnQueryMappingsFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMapName, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAP_NAME", "/HGCM/VBoxSharedFolders/FnQueryMapName"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCreate, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/CREATE successes", "/HGCM/VBoxSharedFolders/FnCreate"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCreateFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/CREATE failures", "/HGCM/VBoxSharedFolders/FnCreateFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLookup, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/LOOKUP successes", "/HGCM/VBoxSharedFolders/FnLookup"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLookupFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CREATE/LOOKUP failures", "/HGCM/VBoxSharedFolders/FnLookupFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatClose, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE successes", "/HGCM/VBoxSharedFolders/FnClose"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE failures", "/HGCM/VBoxSharedFolders/FnCloseFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRead, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READ successes", "/HGCM/VBoxSharedFolders/FnRead"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READ failures", "/HGCM/VBoxSharedFolders/FnReadFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWrite, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WRITE successes", "/HGCM/VBoxSharedFolders/FnWrite"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWriteFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WRITE failures", "/HGCM/VBoxSharedFolders/FnWriteFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLock, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LOCK successes", "/HGCM/VBoxSharedFolders/FnLock"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatLockFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LOCK failures", "/HGCM/VBoxSharedFolders/FnLockFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatList, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LIST successes", "/HGCM/VBoxSharedFolders/FnList"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatListFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_LIST failures", "/HGCM/VBoxSharedFolders/FnListFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadLink, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READLINK successes", "/HGCM/VBoxSharedFolders/FnReadLink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatReadLinkFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_READLINK failures", "/HGCM/VBoxSharedFolders/FnReadLinkFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolderOld, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER_OLD", "/HGCM/VBoxSharedFolders/FnMapFolderOld"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolder, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER successes", "/HGCM/VBoxSharedFolders/FnMapFolder"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMapFolderFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_MAP_FOLDER failures", "/HGCM/VBoxSharedFolders/FnMapFolderFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnmapFolder, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_UNMAP_FOLDER successes", "/HGCM/VBoxSharedFolders/FnUnmapFolder"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnmapFolderFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_UNMAP_FOLDER failures", "/HGCM/VBoxSharedFolders/FnUnmapFolderFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION early failures", "/HGCM/VBoxSharedFolders/FnInformationFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/FILE successes", "/HGCM/VBoxSharedFolders/FnInformationSetFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/FILE failures", "/HGCM/VBoxSharedFolders/FnInformationSetFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetSize, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/SIZE successes", "/HGCM/VBoxSharedFolders/FnInformationSetSize"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationSetSizeFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/SET/SIZE failures", "/HGCM/VBoxSharedFolders/FnInformationSetSizeFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/FILE successes", "/HGCM/VBoxSharedFolders/FnInformationGetFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/FILE failures", "/HGCM/VBoxSharedFolders/FnInformationGetFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetVolume, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/VOLUME successes", "/HGCM/VBoxSharedFolders/FnInformationGetVolume"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatInformationGetVolumeFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_INFORMATION/GET/VOLUME failures", "/HGCM/VBoxSharedFolders/FnInformationGetVolumeFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRemove, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_REMOVE successes", "/HGCM/VBoxSharedFolders/FnRemove"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRemoveFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_REMOVE failures", "/HGCM/VBoxSharedFolders/FnRemoveFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseAndRemove, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE_AND_REMOVE successes", "/HGCM/VBoxSharedFolders/FnCloseAndRemove"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCloseAndRemoveFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CLOSE_AND_REMOVE failures", "/HGCM/VBoxSharedFolders/FnCloseAndRemoveFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRename, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_RENAME successes", "/HGCM/VBoxSharedFolders/FnRename"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatRenameFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_RENAME failures", "/HGCM/VBoxSharedFolders/FnRenameFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatFlush, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_FLUSH successes", "/HGCM/VBoxSharedFolders/FnFlush"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatFlushFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_FLUSH failures", "/HGCM/VBoxSharedFolders/FnFlushFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetErrorStyle, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_ERROR_STYLE", "/HGCM/VBoxSharedFolders/FnSetErrorStyle"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetUtf8, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_UTF8", "/HGCM/VBoxSharedFolders/FnSetUtf8"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSymlink, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SYMLINK successes", "/HGCM/VBoxSharedFolders/FnSymlink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSymlinkFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SYMLINK failures", "/HGCM/VBoxSharedFolders/FnSymlinkFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatSetSymlinks, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_SET_SYMLINKS", "/HGCM/VBoxSharedFolders/FnSetSymlink"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryMapInfo, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_MAP_INFO", "/HGCM/VBoxSharedFolders/FnQueryMapInfo"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatQueryFeatures, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_QUERY_FEATURES", "/HGCM/VBoxSharedFolders/FnQueryFeatures"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFile, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE successes", "/HGCM/VBoxSharedFolders/FnCopyFile"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFileFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE failures", "/HGCM/VBoxSharedFolders/FnCopyFileFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFilePart, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE_PART successes", "/HGCM/VBoxSharedFolders/FnCopyFilePart"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCopyFilePartFail, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_COPY_FILE_PART failures", "/HGCM/VBoxSharedFolders/FnCopyFilePartFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWaitForMappingsChanges, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES successes", "/HGCM/VBoxSharedFolders/FnWaitForMappingsChanges"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatWaitForMappingsChangesFail,STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES failures","/HGCM/VBoxSharedFolders/FnWaitForMappingsChangesFail"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatCancelMappingsChangesWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS", "/HGCM/VBoxSharedFolders/FnCancelMappingsChangesWaits"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatUnknown, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "SHFL_FN_???", "/HGCM/VBoxSharedFolders/FnUnknown"); + HGCMSvcHlpStamRegister(g_pHelpers, &g_StatMsgStage1, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Time from VMMDev arrival to worker thread.","/HGCM/VBoxSharedFolders/MsgStage1"); + } + } + + return rc; +} + diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc new file mode 100644 index 00000000..05a89bc8 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxSharedFoldersSvc.rc $ */ +/** @file + * VBoxSharedFolders - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Shared Folders Host Service\0" + VALUE "InternalName", "VBoxSharedFolders\0" + VALUE "OriginalFilename", "VBoxSharedFolders.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END 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 . + * + * 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 +#include +#include +#include +#include +#include + +#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); + } +} + diff --git a/src/VBox/HostServices/SharedFolders/mappings.h b/src/VBox/HostServices/SharedFolders/mappings.h new file mode 100644 index 00000000..abeafb35 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/mappings.h @@ -0,0 +1,92 @@ +/* $Id: mappings.h $ */ +/** @file + * Shared folders service - Mappings header. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_mappings_h +#define VBOX_INCLUDED_SRC_SharedFolders_mappings_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include + +typedef struct +{ + char *pszFolderName; /**< Directory at the host to share with the guest. */ + PSHFLSTRING pMapName; /**< Share name for the guest. */ + uint32_t cMappings; /**< Number of mappings. */ + bool fValid; /**< Mapping entry is used/valid. */ + bool fHostCaseSensitive; /**< Host file name space is case-sensitive. */ + bool fGuestCaseSensitive; /**< Guest file name space is case-sensitive. */ + bool fWritable; /**< Folder is writable for the guest. */ + PSHFLSTRING pAutoMountPoint; /**< Where the guest should try auto-mount the folder. */ + bool fAutoMount; /**< Folder will be auto-mounted by the guest. */ + bool fSymlinksCreate; /**< Guest is able to create symlinks. */ + bool fMissing; /**< Mapping not invalid but host path does not exist. + Any guest operation on such a folder fails! */ + bool fPlaceholder; /**< Mapping does not exist in the VM settings but the guest + still has. fMissing is always true for this mapping. */ + bool fLoadedRootId; /**< Set if vbsfMappingLoaded has found this mapping already. */ +} MAPPING; +/** Pointer to a MAPPING structure. */ +typedef MAPPING *PMAPPING; + +void vbsfMappingInit(void); + +bool vbsfMappingQuery(uint32_t iMapping, PMAPPING *pMapping); + +int vbsfMappingsAdd(const char *pszFolderName, PSHFLSTRING pMapName, bool fWritable, + bool fAutoMount, PSHFLSTRING pAutoMountPoint, bool fCreateSymlinks, bool fMissing, bool fPlaceholder); +int vbsfMappingsRemove(PSHFLSTRING pMapName); + +int vbsfMappingsQuery(PSHFLCLIENTDATA pClient, bool fOnlyAutoMounts, PSHFLMAPPING pMappings, uint32_t *pcMappings); +int vbsfMappingsQueryName(PSHFLCLIENTDATA pClient, SHFLROOT root, SHFLSTRING *pString); +int vbsfMappingsQueryWritable(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fWritable); +int vbsfMappingsQueryAutoMount(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fAutoMount); +int vbsfMappingsQuerySymlinksCreate(PSHFLCLIENTDATA pClient, SHFLROOT root, bool *fSymlinksCreate); +int vbsfMappingsQueryInfo(PSHFLCLIENTDATA pClient, SHFLROOT root, PSHFLSTRING pNameBuf, PSHFLSTRING pMntPtBuf, + uint64_t *pfFlags, uint32_t *puVersion); + +int vbsfMapFolder(PSHFLCLIENTDATA pClient, PSHFLSTRING pszMapName, RTUTF16 delimiter, + bool fCaseSensitive, SHFLROOT *pRoot); +int vbsfUnmapFolder(PSHFLCLIENTDATA pClient, SHFLROOT root); + +int vbsfMappingsWaitForChanges(PSHFLCLIENTDATA pClient, VBOXHGCMCALLHANDLE hCall, PVBOXHGCMSVCPARM pParm, bool fRestored); +int vbsfMappingsCancelChangesWaits(PSHFLCLIENTDATA pClient); + +const char* vbsfMappingsQueryHostRoot(SHFLROOT root); +int vbsfMappingsQueryHostRootEx(SHFLROOT hRoot, const char **ppszRoot, uint32_t *pcbRootLen); +bool vbsfIsGuestMappingCaseSensitive(SHFLROOT root); +bool vbsfIsHostMappingCaseSensitive(SHFLROOT root); + +void vbsfMappingLoadingStart(void); +int vbsfMappingLoaded(MAPPING const *pLoadedMapping, SHFLROOT root); +void vbsfMappingLoadingDone(void); +PMAPPING vbsfMappingGetByRoot(SHFLROOT root); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_mappings_h */ + diff --git a/src/VBox/HostServices/SharedFolders/shfl.h b/src/VBox/HostServices/SharedFolders/shfl.h new file mode 100644 index 00000000..04f83eb4 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shfl.h @@ -0,0 +1,89 @@ +/** @file + * Shared Folders: Main header - Common data and function prototypes definitions. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_shfl_h +#define VBOX_INCLUDED_SRC_SharedFolders_shfl_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include + +#include + +/** Shared Folders client flags. + * @{ + */ +/** Client has queried mappings at least once and, therefore, the service can + * process its other requests too. */ +#define SHFL_CF_MAPPINGS_QUERIED (0x00000001) +/** Mappings have been changed since last query. */ +#define SHFL_CF_MAPPINGS_CHANGED (0x00000002) +/** Client uses UTF8 encoding, if not set then unicode 16 bit (UCS2) is used. */ +#define SHFL_CF_UTF8 (0x00000004) +/** Client both supports and wants to use symlinks. */ +#define SHFL_CF_SYMLINKS (0x00000008) +/** The call to SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES will return immediately + * because of a SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS call. */ +#define SHFL_CF_CANCEL_NEXT_WAIT (0x00000010) +/** @} */ + +/** + * @note This structure is dumped directly into the saved state, so care must be + * taken when extending it! + */ +typedef struct SHFLCLIENTDATA +{ + /** Client flags */ + uint32_t fu32Flags; + /** Path delimiter. */ + RTUTF16 PathDelimiter; + /** The error style, SHFLERRORSTYLE. */ + uint8_t enmErrorStyle; + /** Set if the client has mapping usage counts. + * This is for helping with saved state. */ + uint8_t fHasMappingCounts; + /** Mapping counts for each root ID so we can unmap the folders when the + * session disconnects or the VM resets. */ + uint16_t acMappings[SHFL_MAX_MAPPINGS]; +} SHFLCLIENTDATA; +/** Pointer to a SHFLCLIENTDATA structure. */ +typedef SHFLCLIENTDATA *PSHFLCLIENTDATA; + + +/** @def SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX + * Whether to make windows error style adjustments on a posix host. + * This always returns false on windows hosts. */ +#ifdef RT_OS_WINDOWS +# define SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(a_pClient) (false) +#else +# define SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(a_pClient) ((a_pClient)->enmErrorStyle == kShflErrorStyle_Windows) +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_shfl_h */ + diff --git a/src/VBox/HostServices/SharedFolders/shflhandle.cpp b/src/VBox/HostServices/SharedFolders/shflhandle.cpp new file mode 100644 index 00000000..abf734dc --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shflhandle.cpp @@ -0,0 +1,236 @@ +/* $Id: shflhandle.cpp $ */ +/** @file + * Shared Folders Service - Handles helper functions. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include "shflhandle.h" +#include +#include +#include + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Very basic and primitive handle management. Should be sufficient for our needs. + * Handle allocation can be rather slow, but at least lookup is fast. + */ +typedef struct +{ + uint32_t uFlags; + uintptr_t pvUserData; + PSHFLCLIENTDATA pClient; +} SHFLINTHANDLE, *PSHFLINTHANDLE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static SHFLINTHANDLE *g_pHandles = NULL; +static int32_t gLastHandleIndex = 0; +static RTCRITSECT gLock; + + +int vbsfInitHandleTable() +{ + g_pHandles = (SHFLINTHANDLE *)RTMemAllocZ (sizeof (SHFLINTHANDLE) * SHFLHANDLE_MAX); + if (!g_pHandles) + { + AssertFailed(); + return VERR_NO_MEMORY; + } + + /* Never return handle 0 */ + g_pHandles[0].uFlags = SHFL_HF_TYPE_DONTUSE; + gLastHandleIndex = 1; + + return RTCritSectInit(&gLock); +} + +int vbsfFreeHandleTable() +{ + if (g_pHandles) + RTMemFree(g_pHandles); + + g_pHandles = NULL; + + if (RTCritSectIsInitialized(&gLock)) + RTCritSectDelete(&gLock); + + return VINF_SUCCESS; +} + +SHFLHANDLE vbsfAllocHandle(PSHFLCLIENTDATA pClient, uint32_t uType, + uintptr_t pvUserData) +{ + SHFLHANDLE handle; + + Assert((uType & SHFL_HF_TYPE_MASK) != 0 && pvUserData); + + RTCritSectEnter(&gLock); + + /* Find next free handle */ + if (gLastHandleIndex >= SHFLHANDLE_MAX-1) + gLastHandleIndex = 1; + + /* Nice linear search */ + for(handle=gLastHandleIndex;handleHeader.u32Flags = SHFL_HF_TYPE_DIR; + return vbsfAllocHandle(pClient, pHandle->Header.u32Flags, + (uintptr_t)pHandle); + } + + return SHFL_HANDLE_NIL; +} + +SHFLHANDLE vbsfAllocFileHandle(PSHFLCLIENTDATA pClient) +{ + SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)RTMemAllocZ (sizeof (SHFLFILEHANDLE)); + + if (pHandle) + { + pHandle->Header.u32Flags = SHFL_HF_TYPE_FILE; + return vbsfAllocHandle(pClient, pHandle->Header.u32Flags, + (uintptr_t)pHandle); + } + + return SHFL_HANDLE_NIL; +} + +void vbsfFreeFileHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE hHandle) +{ + SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(pClient, + hHandle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE); + + if (pHandle) + { + vbsfFreeHandle(pClient, hHandle); + RTMemFree (pHandle); + } + else + AssertFailed(); +} + diff --git a/src/VBox/HostServices/SharedFolders/shflhandle.h b/src/VBox/HostServices/SharedFolders/shflhandle.h new file mode 100644 index 00000000..c4a50fbf --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/shflhandle.h @@ -0,0 +1,91 @@ +/* $Id: shflhandle.h $ */ +/** @file + * Shared Folders Host Service - Handles helper functions header. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h +#define VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include +#include + +#define SHFL_HF_TYPE_MASK (0x000000FF) +#define SHFL_HF_TYPE_DIR (0x00000001) +#define SHFL_HF_TYPE_FILE (0x00000002) +#define SHFL_HF_TYPE_VOLUME (0x00000004) +#define SHFL_HF_TYPE_DONTUSE (0x00000080) + +#define SHFL_HF_VALID (0x80000000) + +#define SHFLHANDLE_MAX (4096) + +typedef struct _SHFLHANDLEHDR +{ + uint32_t u32Flags; +} SHFLHANDLEHDR; + +#define ShflHandleType(__Handle) BIT_FLAG(((SHFLHANDLEHDR *)(__Handle))->u32Flags, SHFL_HF_TYPE_MASK) + +typedef struct _SHFLFILEHANDLE +{ + SHFLHANDLEHDR Header; + SHFLROOT root; /* Where the handle has been opened. */ + union + { + struct + { + RTFILE Handle; + uint64_t fOpenFlags; /**< RTFILE_O_XXX. */ + } file; + struct + { + RTDIR Handle; + RTDIR SearchHandle; + PRTDIRENTRYEX pLastValidEntry; /**< last found file in a directory search */ + } dir; + }; +} SHFLFILEHANDLE; + + +SHFLHANDLE vbsfAllocDirHandle(PSHFLCLIENTDATA pClient); +SHFLHANDLE vbsfAllocFileHandle(PSHFLCLIENTDATA pClient); +void vbsfFreeFileHandle (PSHFLCLIENTDATA pClient, SHFLHANDLE hHandle); + + +int vbsfInitHandleTable(); +int vbsfFreeHandleTable(); +SHFLHANDLE vbsfAllocHandle(PSHFLCLIENTDATA pClient, uint32_t uType, + uintptr_t pvUserData); +SHFLFILEHANDLE *vbsfQueryFileHandle(PSHFLCLIENTDATA pClient, + SHFLHANDLE handle); +SHFLFILEHANDLE *vbsfQueryDirHandle(PSHFLCLIENTDATA pClient, SHFLHANDLE handle); +uint32_t vbsfQueryHandleType(PSHFLCLIENTDATA pClient, + SHFLHANDLE handle); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_shflhandle_h */ diff --git a/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk b/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk new file mode 100644 index 00000000..c451d7a1 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/Makefile.kmk @@ -0,0 +1,104 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Folders Host Service testcases. +# + +# +# 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 . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Structure size testcase. +# +PROGRAMS += tstShflSizes +TESTING += $(tstShflSizes_0_OUTDIR)/tstShflSizes.run +ifndef VBOX_ONLY_SDK + ifeq ($(KBUILD_TARGET),$(KBUILD_HOST)) + if1of ($(KBUILD_TARGET_ARCH).$(KBUILD_HOST_ARCH), x86.x86 amd64.amd64 x86.amd64) + OTHERS += $(tstShflSizes_0_OUTDIR)/tstShflSizes.run + endif + endif +endif +tstShflSizes_TEMPLATE = VBoxR3AutoTest +tstShflSizes_DEFS = VBOX_WITH_HGCM +tstShflSizes_SOURCES = tstShflSizes.cpp +tstShflSizes_CLEAN = $(tstShflSizes_0_OUTDIR)/tstShflSizes.run + +$$(tstShflSizes_0_OUTDIR)/tstShflSizes.run: $$(tstShflSizes_1_STAGE_TARGET) + $(tstShflSizes_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + +ifdef VBOX_WITH_TESTCASES + # + # Case conversion testcase. + # + PROGRAMS += tstShflCase + tstShflCase_TEMPLATE = VBoxR3TstExe + tstShflCase_DEFS = VBOX_WITH_HGCM + tstShflCase_SOURCES = tstShflCase.cpp + tstShflCase_LIBS = $(LIB_RUNTIME) + + # + # HGCM service testcase. + # + + PROGRAMS += tstSharedFolderService + tstSharedFolderService_TEMPLATE = VBoxR3TstExe + tstSharedFolderService_DEFS = VBOX_WITH_HGCM UNITTEST + tstSharedFolderService_INCS = .. + tstSharedFolderService_SOURCES = \ + tstSharedFolderService.cpp \ + ../mappings.cpp \ + ../VBoxSharedFoldersSvc.cpp \ + ../shflhandle.cpp \ + ../vbsfpathabs.cpp \ + ../vbsfpath.cpp \ + ../vbsf.cpp + tstSharedFolderService_LDFLAGS.darwin = \ + -framework Carbon + tstSharedFolderService_LIBS = $(LIB_RUNTIME) + + if 0 # Cannot define two RT_OS_XXX macros! + # As there are differences between the Windows build of the service and others, + # we do an additional build with RT_OS_WINDOWS defined on non-Windows targets. + PROGRAMS += \ + tstSharedFolderService \ + $(if $(eq $(KBUILD_TARGET),win),,tstSharedFolderService-win) + tstSharedFolderService-win_TEMPLATE = $(tstSharedFolderService_TEMPLATE) + tstSharedFolderService-win_DEFS = \ + $(tstSharedFolderService_DEFS) \ + RT_OS_WINDOWS + tstSharedFolderService-win_INCS = $(tstSharedFolderService_INCS) + tstSharedFolderService-win_SOURCES = $(tstSharedFolderService_SOURCES) + tstSharedFolderService-win_LDFLAGS.darwin = \ + $(tstSharedFolderService_LDFLAGS.darwin) + tstSharedFolderService-win_LIBS = $(tstSharedFolderService_LIBS) + endif + +endif # VBOX_WITH_TESTCASES + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp new file mode 100644 index 00000000..3f105284 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp @@ -0,0 +1,1368 @@ +/* $Id: tstSharedFolderService.cpp $ */ +/** @file + * Testcase for the shared folder service vbsf API. + * + * Note that this is still very threadbare (there is an awful lot which should + * really be tested, but it already took too long to produce this much). The + * idea is that anyone who makes changes to the shared folders service and who + * cares about unit testing them should add tests to the skeleton framework to + * exercise the bits they change before and after changing them. + */ + +/* + * Copyright (C) 2011-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#include "tstSharedFolderService.h" +#include "vbsf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "teststubs.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + + +/********************************************************************************************************************************* +* Declarations * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); + + +/********************************************************************************************************************************* +* Helpers * +*********************************************************************************************************************************/ + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) stamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, + STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va) +{ + RT_NOREF(pvInstance, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, va); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) stamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va) +{ + RT_NOREF(pvInstance, pszPatFmt, va); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) infoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser) +{ + RT_NOREF(pvInstance, pszName, pszDesc, pfnHandler, pvUser); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) infoDeregister(void *pvInstance, const char *pszName) +{ + RT_NOREF(pvInstance, pszName); + return VINF_SUCCESS; +} + +/** + * Initialise the HGCM service table as much as we need to start the + * service + * @param pTable the table to initialise + */ +void initTable(VBOXHGCMSVCFNTABLE *pTable, VBOXHGCMSVCHELPERS *pHelpers) +{ + pTable->cbSize = sizeof (VBOXHGCMSVCFNTABLE); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + pHelpers->pfnCallComplete = callComplete; + pHelpers->pfnStamRegisterV = stamRegisterV; + pHelpers->pfnStamDeregisterV = stamDeregisterV; + pHelpers->pfnInfoRegister = infoRegister; + pHelpers->pfnInfoDeregister = infoDeregister; + pTable->pHelpers = pHelpers; +} + +#define LLUIFY(a) ((unsigned long long)(a)) + +static void bufferFromPath(char *pszDst, size_t cbDst, const char *pcszSrc) +{ + RTStrCopy(pszDst, cbDst, pcszSrc); + uintptr_t const uDstEnd = (uintptr_t)&pszDst[cbDst]; + for (char *psz = pszDst; psz && (uintptr_t)psz < uDstEnd; ++psz) + if (*psz == '\\') + *psz = '/'; +} + +#define ARRAY_FROM_PATH(a, b) \ + do { \ + char *p = (a); NOREF(p); \ + Assert((a) == p); /* Constant parameter */ \ + Assert(sizeof((a)) > 0); \ + bufferFromPath(a, sizeof(a), b); \ + } while (0) + + +/********************************************************************************************************************************* +* Stub functions and data * +*********************************************************************************************************************************/ +static bool g_fFailIfNotLowercase = false; + +static RTDIR g_testRTDirClose_hDir = NIL_RTDIR; + +extern int testRTDirClose(RTDIR hDir) +{ + /* RTPrintf("%s: hDir=%p\n", __PRETTY_FUNCTION__, hDir); */ + g_testRTDirClose_hDir = hDir; + return VINF_SUCCESS; +} + +static char g_testRTDirCreate_szPath[256]; +//static RTFMODE testRTDirCreateMode; - unused + +extern int testRTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate) +{ + RT_NOREF2(fMode, fCreate); + /* RTPrintf("%s: pszPath=%s, fMode=0x%llx\n", __PRETTY_FUNCTION__, pszPath, + LLUIFY(fMode)); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirCreate_szPath, pszPath); + return 0; +} + +static char g_testRTDirOpen_szName[256]; +static struct TESTDIRHANDLE +{ + int iEntry; + int iDir; +} g_aTestDirHandles[4]; +static int g_iNextDirHandle = 0; +static RTDIR g_testRTDirOpen_hDir; + +extern int testRTDirOpen(RTDIR *phDir, const char *pszPath) +{ + /* RTPrintf("%s: pszPath=%s\n", __PRETTY_FUNCTION__, pszPath); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirOpen_szName, pszPath); + *phDir = g_testRTDirOpen_hDir; + g_testRTDirOpen_hDir = NIL_RTDIR; + if (!*phDir && g_fFailIfNotLowercase) + *phDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + if (*phDir) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)*phDir; + pRealDir->iEntry = 0; + pRealDir->iDir = 0; + const char *pszSlash = pszPath - 1; + while ((pszSlash = strpbrk(pszSlash + 1, "\\/")) != NULL) + pRealDir->iDir += 1; + /*RTPrintf("opendir %s = %d \n", pszPath, pRealDir->iDir);*/ + } + return VINF_SUCCESS; +} + +/** @todo Do something useful with the last two arguments. */ +extern int testRTDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER, uint32_t) +{ + /* RTPrintf("%s: pszPath=%s\n", __PRETTY_FUNCTION__, pszPath); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + ARRAY_FROM_PATH(g_testRTDirOpen_szName, pszPath); + *phDir = g_testRTDirOpen_hDir; + g_testRTDirOpen_hDir = NIL_RTDIR; + if (!*phDir && g_fFailIfNotLowercase) + *phDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + if (*phDir) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)*phDir; + pRealDir->iEntry = 0; + pRealDir->iDir = 0; + const char *pszSlash = pszPath - 1; + while ((pszSlash = strpbrk(pszSlash + 1, "\\/")) != NULL) + pRealDir->iDir += 1; + pRealDir->iDir -= 1; + /*RTPrintf("openfiltered %s = %d\n", pszPath, pRealDir->iDir);*/ + } + return VINF_SUCCESS; +} + +static RTDIR g_testRTDirQueryInfo_hDir; +static RTTIMESPEC g_testRTDirQueryInfo_ATime; + +extern int testRTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF1(enmAdditionalAttribs); + /* RTPrintf("%s: hDir=%p, enmAdditionalAttribs=0x%llx\n", __PRETTY_FUNCTION__, + hDir, LLUIFY(enmAdditionalAttribs)); */ + g_testRTDirQueryInfo_hDir = hDir; + RT_ZERO(*pObjInfo); + pObjInfo->AccessTime = g_testRTDirQueryInfo_ATime; + RT_ZERO(g_testRTDirQueryInfo_ATime); + return VINF_SUCCESS; +} + +extern int testRTDirRemove(const char *pszPath) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +static RTDIR g_testRTDirReadEx_hDir; + +extern int testRTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF4(pDirEntry, pcbDirEntry, enmAdditionalAttribs, fFlags); + /* RTPrintf("%s: hDir=%p, pcbDirEntry=%d, enmAdditionalAttribs=%llu, fFlags=0x%llx\n", + __PRETTY_FUNCTION__, hDir, pcbDirEntry ? (int) *pcbDirEntry : -1, + LLUIFY(enmAdditionalAttribs), LLUIFY(fFlags)); */ + g_testRTDirReadEx_hDir = hDir; + if (g_fFailIfNotLowercase && hDir != NIL_RTDIR) + { + struct TESTDIRHANDLE *pRealDir = (struct TESTDIRHANDLE *)hDir; + if (pRealDir->iDir == 2) /* /test/mapping/ */ + { + if (pRealDir->iEntry == 0) + { + pRealDir->iEntry++; + RT_ZERO(*pDirEntry); + pDirEntry->Info.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH; + pDirEntry->cbName = 4; + pDirEntry->cwcShortName = 4; + strcpy(pDirEntry->szName, "test"); + RTUtf16CopyAscii(pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName), "test"); + /*RTPrintf("readdir: 'test'\n");*/ + return VINF_SUCCESS; + } + } + else if (pRealDir->iDir == 3) /* /test/mapping/test/ */ + { + if (pRealDir->iEntry == 0) + { + pRealDir->iEntry++; + RT_ZERO(*pDirEntry); + pDirEntry->Info.Attr.fMode = RTFS_TYPE_FILE | RTFS_DOS_NT_NORMAL | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH; + pDirEntry->cbName = 4; + pDirEntry->cwcShortName = 4; + strcpy(pDirEntry->szName, "file"); + RTUtf16CopyAscii(pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName), "file"); + /*RTPrintf("readdir: 'file'\n");*/ + return VINF_SUCCESS; + } + } + /*else RTPrintf("%s: iDir=%d\n", pRealDir->iDir);*/ + } + return VERR_NO_MORE_FILES; +} + +static uint64_t g_testRTDirSetMode_fMode; + +extern int testRTDirSetMode(RTDIR hDir, RTFMODE fMode) +{ + RT_NOREF1(hDir); + /* RTPrintf("%s: fMode=%llu\n", __PRETTY_FUNCTION__, LLUIFY(fMode)); */ + g_testRTDirSetMode_fMode = fMode; + return VINF_SUCCESS; +} + +static RTTIMESPEC g_testRTDirSetTimes_ATime; + +extern int testRTDirSetTimes(RTDIR hDir, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RT_NOREF4(hDir, pModificationTime, pChangeTime, pBirthTime); + /* RTPrintf("%s: hDir=%p, *pAccessTime=%lli, *pModificationTime=%lli, *pChangeTime=%lli, *pBirthTime=%lli\n", + __PRETTY_FUNCTION__, hDir, + pAccessTime ? (long long)RTTimeSpecGetNano(pAccessTime) : -1, + pModificationTime + ? (long long)RTTimeSpecGetNano(pModificationTime) : -1, + pChangeTime ? (long long)RTTimeSpecGetNano(pChangeTime) : -1, + pBirthTime ? (long long)RTTimeSpecGetNano(pBirthTime) : -1); */ + if (pAccessTime) + g_testRTDirSetTimes_ATime = *pAccessTime; + else + RT_ZERO(g_testRTDirSetTimes_ATime); + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileClose_hFile; + +extern int testRTFileClose(RTFILE File) +{ + /* RTPrintf("%s: File=%p\n", __PRETTY_FUNCTION__, File); */ + g_testRTFileClose_hFile = File; + return 0; +} + +extern int testRTFileDelete(const char *pszFilename) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszFilename, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +static RTFILE g_testRTFileFlush_hFile; + +extern int testRTFileFlush(RTFILE File) +{ + /* RTPrintf("%s: File=%p\n", __PRETTY_FUNCTION__, File); */ + g_testRTFileFlush_hFile = File; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileLock_hFile; +static unsigned g_testRTFileLock_fLock; +static int64_t g_testRTFileLock_offLock; +static uint64_t g_testRTFileLock_cbLock; + +extern int testRTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + /* RTPrintf("%s: hFile=%p, fLock=%u, offLock=%lli, cbLock=%llu\n", + __PRETTY_FUNCTION__, hFile, fLock, (long long) offLock, + LLUIFY(cbLock)); */ + g_testRTFileLock_hFile = hFile; + g_testRTFileLock_fLock = fLock; + g_testRTFileLock_offLock = offLock; + g_testRTFileLock_cbLock = cbLock; + return VINF_SUCCESS; +} + +static char g_testRTFileOpen_szName[256]; +static uint64_t g_testRTFileOpen_fOpen; +static RTFILE g_testRTFileOpen_hFile; + +extern int testRTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken) +{ + /* RTPrintf("%s, pszFilename=%s, fOpen=0x%llx\n", __PRETTY_FUNCTION__, + pszFilename, LLUIFY(fOpen)); */ + ARRAY_FROM_PATH(g_testRTFileOpen_szName, pszFilename); + g_testRTFileOpen_fOpen = fOpen; + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszFilename, "/\\"))) + return VERR_FILE_NOT_FOUND; + *phFile = g_testRTFileOpen_hFile; + *penmActionTaken = RTFILEACTION_CREATED; + g_testRTFileOpen_hFile = 0; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileQueryInfo_hFile; +static RTTIMESPEC g_testRTFileQueryInfo_ATime; +static uint32_t g_testRTFileQueryInfo_fMode; + +extern int testRTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF1(enmAdditionalAttribs); + /* RTPrintf("%s, hFile=%p, enmAdditionalAttribs=0x%llx\n", + __PRETTY_FUNCTION__, hFile, LLUIFY(enmAdditionalAttribs)); */ + g_testRTFileQueryInfo_hFile = hFile; + RT_ZERO(*pObjInfo); + pObjInfo->AccessTime = g_testRTFileQueryInfo_ATime; + RT_ZERO(g_testRTDirQueryInfo_ATime); + pObjInfo->Attr.fMode = g_testRTFileQueryInfo_fMode; + g_testRTFileQueryInfo_fMode = 0; + return VINF_SUCCESS; +} + +static const char *g_testRTFileRead_pszData; + +extern int testRTFileRead(RTFILE File, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RT_NOREF1(File); + /* RTPrintf("%s : File=%p, cbToRead=%llu\n", __PRETTY_FUNCTION__, File, + LLUIFY(cbToRead)); */ + bufferFromPath((char *)pvBuf, cbToRead, g_testRTFileRead_pszData); + if (pcbRead) + *pcbRead = RT_MIN(cbToRead, strlen(g_testRTFileRead_pszData) + 1); + g_testRTFileRead_pszData = 0; + return VINF_SUCCESS; +} + +extern int testRTFileReadAt(RTFILE hFile, uint64_t offFile, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RT_NOREF1(hFile); + RT_NOREF(offFile); + /* RTPrintf("%s : File=%p, cbToRead=%llu\n", __PRETTY_FUNCTION__, File, + LLUIFY(cbToRead)); */ + bufferFromPath((char *)pvBuf, cbToRead, g_testRTFileRead_pszData); + if (pcbRead) + *pcbRead = RT_MIN(cbToRead, strlen(g_testRTFileRead_pszData) + 1); + g_testRTFileRead_pszData = 0; + return VINF_SUCCESS; +} + +extern int testRTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) +{ + RT_NOREF3(hFile, offSeek, uMethod); + /* RTPrintf("%s : hFile=%p, offSeek=%llu, uMethod=%u\n", __PRETTY_FUNCTION__, + hFile, LLUIFY(offSeek), uMethod); */ + if (poffActual) + *poffActual = 0; + return VINF_SUCCESS; +} + +static uint64_t g_testRTFileSet_fMode; + +extern int testRTFileSetMode(RTFILE File, RTFMODE fMode) +{ + RT_NOREF1(File); + /* RTPrintf("%s: fMode=%llu\n", __PRETTY_FUNCTION__, LLUIFY(fMode)); */ + g_testRTFileSet_fMode = fMode; + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileSetSize_hFile; +static RTFOFF g_testRTFileSetSize_cbSize; + +extern int testRTFileSetSize(RTFILE File, uint64_t cbSize) +{ + /* RTPrintf("%s: File=%llu, cbSize=%llu\n", __PRETTY_FUNCTION__, LLUIFY(File), + LLUIFY(cbSize)); */ + g_testRTFileSetSize_hFile = File; + g_testRTFileSetSize_cbSize = (RTFOFF) cbSize; /* Why was this signed before? */ + return VINF_SUCCESS; +} + +static RTTIMESPEC g_testRTFileSetTimes_ATime; + +extern int testRTFileSetTimes(RTFILE File, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RT_NOREF4(File, pModificationTime, pChangeTime, pBirthTime); + /* RTPrintf("%s: pFile=%p, *pAccessTime=%lli, *pModificationTime=%lli, *pChangeTime=%lli, *pBirthTime=%lli\n", + __PRETTY_FUNCTION__, + pAccessTime ? (long long)RTTimeSpecGetNano(pAccessTime) : -1, + pModificationTime + ? (long long)RTTimeSpecGetNano(pModificationTime) : -1, + pChangeTime ? (long long)RTTimeSpecGetNano(pChangeTime) : -1, + pBirthTime ? (long long)RTTimeSpecGetNano(pBirthTime) : -1); */ + if (pAccessTime) + g_testRTFileSetTimes_ATime = *pAccessTime; + else + RT_ZERO(g_testRTFileSetTimes_ATime); + return VINF_SUCCESS; +} + +static RTFILE g_testRTFileUnlock_hFile; +static int64_t g_testRTFileUnlock_offLock; +static uint64_t g_testRTFileUnlock_cbLock; + +extern int testRTFileUnlock(RTFILE File, int64_t offLock, uint64_t cbLock) +{ + /* RTPrintf("%s: hFile=%p, ofLock=%lli, cbLock=%llu\n", __PRETTY_FUNCTION__, + File, (long long) offLock, LLUIFY(cbLock)); */ + g_testRTFileUnlock_hFile = File; + g_testRTFileUnlock_offLock = offLock; + g_testRTFileUnlock_cbLock = cbLock; + return VINF_SUCCESS; +} + +static char g_testRTFileWrite_szData[256]; + +extern int testRTFileWrite(RTFILE File, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RT_NOREF2(File, cbToWrite); + /* RTPrintf("%s: File=%p, pvBuf=%.*s, cbToWrite=%llu\n", __PRETTY_FUNCTION__, + File, cbToWrite, (const char *)pvBuf, LLUIFY(cbToWrite)); */ + ARRAY_FROM_PATH(g_testRTFileWrite_szData, (const char *)pvBuf); + if (pcbWritten) + *pcbWritten = strlen(g_testRTFileWrite_szData) + 1; + return VINF_SUCCESS; +} + +extern int testRTFileWriteAt(RTFILE File, uint64_t offFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RT_NOREF3(File, cbToWrite, offFile); + /* RTPrintf("%s: File=%p, pvBuf=%.*s, cbToWrite=%llu\n", __PRETTY_FUNCTION__, + File, cbToWrite, (const char *)pvBuf, LLUIFY(cbToWrite)); */ + ARRAY_FROM_PATH(g_testRTFileWrite_szData, (const char *)pvBuf); + if (pcbWritten) + *pcbWritten = strlen(g_testRTFileWrite_szData) + 1; + return VINF_SUCCESS; +} + +extern int testRTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + RT_NOREF1(pszFsPath); + /* RTPrintf("%s, pszFsPath=%s\n", __PRETTY_FUNCTION__, pszFsPath); + RT_ZERO(*pProperties); */ + pProperties->cbMaxComponent = 256; + pProperties->fCaseSensitive = true; + return VINF_SUCCESS; +} + +extern int testRTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + RT_NOREF2(pszFsPath, pu32Serial); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} +extern int testRTFsQuerySizes(const char *pszFsPath, PRTFOFF pcbTotal, RTFOFF *pcbFree, uint32_t *pcbBlock, uint32_t *pcbSector) +{ + RT_NOREF5(pszFsPath, pcbTotal, pcbFree, pcbBlock, pcbSector); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +extern int testRTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF2(enmAdditionalAttribs, fFlags); + /* RTPrintf("%s: pszPath=%s, enmAdditionalAttribs=0x%x, fFlags=0x%x\n", + __PRETTY_FUNCTION__, pszPath, (unsigned) enmAdditionalAttribs, + (unsigned) fFlags); */ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszPath, "/\\"))) + return VERR_FILE_NOT_FOUND; + RT_ZERO(*pObjInfo); + return VINF_SUCCESS; +} + +extern int testRTSymlinkDelete(const char *pszSymlink, uint32_t fDelete) +{ + RT_NOREF2(pszSymlink, fDelete); + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszSymlink, "/\\"))) + return VERR_FILE_NOT_FOUND; + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + +extern int testRTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + if (g_fFailIfNotLowercase && !RTStrIsLowerCased(strpbrk(pszSymlink, "/\\"))) + return VERR_FILE_NOT_FOUND; + RT_NOREF4(pszSymlink, pszTarget, cbTarget, fRead); + RTPrintf("%s\n", __PRETTY_FUNCTION__); + return 0; +} + + +/********************************************************************************************************************************* +* Tests * +*********************************************************************************************************************************/ + +/* Sub-tests for testMappingsQuery(). */ +void testMappingsQuerySimple(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryTooFewBuffers(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryAutoMount(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryArrayWrongSize(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsQueryName(). */ +void testMappingsQueryNameValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryNameInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMappingsQueryNameBadBuffer(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMapFolder(). */ +void testMapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderTwice(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderDelimiter(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderCaseSensitive(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderCaseInsensitive(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testUnmapFolder(). */ +void testUnmapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } +void testUnmapFolderInvalid(RTTEST hTest) { RT_NOREF1(hTest); } +void testUnmapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testCreate(). */ +void testCreateBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testClose(). */ +void testCloseBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRead(). */ +void testReadBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testWrite(). */ +void testWriteBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testLock(). */ +void testLockBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testFlush(). */ +void testFlushBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testDirList(). */ +void testDirListBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testReadLink(). */ +void testReadLinkBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testFSInfo(). */ +void testFSInfoBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRemove(). */ +void testRemoveBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testRename(). */ +void testRenameBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testSymlink(). */ +void testSymlinkBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsAdd(). */ +void testMappingsAddBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +/* Sub-tests for testMappingsRemove(). */ +void testMappingsRemoveBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } + +union TESTSHFLSTRING +{ + SHFLSTRING string; + char acData[256]; +}; + +static void fillTestShflString(union TESTSHFLSTRING *pDest, + const char *pcszSource) +{ + const size_t cchSource = strlen(pcszSource); + AssertRelease( cchSource * 2 + 2 + < sizeof(*pDest) - RT_UOFFSETOF(SHFLSTRING, String)); + pDest->string.u16Length = (uint16_t)(cchSource * sizeof(RTUTF16)); + pDest->string.u16Size = pDest->string.u16Length + sizeof(RTUTF16); + /* Copy pcszSource ASCIIZ, including the trailing 0, to the UTF16 pDest->string.String.ucs2. */ + for (unsigned i = 0; i <= cchSource; ++i) + pDest->string.String.ucs2[i] = (uint16_t)pcszSource[i]; +} + +static SHFLROOT initWithWritableMapping(RTTEST hTest, + VBOXHGCMSVCFNTABLE *psvcTable, + VBOXHGCMSVCHELPERS *psvcHelpers, + const char *pcszFolderName, + const char *pcszMapping, + bool fCaseSensitive = true) +{ + VBOXHGCMSVCPARM aParms[RT_MAX(SHFL_CPARMS_ADD_MAPPING, + SHFL_CPARMS_MAP_FOLDER)]; + union TESTSHFLSTRING FolderName; + union TESTSHFLSTRING Mapping; + union TESTSHFLSTRING AutoMountPoint; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + int rc; + + initTable(psvcTable, psvcHelpers); + AssertReleaseRC(VBoxHGCMSvcLoad(psvcTable)); + AssertRelease( psvcTable->pvService + = RTTestGuardedAllocTail(hTest, psvcTable->cbClient)); + RT_BZERO(psvcTable->pvService, psvcTable->cbClient); + fillTestShflString(&FolderName, pcszFolderName); + fillTestShflString(&Mapping, pcszMapping); + fillTestShflString(&AutoMountPoint, ""); + HGCMSvcSetPv(&aParms[0], &FolderName, RT_UOFFSETOF(SHFLSTRING, String) + + FolderName.string.u16Size); + HGCMSvcSetPv(&aParms[1], &Mapping, RT_UOFFSETOF(SHFLSTRING, String) + + Mapping.string.u16Size); + HGCMSvcSetU32(&aParms[2], 1); + HGCMSvcSetPv(&aParms[3], &AutoMountPoint, SHFLSTRING_HEADER_SIZE + AutoMountPoint.string.u16Size); + rc = psvcTable->pfnHostCall(psvcTable->pvService, SHFL_FN_ADD_MAPPING, + SHFL_CPARMS_ADD_MAPPING, aParms); + AssertReleaseRC(rc); + HGCMSvcSetPv(&aParms[0], &Mapping, RT_UOFFSETOF(SHFLSTRING, String) + + Mapping.string.u16Size); + HGCMSvcSetU32(&aParms[1], 0); /* root */ + HGCMSvcSetU32(&aParms[2], '/'); /* delimiter */ + HGCMSvcSetU32(&aParms[3], fCaseSensitive); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + AssertReleaseRC(callHandle.rc); + return aParms[1].u.uint32; +} + +/** @todo Mappings should be automatically removed by unloading the service, + * but unloading is currently a no-op! */ +static void unmapAndRemoveMapping(RTTEST hTest, VBOXHGCMSVCFNTABLE *psvcTable, + SHFLROOT root, const char *pcszFolderName) +{ + RT_NOREF1(hTest); + VBOXHGCMSVCPARM aParms[RT_MAX(SHFL_CPARMS_UNMAP_FOLDER, + SHFL_CPARMS_REMOVE_MAPPING)]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + union TESTSHFLSTRING FolderName; + int rc; + + HGCMSvcSetU32(&aParms[0], root); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_UNMAP_FOLDER, + SHFL_CPARMS_UNMAP_FOLDER, aParms, 0); + AssertReleaseRC(callHandle.rc); + fillTestShflString(&FolderName, pcszFolderName); + HGCMSvcSetPv(&aParms[0], &FolderName, RT_UOFFSETOF(SHFLSTRING, String) + + FolderName.string.u16Size); + rc = psvcTable->pfnHostCall(psvcTable->pvService, SHFL_FN_REMOVE_MAPPING, + SHFL_CPARMS_REMOVE_MAPPING, aParms); + AssertReleaseRC(rc); +} + +static int createFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + const char *pcszFilename, uint32_t fCreateFlags, + SHFLHANDLE *pHandle, SHFLCREATERESULT *pResult) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_CREATE]; + union TESTSHFLSTRING Path; + SHFLCREATEPARMS CreateParms; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + fillTestShflString(&Path, pcszFilename); + RT_ZERO(CreateParms); + CreateParms.CreateFlags = fCreateFlags; + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetPv(&aParms[1], &Path, RT_UOFFSETOF(SHFLSTRING, String) + + Path.string.u16Size); + HGCMSvcSetPv(&aParms[2], &CreateParms, sizeof(CreateParms)); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_CREATE, + RT_ELEMENTS(aParms), aParms, 0); + if (RT_FAILURE(callHandle.rc)) + return callHandle.rc; + if (pHandle) + *pHandle = CreateParms.Handle; + if (pResult) + *pResult = CreateParms.Result; + return VINF_SUCCESS; +} + +static int readFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + SHFLHANDLE hFile, uint64_t offSeek, uint32_t cbRead, + uint32_t *pcbRead, void *pvBuf, uint32_t cbBuf) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_READ]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetU64(&aParms[1], (uint64_t) hFile); + HGCMSvcSetU64(&aParms[2], offSeek); + HGCMSvcSetU32(&aParms[3], cbRead); + HGCMSvcSetPv(&aParms[4], pvBuf, cbBuf); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_READ, + RT_ELEMENTS(aParms), aParms, 0); + if (pcbRead) + *pcbRead = aParms[3].u.uint32; + return callHandle.rc; +} + +static int writeFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT Root, + SHFLHANDLE hFile, uint64_t offSeek, uint32_t cbWrite, + uint32_t *pcbWritten, const void *pvBuf, uint32_t cbBuf) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_WRITE]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], Root); + HGCMSvcSetU64(&aParms[1], (uint64_t) hFile); + HGCMSvcSetU64(&aParms[2], offSeek); + HGCMSvcSetU32(&aParms[3], cbWrite); + HGCMSvcSetPv(&aParms[4], (void *)pvBuf, cbBuf); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_WRITE, + RT_ELEMENTS(aParms), aParms, 0); + if (pcbWritten) + *pcbWritten = aParms[3].u.uint32; + return callHandle.rc; +} + +static int flushFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_FLUSH]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_FLUSH, + SHFL_CPARMS_FLUSH, aParms, 0); + return callHandle.rc; +} + +static int listDir(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, uint32_t fFlags, + const char *pcszPath, void *pvBuf, uint32_t cbBuf, + uint32_t resumePoint, uint32_t *pcFiles) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_LIST]; + union TESTSHFLSTRING Path; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU32(&aParms[2], fFlags); + HGCMSvcSetU32(&aParms[3], cbBuf); + if (pcszPath) + { + fillTestShflString(&Path, pcszPath); + HGCMSvcSetPv(&aParms[4], &Path, RT_UOFFSETOF(SHFLSTRING, String) + + Path.string.u16Size); + } + else + HGCMSvcSetPv(&aParms[4], NULL, 0); + HGCMSvcSetPv(&aParms[5], pvBuf, cbBuf); + HGCMSvcSetU32(&aParms[6], resumePoint); + HGCMSvcSetU32(&aParms[7], 0); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_LIST, + RT_ELEMENTS(aParms), aParms, 0); + if (pcFiles) + *pcFiles = aParms[7].u.uint32; + return callHandle.rc; +} + +static int sfInformation(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, uint32_t fFlags, uint32_t cb, + SHFLFSOBJINFO *pInfo) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_INFORMATION]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU32(&aParms[2], fFlags); + HGCMSvcSetU32(&aParms[3], cb); + HGCMSvcSetPv(&aParms[4], pInfo, cb); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_INFORMATION, + RT_ELEMENTS(aParms), aParms, 0); + return callHandle.rc; +} + +static int lockFile(VBOXHGCMSVCFNTABLE *psvcTable, SHFLROOT root, + SHFLHANDLE handle, int64_t offLock, uint64_t cbLock, + uint32_t fFlags) +{ + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_LOCK]; + VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS }; + + HGCMSvcSetU32(&aParms[0], root); + HGCMSvcSetU64(&aParms[1], handle); + HGCMSvcSetU64(&aParms[2], offLock); + HGCMSvcSetU64(&aParms[3], cbLock); + HGCMSvcSetU32(&aParms[4], fFlags); + psvcTable->pfnCall(psvcTable->pvService, &callHandle, 0, + psvcTable->pvService, SHFL_FN_LOCK, + RT_ELEMENTS(aParms), aParms, 0); + return callHandle.rc; +} + +void testCreateFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLCREATERESULT Result; + int rc; + + RTTestSub(hTest, "Create file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, NULL, + &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/file"), + (hTest, "pszFilename=%s\n", &g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, g_testRTFileOpen_fOpen == 0x181, + (hTest, "fOpen=%llu\n", LLUIFY(g_testRTFileOpen_fOpen))); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, + (hTest, "File=%u\n", (uintptr_t)g_testRTFileClose_hFile)); +} + +void testCreateFileSimpleCaseInsensitive(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLCREATERESULT Result; + int rc; + + g_fFailIfNotLowercase = true; + + RTTestSub(hTest, "Create file case insensitive"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname", false /*fCaseSensitive*/); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/TesT/FilE", SHFL_CF_ACCESS_READ, NULL, + &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/file"), + (hTest, "pszFilename=%s\n", &g_testRTFileOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, g_testRTFileOpen_fOpen == 0x181, + (hTest, "fOpen=%llu\n", LLUIFY(g_testRTFileOpen_fOpen))); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, + (hTest, "File=%u\n", (uintptr_t)g_testRTFileClose_hFile)); + + g_fFailIfNotLowercase = false; +} + +void testCreateDirSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + SHFLCREATERESULT Result; + int rc; + + RTTestSub(hTest, "Create directory simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, NULL, &Result); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTDirCreate_szPath[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/dir"), + (hTest, "pszPath=%s\n", &g_testRTDirCreate_szPath[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, + !strcmp(&g_testRTDirOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0], + "/test/mapping/test/dir"), + (hTest, "pszFilename=%s\n", &g_testRTDirOpen_szName[RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS ? 2 : 0])); + RTTEST_CHECK_MSG(hTest, Result == SHFL_FILE_CREATED, + (hTest, "Result=%d\n", (int) Result)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testReadFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + const char *pcszReadData = "Data to read"; + char achBuf[sizeof(pcszReadData) + 10]; + uint32_t cbRead; + int rc; + + RTTestSub(hTest, "Read file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + g_testRTFileRead_pszData = pcszReadData; + memset(achBuf, 'f', sizeof(achBuf)); + rc = readFile(&svcTable, Root, Handle, 0, (uint32_t)strlen(pcszReadData) + 1, + &cbRead, achBuf, (uint32_t)sizeof(achBuf)); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strncmp(achBuf, pcszReadData, sizeof(achBuf)), + (hTest, "pvBuf=%.*s Handle=%#RX64\n", sizeof(achBuf), achBuf, Handle)); + RTTEST_CHECK_MSG(hTest, cbRead == strlen(pcszReadData) + 1, + (hTest, "cbRead=%llu\n", LLUIFY(cbRead))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); +} + +void testWriteFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + const char *pcszWrittenData = "Data to write"; + uint32_t cbToWrite = (uint32_t)strlen(pcszWrittenData) + 1; + uint32_t cbWritten; + int rc; + + RTTestSub(hTest, "Write file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = writeFile(&svcTable, Root, Handle, 0, cbToWrite, &cbWritten, + pcszWrittenData, cbToWrite); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, + !strcmp(g_testRTFileWrite_szData, pcszWrittenData), + (hTest, "pvBuf=%s\n", g_testRTFileWrite_szData)); + RTTEST_CHECK_MSG(hTest, cbWritten == cbToWrite, + (hTest, "cbWritten=%llu\n", LLUIFY(cbWritten))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); +} + +void testFlushFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Flush file simple"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = flushFile(&svcTable, Root, Handle); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileFlush_hFile == hFile, (hTest, "File=%u\n", g_testRTFileFlush_hFile)); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testDirListEmpty(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + SHFLHANDLE Handle; + union + { + SHFLDIRINFO DirInfo; + uint8_t abBuffer[sizeof(SHFLDIRINFO) + 2 * sizeof(RTUTF16)]; + } Buf; + uint32_t cFiles; + int rc; + + RTTestSub(hTest, "List empty directory"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = listDir(&svcTable, Root, Handle, 0, NULL, &Buf.DirInfo, sizeof(Buf), 0, &cFiles); + RTTEST_CHECK_RC(hTest, rc, VERR_NO_MORE_FILES); + RTTEST_CHECK_MSG(hTest, g_testRTDirReadEx_hDir == hDir, (hTest, "Dir=%p\n", g_testRTDirReadEx_hDir)); + RTTEST_CHECK_MSG(hTest, cFiles == 0, + (hTest, "cFiles=%llu\n", LLUIFY(cFiles))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testFSInfoQuerySetFMode(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const uint32_t fMode = 0660; + SHFLFSOBJINFO Info; + int rc; + + RTTestSub(hTest, "Query and set file size"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + SHFLHANDLE Handle = SHFL_HANDLE_NIL; + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK_RETV(hTest, rc); + + RT_ZERO(Info); + g_testRTFileQueryInfo_fMode = fMode; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileQueryInfo_hFile == hFile, (hTest, "File=%u\n", g_testRTFileQueryInfo_hFile)); + RTTEST_CHECK_MSG(hTest, Info.Attr.fMode == fMode, + (hTest, "cbObject=%llu\n", LLUIFY(Info.cbObject))); + RT_ZERO(Info); + Info.Attr.fMode = fMode; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileSet_fMode == fMode, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileSet_fMode))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testFSInfoQuerySetDirATime(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTDIR hDir = (RTDIR)&g_aTestDirHandles[g_iNextDirHandle++ % RT_ELEMENTS(g_aTestDirHandles)]; + const int64_t ccAtimeNano = 100000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Query and set directory atime"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTDirOpen_hDir = hDir; + rc = createFile(&svcTable, Root, "test/dir", + SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ, &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + RTTimeSpecSetNano(&g_testRTDirQueryInfo_ATime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTDirQueryInfo_hDir == hDir, (hTest, "Dir=%p\n", g_testRTDirQueryInfo_hDir)); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&Info.AccessTime) == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&Info.AccessTime)))); + RT_ZERO(Info); + RTTimeSpecSetNano(&Info.AccessTime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&g_testRTDirSetTimes_ATime) + == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&g_testRTDirSetTimes_ATime)))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTDirClose_hDir == hDir, (hTest, "hDir=%p\n", g_testRTDirClose_hDir)); +} + +void testFSInfoQuerySetFileATime(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const int64_t ccAtimeNano = 100000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Query and set file atime"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + RTTimeSpecSetNano(&g_testRTFileQueryInfo_ATime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_FILE, sizeof(Info), + &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileQueryInfo_hFile == hFile, (hTest, "File=%u\n", g_testRTFileQueryInfo_hFile)); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&Info.AccessTime) == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&Info.AccessTime)))); + RT_ZERO(Info); + RTTimeSpecSetNano(&Info.AccessTime, ccAtimeNano); + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, RTTimeSpecGetNano(&g_testRTFileSetTimes_ATime) + == ccAtimeNano, + (hTest, "ATime=%llu\n", + LLUIFY(RTTimeSpecGetNano(&g_testRTFileSetTimes_ATime)))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testFSInfoQuerySetEndOfFile(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const RTFOFF cbNew = 50000; + SHFLFSOBJINFO Info; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Set end of file position"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + RT_ZERO(Info); + Info.cbObject = cbNew; + rc = sfInformation(&svcTable, Root, Handle, SHFL_INFO_SET | SHFL_INFO_SIZE, + sizeof(Info), &Info); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK_MSG(hTest, g_testRTFileSetSize_hFile == hFile, (hTest, "File=%u\n", g_testRTFileSetSize_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileSetSize_cbSize == cbNew, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileSetSize_cbSize))); + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + +void testLockFileSimple(RTTEST hTest) +{ + VBOXHGCMSVCFNTABLE svcTable; + VBOXHGCMSVCHELPERS svcHelpers; + SHFLROOT Root; + const RTFILE hFile = (RTFILE) 0x10000; + const int64_t offLock = 50000; + const uint64_t cbLock = 4000; + SHFLHANDLE Handle; + int rc; + + RTTestSub(hTest, "Simple file lock and unlock"); + Root = initWithWritableMapping(hTest, &svcTable, &svcHelpers, + "/test/mapping", "testname"); + g_testRTFileOpen_hFile = hFile; + rc = createFile(&svcTable, Root, "/test/file", SHFL_CF_ACCESS_READ, + &Handle, NULL); + RTTEST_CHECK_RC_OK(hTest, rc); + rc = lockFile(&svcTable, Root, Handle, offLock, cbLock, SHFL_LOCK_SHARED); + RTTEST_CHECK_RC_OK(hTest, rc); +#ifdef RT_OS_WINDOWS /* Locking is a no-op elsewhere. */ + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_hFile == hFile, (hTest, "File=%u\n", g_testRTFileLock_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_fLock == 0, + (hTest, "fLock=%u\n", g_testRTFileLock_fLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_offLock == offLock, + (hTest, "Offs=%llu\n", (long long) g_testRTFileLock_offLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileLock_cbLock == cbLock, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileLock_cbLock))); +#endif + rc = lockFile(&svcTable, Root, Handle, offLock, cbLock, SHFL_LOCK_CANCEL); + RTTEST_CHECK_RC_OK(hTest, rc); +#ifdef RT_OS_WINDOWS + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_hFile == hFile, (hTest, "File=%u\n", g_testRTFileUnlock_hFile)); + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_offLock == offLock, + (hTest, "Offs=%llu\n", + (long long) g_testRTFileUnlock_offLock)); + RTTEST_CHECK_MSG(hTest, g_testRTFileUnlock_cbLock == cbLock, + (hTest, "Size=%llu\n", LLUIFY(g_testRTFileUnlock_cbLock))); +#endif + unmapAndRemoveMapping(hTest, &svcTable, Root, "testname"); + AssertReleaseRC(svcTable.pfnDisconnect(NULL, 0, svcTable.pvService)); + AssertReleaseRC(svcTable.pfnUnload(NULL)); + RTTestGuardedFree(hTest, svcTable.pvService); + RTTEST_CHECK_MSG(hTest, g_testRTFileClose_hFile == hFile, (hTest, "File=%u\n", g_testRTFileClose_hFile)); +} + + +/********************************************************************************************************************************* +* Main code * +*********************************************************************************************************************************/ + +static void testAPI(RTTEST hTest) +{ + testMappingsQuery(hTest); + testMappingsQueryName(hTest); + testMapFolder(hTest); + testUnmapFolder(hTest); + testCreate(hTest); + testClose(hTest); + testRead(hTest); + testWrite(hTest); + testLock(hTest); + testFlush(hTest); + testDirList(hTest); + testReadLink(hTest); + testFSInfo(hTest); + testRemove(hTest); + testRename(hTest); + testSymlink(hTest); + testMappingsAdd(hTest); + testMappingsRemove(hTest); + /* testSetStatusLed(hTest); */ +} + +int main(int argc, char **argv) +{ + RT_NOREF1(argc); + RTEXITCODE rcExit = RTTestInitAndCreate(RTPathFilename(argv[0]), &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + testAPI(g_hTest); + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h new file mode 100644 index 00000000..3be50c23 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.h @@ -0,0 +1,140 @@ +/** @file + * VBox Shared Folders testcase stub redefinitions. + */ + +/* + * Copyright (C) 2011-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h +#define VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Grumble... if the coding style let us use the anonymous "struct RTTESTINT *" + * instead of "PRTTEST" here we wouldn't need to unnecessarily include this. */ +#include + +void testMappingsQuery(RTTEST hTest); +/* Sub-tests for testMappingsQuery(). */ +void testMappingsQuerySimple(RTTEST hTest); +void testMappingsQueryTooFewBuffers(RTTEST hTest); +void testMappingsQueryAutoMount(RTTEST hTest); +void testMappingsQueryArrayWrongSize(RTTEST hTest); + +void testMappingsQueryName(RTTEST hTest); +/* Sub-tests for testMappingsQueryName(). */ +void testMappingsQueryNameValid(RTTEST hTest); +void testMappingsQueryNameInvalid(RTTEST hTest); +void testMappingsQueryNameBadBuffer(RTTEST hTest); + +void testMapFolder(RTTEST hTest); +/* Sub-tests for testMapFolder(). */ +void testMapFolderValid(RTTEST hTest); +void testMapFolderInvalid(RTTEST hTest); +void testMapFolderTwice(RTTEST hTest); +void testMapFolderDelimiter(RTTEST hTest); +void testMapFolderCaseSensitive(RTTEST hTest); +void testMapFolderCaseInsensitive(RTTEST hTest); +void testMapFolderBadParameters(RTTEST hTest); + +void testUnmapFolder(RTTEST hTest); +/* Sub-tests for testUnmapFolder(). */ +void testUnmapFolderValid(RTTEST hTest); +void testUnmapFolderInvalid(RTTEST hTest); +void testUnmapFolderBadParameters(RTTEST hTest); + +void testCreate(RTTEST hTest); +/* Sub-tests for testCreate(). */ +void testCreateFileSimple(RTTEST hTest); +void testCreateFileSimpleCaseInsensitive(RTTEST hTest); +void testCreateDirSimple(RTTEST hTest); +void testCreateBadParameters(RTTEST hTest); + +void testClose(RTTEST hTest); +/* Sub-tests for testClose(). */ +void testCloseBadParameters(RTTEST hTest); + +void testRead(RTTEST hTest); +/* Sub-tests for testRead(). */ +void testReadBadParameters(RTTEST hTest); +void testReadFileSimple(RTTEST hTest); + +void testWrite(RTTEST hTest); +/* Sub-tests for testWrite(). */ +void testWriteBadParameters(RTTEST hTest); +void testWriteFileSimple(RTTEST hTest); + +void testLock(RTTEST hTest); +/* Sub-tests for testLock(). */ +void testLockBadParameters(RTTEST hTest); +void testLockFileSimple(RTTEST hTest); + +void testFlush(RTTEST hTest); +/* Sub-tests for testFlush(). */ +void testFlushBadParameters(RTTEST hTest); +void testFlushFileSimple(RTTEST hTest); + +void testDirList(RTTEST hTest); +/* Sub-tests for testDirList(). */ +void testDirListBadParameters(RTTEST hTest); +void testDirListEmpty(RTTEST hTest); + +void testReadLink(RTTEST hTest); +/* Sub-tests for testReadLink(). */ +void testReadLinkBadParameters(RTTEST hTest); + +void testFSInfo(RTTEST hTest); +/* Sub-tests for testFSInfo(). */ +void testFSInfoBadParameters(RTTEST hTest); +void testFSInfoQuerySetFMode(RTTEST hTest); +void testFSInfoQuerySetDirATime(RTTEST hTest); +void testFSInfoQuerySetFileATime(RTTEST hTest); +void testFSInfoQuerySetEndOfFile(RTTEST hTest); + +void testRemove(RTTEST hTest); +/* Sub-tests for testRemove(). */ +void testRemoveBadParameters(RTTEST hTest); + +void testRename(RTTEST hTest); +/* Sub-tests for testRename(). */ +void testRenameBadParameters(RTTEST hTest); + +void testSymlink(RTTEST hTest); +/* Sub-tests for testSymlink(). */ +void testSymlinkBadParameters(RTTEST hTest); + +void testMappingsAdd(RTTEST hTest); +/* Sub-tests for testMappingsAdd(). */ +void testMappingsAddBadParameters(RTTEST hTest); + +void testMappingsRemove(RTTEST hTest); +/* Sub-tests for testMappingsRemove(). */ +void testMappingsRemoveBadParameters(RTTEST hTest); + +#if 0 /* Where should this go? */ +void testSetStatusLed(RTTEST hTest); +/* Sub-tests for testStatusLed(). */ +void testSetStatusLedBadParameters(RTTEST hTest); +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_testcase_tstSharedFolderService_h */ diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp new file mode 100644 index 00000000..052b80d7 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstShflCase.cpp @@ -0,0 +1,452 @@ +/** @file + * Testcase for shared folder case conversion code. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MISC +#define LOG_ENABLED +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* Override slash for non-windows hosts. */ +#undef RTPATH_DELIMITER +#define RTPATH_DELIMITER '\\' + +/* Use our own RTPath and RTDir methods. */ +#define RTPathQueryInfo rtPathQueryInfo +#define RTDirOpenFiltered rtDirOpenFiltered +#define RTDirClose rtDirClose +#define RTDirReadEx rtDirReadEx + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static int iDirList = 0; +static int iDirFile = 0; + +static const char *g_apszDirs[] = +{ + "c:", + "c:\\test dir", + "c:\\test dir\\SUBDIR", +}; + +static const char *g_apszDirsC[] = +{ + ".", + "..", + "test dir" +}; + +static const char *g_apszTestdirEntries[] = +{ + ".", + "..", + "SUBDIR", + "a.bat", + "aTestJe.bat", + "aTestje.bat", + "b.bat", + "c.bat", + "d.bat", + "e.bat", + "f.bat", + "g.bat", + "h.bat", + "x.bat", + "z.bat", +}; + +static const char *g_apszSUBDIREntries[] = +{ + ".", + "..", + "a.bat", + "aTestJe.bat", + "aTestje.bat", + "b.bat", + "c.bat", + "d.bat", + "e.bat", + "f.bat", + "g.bat", + "h.bat", + "x.bat", + "z.bat", +}; + +int rtDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags) +{ + RT_NOREF2(enmFilter, fFlags); + if (!strcmp(pszPath, "c:\\*")) + iDirList = 1; + else if (!strcmp(pszPath, "c:\\test dir\\*")) + iDirList = 2; + else if (!strcmp(pszPath, "c:\\test dir\\SUBDIR\\*")) + iDirList = 3; + else + AssertFailed(); + + *phDir = (RTDIR)1; + return VINF_SUCCESS; +} + +int rtDirClose(RTDIR hDir) +{ + RT_NOREF1(hDir); + iDirFile = 0; + return VINF_SUCCESS; +} + +int rtDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + RT_NOREF4(hDir, pcbDirEntry, enmAdditionalAttribs, fFlags); + switch (iDirList) + { + case 1: + if (iDirFile == RT_ELEMENTS(g_apszDirsC)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszDirsC[iDirFile]); + strcpy(pDirEntry->szName, g_apszDirsC[iDirFile++]); + break; + case 2: + if (iDirFile == RT_ELEMENTS(g_apszTestdirEntries)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszTestdirEntries[iDirFile]); + strcpy(pDirEntry->szName, g_apszTestdirEntries[iDirFile++]); + break; + case 3: + if (iDirFile == RT_ELEMENTS(g_apszSUBDIREntries)) + return VERR_NO_MORE_FILES; + pDirEntry->cbName = (uint16_t)strlen(g_apszSUBDIREntries[iDirFile]); + strcpy(pDirEntry->szName, g_apszSUBDIREntries[iDirFile++]); + break; + } + return VINF_SUCCESS; +} + +int rtPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + RT_NOREF2(pObjInfo, enmAdditionalAttribs); + int cMax; + + /* first try g_apszDirs */ + for (unsigned int i=0;iszName, pszFullPath); + szWildCard[0] = RTPATH_DELIMITER; + szWildCard[1] = '*'; + szWildCard[2] = 0; + strcat(pDirEntry->szName, szWildCard); + + rc = RTDirOpenFiltered(&hSearch, pDirEntry->szName, RTDIRFILTER_WINNT, 0 /*fFlags*/); + *(pszStartComponent-1) = RTPATH_DELIMITER; + if (RT_FAILURE(rc)) + goto end; + + for(;;) + { + size_t cbDirEntrySize = cbDirEntry; + + rc = RTDirReadEx(hSearch, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (rc == VERR_NO_MORE_FILES) + break; + + if (VINF_SUCCESS != rc && rc != VWRN_NO_DIRENT_INFO) + { + AssertFailed(); + if (rc != VERR_NO_TRANSLATION) + break; + else + continue; + } + + Log2(("vbsfCorrectCasing: found %s\n", &pDirEntry->szName[0])); + if ( pDirEntry->cbName == cbComponent + && !RTStrICmp(pszStartComponent, &pDirEntry->szName[0])) + { + Log(("Found original name %s (%s)\n", &pDirEntry->szName[0], pszStartComponent)); + strcpy(pszStartComponent, &pDirEntry->szName[0]); + rc = VINF_SUCCESS; + break; + } + } + if (RT_FAILURE(rc)) + Log(("vbsfCorrectCasing %s failed with %d\n", pszStartComponent, rc)); + +end: + if (pDirEntry) + RTMemFree(pDirEntry); + + if (hSearch) + RTDirClose(hSearch); + return rc; +} + + + +int testCase(char *pszFullPath, bool fWildCard = false) +{ + int rc; + RTFSOBJINFO info; + char *pszWildCardComponent = NULL; + + if (fWildCard) + { + /* strip off the last path component, that contains the wildcard(s) */ + size_t len = strlen(pszFullPath); + char *src = pszFullPath + len - 1; + + while(src > pszFullPath) + { + if (*src == RTPATH_DELIMITER) + break; + src--; + } + if (*src == RTPATH_DELIMITER) + { + bool fHaveWildcards = false; + char *temp = src; + + while(*temp) + { + char uc = *temp; + /** @todo should depend on the guest OS */ + if (uc == '*' || uc == '?' || uc == '>' || uc == '<' || uc == '"') + { + fHaveWildcards = true; + break; + } + temp++; + } + + if (fHaveWildcards) + { + pszWildCardComponent = src; + *pszWildCardComponent = 0; + } + } + } + + rc = RTPathQueryInfo(pszFullPath, &info, RTFSOBJATTRADD_NOTHING); + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + size_t len = strlen(pszFullPath); + char *src = pszFullPath + len - 1; + + Log(("Handle case insensitive guest fs on top of host case sensitive fs for %s\n", pszFullPath)); + + /* Find partial path that's valid */ + while(src > pszFullPath) + { + if (*src == RTPATH_DELIMITER) + { + *src = 0; + rc = RTPathQueryInfo (pszFullPath, &info, RTFSOBJATTRADD_NOTHING); + *src = RTPATH_DELIMITER; + if (rc == VINF_SUCCESS) + { +#ifdef DEBUG + *src = 0; + Log(("Found valid partial path %s\n", pszFullPath)); + *src = RTPATH_DELIMITER; +#endif + break; + } + } + + src--; + } + Assert(*src == RTPATH_DELIMITER && RT_SUCCESS(rc)); + if ( *src == RTPATH_DELIMITER + && RT_SUCCESS(rc)) + { + src++; + for(;;) + { + char *end = src; + bool fEndOfString = true; + + while(*end) + { + if (*end == RTPATH_DELIMITER) + break; + end++; + } + + if (*end == RTPATH_DELIMITER) + { + fEndOfString = false; + *end = 0; + rc = RTPathQueryInfo(src, &info, RTFSOBJATTRADD_NOTHING); + Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND); + } + else + if (end == src) + rc = VINF_SUCCESS; /* trailing delimiter */ + else + rc = VERR_FILE_NOT_FOUND; + + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + /* path component is invalid; try to correct the casing */ + rc = vbsfCorrectCasing(pszFullPath, src); + if (RT_FAILURE(rc)) + { + if (!fEndOfString) + *end = RTPATH_DELIMITER; + break; + } + } + + if (fEndOfString) + break; + + *end = RTPATH_DELIMITER; + src = end + 1; + } + if (RT_FAILURE(rc)) + Log(("Unable to find suitable component rc=%d\n", rc)); + } + else + rc = VERR_FILE_NOT_FOUND; + + } + if (pszWildCardComponent) + *pszWildCardComponent = RTPATH_DELIMITER; + + if (RT_SUCCESS(rc)) + Log(("New valid path %s\n", pszFullPath)); + else + Log(("Old invalid path %s\n", pszFullPath)); + return rc; +} + + +int main() +{ + char szTest[128]; + + RTR3InitExeNoArguments(0); + RTLogFlush(NULL); + RTLogDestinations(NULL, "stdout"); + RTLogGroupSettings(NULL, "misc=~0"); + RTLogFlags(NULL, "unbuffered"); + + strcpy(szTest, "c:\\test Dir\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\SUBDIR\\z.bAt"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\SUBDiR\\atestje.bat"); + testCase(szTest); + strcpy(szTest, "c:\\TEST dir\\subDiR\\aTestje.baT"); + testCase(szTest); + strcpy(szTest, "c:\\TEST dir\\subDiR\\*"); + testCase(szTest, true); + strcpy(szTest, "c:\\TEST dir\\subDiR\\"); + testCase(szTest ,true); + strcpy(szTest, "c:\\test dir\\SUBDIR\\"); + testCase(szTest); + strcpy(szTest, "c:\\test dir\\invalid\\SUBDIR\\test.bat"); + testCase(szTest); + return 0; +} + diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp new file mode 100644 index 00000000..c9832493 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/testcase/tstShflSizes.cpp @@ -0,0 +1,144 @@ +/** @file + * tstShflSize - Testcase for shared folder structure sizes. + * Run this on Linux and Windows, then compare. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#define STRUCT(t, size) \ + do { \ + if (fPrintChecks) \ + RTPrintf(" STRUCT(" #t ", %d);\n", (int)sizeof(t)); \ + else if ((size) != sizeof(t)) \ + { \ + RTPrintf("%30s: %d expected %d!\n", #t, (int)sizeof(t), (size)); \ + cErrors++; \ + } \ + else if (!fQuiet)\ + RTPrintf("%30s: %d\n", #t, (int)sizeof(t)); \ + } while (0) + + +int main(int argc, char **argv) +{ + unsigned cErrors = 0; + + /* + * Prints the code below if any argument was giving. + */ + bool fQuiet = argc == 2 && !strcmp(argv[1], "quiet"); + bool fPrintChecks = !fQuiet && argc != 1; + + RTPrintf("tstShflSizes: TESTING\n"); + + /* + * The checks. + */ + STRUCT(SHFLROOT, 4); + STRUCT(SHFLHANDLE, 8); + STRUCT(SHFLSTRING, 6); + STRUCT(SHFLCREATERESULT, 4); + STRUCT(SHFLCREATEPARMS, 108); + STRUCT(SHFLMAPPING, 8); + STRUCT(SHFLDIRINFO, 128); + STRUCT(SHFLVOLINFO, 40); + STRUCT(SHFLFSOBJATTR, 44); + STRUCT(SHFLFSOBJINFO, 92); +#ifdef VBOX_WITH_64_BITS_GUESTS +/* The size of the guest structures depends on the current architecture bit count (ARCH_BITS) + * because the HGCMFunctionParameter structure differs in 32 and 64 bit guests. + * The host VMMDev device takes care about this. + * + * Therefore this testcase verifies whether structure sizes are correct for the current ARCH_BITS. + */ +# if ARCH_BITS == 64 + STRUCT(VBoxSFQueryMappings, 88); + STRUCT(VBoxSFQueryMapName, 72); + STRUCT(VBoxSFMapFolder_Old, 88); + STRUCT(VBoxSFMapFolder, 104); + STRUCT(VBoxSFUnmapFolder, 56); + STRUCT(VBoxSFCreate, 88); + STRUCT(VBoxSFClose, 72); + STRUCT(VBoxSFRead, 120); + STRUCT(VBoxSFWrite, 120); + STRUCT(VBoxSFLock, 120); + STRUCT(VBoxSFFlush, 72); + STRUCT(VBoxSFList, 168); + STRUCT(VBoxSFInformation, 120); + STRUCT(VBoxSFRemove, 88); + STRUCT(VBoxSFRename, 104); +# elif ARCH_BITS == 32 + STRUCT(VBoxSFQueryMappings, 24+52); + STRUCT(VBoxSFQueryMapName, 24+40); /* this was changed from 52 in 21976 after VBox-1.4. */ + STRUCT(VBoxSFMapFolder_Old, 24+52); + STRUCT(VBoxSFMapFolder, 24+64); + STRUCT(VBoxSFUnmapFolder, 24+28); + STRUCT(VBoxSFCreate, 24+52); + STRUCT(VBoxSFClose, 24+40); + STRUCT(VBoxSFRead, 24+76); + STRUCT(VBoxSFWrite, 24+76); + STRUCT(VBoxSFLock, 24+76); + STRUCT(VBoxSFFlush, 24+40); + STRUCT(VBoxSFList, 24+112); + STRUCT(VBoxSFInformation, 24+76); + STRUCT(VBoxSFRemove, 24+52); + STRUCT(VBoxSFRename, 24+64); +# else +# error "Unsupported ARCH_BITS" +# endif /* ARCH_BITS */ +#else + STRUCT(VBoxSFQueryMappings, 24+52); + STRUCT(VBoxSFQueryMapName, 24+40); /* this was changed from 52 in 21976 after VBox-1.4. */ + STRUCT(VBoxSFMapFolder_Old, 24+52); + STRUCT(VBoxSFMapFolder, 24+64); + STRUCT(VBoxSFUnmapFolder, 24+28); + STRUCT(VBoxSFCreate, 24+52); + STRUCT(VBoxSFClose, 24+40); + STRUCT(VBoxSFRead, 24+76); + STRUCT(VBoxSFWrite, 24+76); + STRUCT(VBoxSFLock, 24+76); + STRUCT(VBoxSFFlush, 24+40); + STRUCT(VBoxSFList, 24+112); + STRUCT(VBoxSFInformation, 24+76); + STRUCT(VBoxSFRemove, 24+52); + STRUCT(VBoxSFRename, 24+64); +#endif /* VBOX_WITH_64_BITS_GUESTS */ + + /* + * The summary. + */ + if (!cErrors) + RTPrintf("tstShflSizes: SUCCESS\n"); + else + RTPrintf("tstShflSizes: FAILURE - %d errors\n", cErrors); + return !!cErrors; +} + diff --git a/src/VBox/HostServices/SharedFolders/teststubs.h b/src/VBox/HostServices/SharedFolders/teststubs.h new file mode 100644 index 00000000..c7478621 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/teststubs.h @@ -0,0 +1,104 @@ +/** @file + * VBox Shared Folders testcase stub redefinitions. + */ + +/* + * Copyright (C) 2011-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** + * Macros for renaming iprt file operations to redirect them to testcase + * stub functions (mocks). The religiously correct way to do this would be + * to make the service use a file operations structure with function pointers + * but I'm not sure that would be universally appreciated. */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_teststubs_h +#define VBOX_INCLUDED_SRC_SharedFolders_teststubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include + +#define RTDirClose testRTDirClose +extern int testRTDirClose(RTDIR hDir); +#define RTDirCreate testRTDirCreate +extern int testRTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate); +#define RTDirOpen testRTDirOpen +extern int testRTDirOpen(RTDIR *phDir, const char *pszPath); +#define RTDirOpenFiltered testRTDirOpenFiltered +extern int testRTDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags); +#define RTDirQueryInfo testRTDirQueryInfo +extern int testRTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs); +#define RTDirRemove testRTDirRemove +extern int testRTDirRemove(const char *pszPath); +#define RTDirReadEx testRTDirReadEx +extern int testRTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags); +#define RTDirSetMode testRTDirSetMode +extern int testRTDirSetMode(RTDIR hDir, RTFMODE fMode); +#define RTDirSetTimes testRTDirSetTimes +extern int testRTDirSetTimes(RTDIR hDir, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime); +#define RTFileClose testRTFileClose +extern int testRTFileClose(RTFILE hFile); +#define RTFileDelete testRTFileDelete +extern int testRTFileDelete(const char *pszFilename); +#define RTFileFlush testRTFileFlush +extern int testRTFileFlush(RTFILE hFile); +#define RTFileLock testRTFileLock +extern int testRTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock); +#define RTFileOpenEx testRTFileOpenEx +extern int testRTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken); +#define RTFileQueryInfo testRTFileQueryInfo +extern int testRTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs); +#define RTFileRead testRTFileRead +extern int testRTFileRead(RTFILE hFile, void *pvBuf, size_t cbToRead, size_t *pcbRead); +#define RTFileReadAt testRTFileReadAt +extern int testRTFileReadAt(RTFILE hFile, uint64_t offFile, void *pvBuf, size_t cbToRead, size_t *pcbRead); +#define RTFileSetMode testRTFileSetMode +extern int testRTFileSetMode(RTFILE hFile, RTFMODE fMode); +#define RTFileSetSize testRTFileSetSize +extern int testRTFileSetSize(RTFILE hFile, uint64_t cbSize); +#define RTFileSetTimes testRTFileSetTimes +extern int testRTFileSetTimes(RTFILE hFile, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime); +#define RTFileSeek testRTFileSeek +extern int testRTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual); +#define RTFileUnlock testRTFileUnlock +extern int testRTFileUnlock(RTFILE hFile, int64_t offLock, uint64_t cbLock); +#define RTFileWrite testRTFileWrite +extern int testRTFileWrite(RTFILE hFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten); +#define RTFileWriteAt testRTFileWriteAt +extern int testRTFileWriteAt(RTFILE hFile, uint64_t offFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten); +#define RTFsQueryProperties testRTFsQueryProperties +extern int testRTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties); +#define RTFsQuerySerial testRTFsQuerySerial +extern int testRTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial); +#define RTFsQuerySizes testRTFsQuerySizes +extern int testRTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, uint32_t *pcbBlock, uint32_t *pcbSector); +#define RTPathQueryInfoEx testRTPathQueryInfoEx +extern int testRTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags); +#define RTSymlinkDelete testRTSymlinkDelete +extern int testRTSymlinkDelete(const char *pszSymlink, uint32_t fDelete); +#define RTSymlinkRead testRTSymlinkRead +extern int testRTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_teststubs_h */ diff --git a/src/VBox/HostServices/SharedFolders/vbsf.cpp b/src/VBox/HostServices/SharedFolders/vbsf.cpp new file mode 100644 index 00000000..59907584 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsf.cpp @@ -0,0 +1,2690 @@ +/* $Id: vbsf.cpp $ */ +/** @file + * Shared Folders - VBox Shared Folders. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "vbsfpath.h" +#include "mappings.h" +#include "vbsf.h" +#include "shflhandle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef RT_OS_DARWIN +# include +#endif + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_RT_LINK(pClient) ((pClient)->fu32Flags & SHFL_CF_SYMLINKS ? RTPATH_F_ON_LINK : RTPATH_F_FOLLOW_LINK) + +/** + * @todo find a better solution for supporting the execute bit for non-windows + * guests on windows host. Search for "0111" to find all the relevant places. + */ + + +#ifndef RT_OS_WINDOWS + +/** + * Helps to check if pszPath deserves a VERR_PATH_NOT_FOUND status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsPathNotFound(char *pszPath) +{ + /* + * Check if the parent directory actually exists. We temporarily modify the path here. + */ + size_t cchParent = RTPathParentLength(pszPath); + char chSaved = pszPath[cchParent]; + pszPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszPath[cchParent] = chSaved; + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return false; + return true; + } + if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND) + return true; + return false; +} + +/** + * Helps to check if pszPath deserves a VERR_PATH_NOT_FOUND status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsPathNotFound2(char *pszSrcPath, char *pszDstPath) +{ + /* + * Do the source parent first. + */ + size_t cchParent = RTPathParentLength(pszSrcPath); + char chSaved = pszSrcPath[cchParent]; + pszSrcPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszSrcPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszSrcPath[cchParent] = chSaved; + if ( (RT_SUCCESS(vrc) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + || vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + return true; + if (RT_FAILURE(vrc)) + return false; + + /* + * The source itself. + */ + vrc = RTPathQueryInfoEx(pszSrcPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + /* + * The source is fine, continue with the destination. + */ + cchParent = RTPathParentLength(pszDstPath); + chSaved = pszDstPath[cchParent]; + pszDstPath[cchParent] = '\0'; + vrc = RTPathQueryInfoEx(pszDstPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszDstPath[cchParent] = chSaved; + if ( (RT_SUCCESS(vrc) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + || vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + return true; + } + return false; +} + +/** + * Helps checking if the specified path happens to exist but not be a directory. + */ +static bool vbsfErrorStyleIsWindowsNotADirectory(const char *pszPath) +{ + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return false; + return true; + } + return false; +} + +/** + * Helps to check if pszPath deserves a VERR_INVALID_NAME status when catering + * to windows guests. + */ +static bool vbsfErrorStyleIsWindowsInvalidNameForNonDir(char *pszPath) +{ + /* + * This only applies to paths with trailing slashes. + */ + size_t const cchPath = strlen(pszPath); + if (cchPath > 0 && RTPATH_IS_SLASH(pszPath[cchPath - 1])) + { + /* + * However it doesn't if an earlier path component is missing or not a file. + */ + size_t cchParent = RTPathParentLength(pszPath); + char chSaved = pszPath[cchParent]; + pszPath[cchParent] = '\0'; + RTFSOBJINFO ObjInfo; + int vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + pszPath[cchParent] = chSaved; + if (RT_SUCCESS(vrc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + return true; + } + return false; +} + +#endif /* RT_OS_WINDOWS */ + +void vbsfStripLastComponent(char *pszFullPath, uint32_t cbFullPathRoot) +{ + RTUNICP cp; + + /* Do not strip root. */ + char *s = pszFullPath + cbFullPathRoot; + char *delimSecondLast = NULL; + char *delimLast = NULL; + + LogFlowFunc(("%s -> %s\n", pszFullPath, s)); + + for (;;) + { + cp = RTStrGetCp(s); + + if (cp == RTUNICP_INVALID || cp == 0) + { + break; + } + + if (cp == RTPATH_DELIMITER) + { + if (delimLast != NULL) + { + delimSecondLast = delimLast; + } + + delimLast = s; + } + + s = RTStrNextCp(s); + } + + if (cp == 0) + { + if (delimLast + 1 == s) + { + if (delimSecondLast) + { + *delimSecondLast = 0; + } + else if (delimLast) + { + *delimLast = 0; + } + } + else + { + if (delimLast) + { + *delimLast = 0; + } + } + } + + LogFlowFunc(("%s, %s, %s\n", pszFullPath, delimLast, delimSecondLast)); +} + +static int vbsfBuildFullPath(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, + uint32_t cbPath, char **ppszFullPath, uint32_t *pcbFullPathRoot, + bool fWildCard = false, bool fPreserveLastComponent = false) +{ + char *pszHostPath = NULL; + uint32_t fu32PathFlags = 0; + uint32_t fu32Options = VBSF_O_PATH_CHECK_ROOT_ESCAPE + | (fWildCard? VBSF_O_PATH_WILDCARD: 0) + | (fPreserveLastComponent? VBSF_O_PATH_PRESERVE_LAST_COMPONENT: 0); + + int rc = vbsfPathGuestToHost(pClient, root, pPath, cbPath, + &pszHostPath, pcbFullPathRoot, fu32Options, &fu32PathFlags); + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + LogRel2(("SharedFolders: GuestToHost 0x%RX32 [%.*s]->[%s] %Rrc\n", fu32PathFlags, pPath->u16Length, &pPath->String.utf8[0], pszHostPath, rc)); + } + else + { + LogRel2(("SharedFolders: GuestToHost 0x%RX32 [%.*ls]->[%s] %Rrc\n", fu32PathFlags, pPath->u16Length / 2, &pPath->String.ucs2[0], pszHostPath, rc)); + } + + if (RT_SUCCESS(rc)) + { + if (ppszFullPath) + *ppszFullPath = pszHostPath; + } + return rc; +} + +static void vbsfFreeFullPath(char *pszFullPath) +{ + vbsfFreeHostPath(pszFullPath); +} + +typedef enum VBSFCHECKACCESS +{ + VBSF_CHECK_ACCESS_READ = 0, + VBSF_CHECK_ACCESS_WRITE = 1 +} VBSFCHECKACCESS; + +/** + * Check if the handle data is valid and the operation is allowed on the shared folder. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared folder + * @param root The index of the shared folder in the table of mappings. + * @param pHandle Information about the file or directory object. + * @param enmCheckAccess Whether the operation needs read only or write access. + */ +static int vbsfCheckHandleAccess(SHFLCLIENTDATA *pClient, SHFLROOT root, + SHFLFILEHANDLE *pHandle, VBSFCHECKACCESS enmCheckAccess) +{ + /* Handle from the same 'root' index? */ + if (RT_LIKELY(RT_VALID_PTR(pHandle) && root == pHandle->root)) + { /* likely */ } + else + return VERR_INVALID_HANDLE; + + /* Check if the guest is still allowed to access this share. + * vbsfMappingsQueryWritable returns error if the shared folder has been removed from the VM settings. + */ + bool fWritable; + int rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + if (enmCheckAccess == VBSF_CHECK_ACCESS_WRITE) + { + /* Operation requires write access. Check if the shared folder is writable too. */ + if (RT_LIKELY(fWritable)) + { /* likely */ } + else + return VERR_WRITE_PROTECT; + } + + return VINF_SUCCESS; +} + +/** + * Convert shared folder create flags (see include/iprt/shflsvc.h) into iprt create flags. + * + * @returns iprt status code + * @param fWritable whether the shared folder is writable + * @param fShflFlags shared folder create flags + * @param fMode file attributes + * @param handleInitial initial handle + * @param pfOpen Where to return iprt create flags + */ +static int vbsfConvertFileOpenFlags(bool fWritable, unsigned fShflFlags, RTFMODE fMode, SHFLHANDLE handleInitial, uint64_t *pfOpen) +{ + uint64_t fOpen = 0; + int rc = VINF_SUCCESS; + + if ( (fMode & RTFS_DOS_MASK) != 0 + && (fMode & RTFS_UNIX_MASK) == 0) + { + /* A DOS/Windows guest, make RTFS_UNIX_* from RTFS_DOS_*. + * @todo this is based on rtFsModeNormalize/rtFsModeFromDos. + * May be better to use RTFsModeNormalize here. + */ + fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH; + /* x for directories. */ + if (fMode & RTFS_DOS_DIRECTORY) + fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH; + /* writable? */ + if (!(fMode & RTFS_DOS_READONLY)) + fMode |= RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH; + + /* Set the requested mode using only allowed bits. */ + fOpen |= ((fMode & RTFS_UNIX_MASK) << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + } + else + { + /* Old linux and solaris additions did not initialize the Info.Attr.fMode field + * and it contained random bits from stack. Detect this using the handle field value + * passed from the guest: old additions set it (incorrectly) to 0, new additions + * set it to SHFL_HANDLE_NIL(~0). + */ + if (handleInitial == 0) + { + /* Old additions. Do nothing, use default mode. */ + } + else + { + /* New additions or Windows additions. Set the requested mode using only allowed bits. + * Note: Windows guest set RTFS_UNIX_MASK bits to 0, which means a default mode + * will be set in fOpen. + */ + fOpen |= ((fMode & RTFS_UNIX_MASK) << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + } + } + + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_RW)) + { + default: + case SHFL_CF_ACCESS_NONE: + { +#ifdef RT_OS_WINDOWS + if (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_ATTR) != SHFL_CF_ACCESS_ATTR_NONE) + fOpen |= RTFILE_O_ATTR_ONLY; + else +#endif + fOpen |= RTFILE_O_READ; + Log(("FLAG: SHFL_CF_ACCESS_NONE\n")); + break; + } + + case SHFL_CF_ACCESS_READ: + { + fOpen |= RTFILE_O_READ; + Log(("FLAG: SHFL_CF_ACCESS_READ\n")); + break; + } + + case SHFL_CF_ACCESS_WRITE: + { + fOpen |= RTFILE_O_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_WRITE\n")); + break; + } + + case SHFL_CF_ACCESS_READWRITE: + { + fOpen |= RTFILE_O_READWRITE; + Log(("FLAG: SHFL_CF_ACCESS_READWRITE\n")); + break; + } + } + + if (fShflFlags & SHFL_CF_ACCESS_APPEND) + { + fOpen |= RTFILE_O_APPEND; + } + + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_ATTR)) + { + default: + case SHFL_CF_ACCESS_ATTR_NONE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_DEFAULT; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_NONE\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_READ: + { + fOpen |= RTFILE_O_ACCESS_ATTR_READ; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_READ\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_WRITE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_WRITE\n")); + break; + } + + case SHFL_CF_ACCESS_ATTR_READWRITE: + { + fOpen |= RTFILE_O_ACCESS_ATTR_READWRITE; + Log(("FLAG: SHFL_CF_ACCESS_ATTR_READWRITE\n")); + break; + } + } + + /* Sharing mask */ + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_DENY)) + { + default: + case SHFL_CF_ACCESS_DENYNONE: + fOpen |= RTFILE_O_DENY_NONE; + Log(("FLAG: SHFL_CF_ACCESS_DENYNONE\n")); + break; + + case SHFL_CF_ACCESS_DENYREAD: + fOpen |= RTFILE_O_DENY_READ; + Log(("FLAG: SHFL_CF_ACCESS_DENYREAD\n")); + break; + + case SHFL_CF_ACCESS_DENYWRITE: + fOpen |= RTFILE_O_DENY_WRITE; + Log(("FLAG: SHFL_CF_ACCESS_DENYWRITE\n")); + break; + + case SHFL_CF_ACCESS_DENYALL: + fOpen |= RTFILE_O_DENY_ALL; + Log(("FLAG: SHFL_CF_ACCESS_DENYALL\n")); + break; + } + + /* Open/Create action mask */ + switch (BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_EXISTS)) + { + case SHFL_CF_ACT_OPEN_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN_CREATE; + Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN; + Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_FAIL_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE; + Log(("FLAGS: SHFL_CF_ACT_FAIL_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_REPLACE_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE_REPLACE; + Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE; + Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + case SHFL_CF_ACT_OVERWRITE_IF_EXISTS: + if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_CREATE_REPLACE; + Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n")); + } + else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE; + Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n")); + } + else + { + Log(("FLAGS: invalid open/create action combination\n")); + rc = VERR_INVALID_PARAMETER; + } + break; + default: + rc = VERR_INVALID_PARAMETER; + Log(("FLAG: SHFL_CF_ACT_MASK_IF_EXISTS - invalid parameter\n")); + } + + if (RT_SUCCESS(rc)) + { + if (!fWritable) + fOpen &= ~RTFILE_O_WRITE; + + *pfOpen = fOpen; + } + return rc; +} + +/** + * Open a file or create and open a new one. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared folder + * @param root The index of the shared folder in the table of mappings. + * @param pszPath Path to the file or folder on the host. + * @param pParms Input: + * - @a CreateFlags: Creation or open parameters, see include/VBox/shflsvc.h + * - @a Info: When a new file is created this specifies the initial parameters. + * When a file is created or overwritten, it also specifies the + * initial size. + * Output: + * - @a Result: Shared folder status code, see include/VBox/shflsvc.h + * - @a Handle: On success the (shared folder) handle of the file opened or + * created + * - @a Info: On success the parameters of the file opened or created + */ +static int vbsfOpenFile(SHFLCLIENTDATA *pClient, SHFLROOT root, char *pszPath, SHFLCREATEPARMS *pParms) +{ + LogFlow(("vbsfOpenFile: pszPath = %s, pParms = %p\n", pszPath, pParms)); + Log(("SHFL create flags %08x\n", pParms->CreateFlags)); + + RTFILEACTION enmActionTaken = RTFILEACTION_INVALID; + SHFLHANDLE handle = SHFL_HANDLE_NIL; + SHFLFILEHANDLE *pHandle = NULL; + + /* is the guest allowed to write to this share? */ + bool fWritable; + int rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc)) + fWritable = false; + + uint64_t fOpen = 0; + rc = vbsfConvertFileOpenFlags(fWritable, pParms->CreateFlags, pParms->Info.Attr.fMode, pParms->Handle, &fOpen); + if (RT_SUCCESS(rc)) + { + rc = VERR_NO_MEMORY; /* Default error. */ + handle = vbsfAllocFileHandle(pClient); + if (handle != SHFL_HANDLE_NIL) + { + pHandle = vbsfQueryFileHandle(pClient, handle); + if (pHandle) + { + pHandle->root = root; + pHandle->file.fOpenFlags = fOpen; + rc = RTFileOpenEx(pszPath, fOpen, &pHandle->file.Handle, &enmActionTaken); + } + } + } + bool fNoError = false; + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_FILE_NOT_FOUND: + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + pParms->Result = SHFL_PATH_NOT_FOUND; +#endif + /* This actually isn't an error, so correct the rc before return later, + because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */ + fNoError = true; + break; + + case VERR_PATH_NOT_FOUND: +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsInvalidNameForNonDir(pszPath)) + { + rc = VERR_INVALID_NAME; + pParms->Result = SHFL_NO_RESULT; + break; + } +#endif + pParms->Result = SHFL_PATH_NOT_FOUND; + fNoError = true; /* Not an error either (see above). */ + break; + + case VERR_ALREADY_EXISTS: + { + RTFSOBJINFO info; + + /** @todo Possible race left here. */ + if (RT_SUCCESS(RTPathQueryInfoEx(pszPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)))) + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + pParms->Result = SHFL_FILE_EXISTS; + + /* This actually isn't an error, so correct the rc before return later, + because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */ + fNoError = true; + break; + } + + case VERR_TOO_MANY_OPEN_FILES: + { + static int s_cErrors; + if (s_cErrors < 32) + { + LogRel(("SharedFolders host service: Cannot open '%s' -- too many open files.\n", pszPath)); +#if defined RT_OS_LINUX || defined(RT_OS_SOLARIS) + if (s_cErrors < 1) + LogRel(("SharedFolders host service: Try to increase the limit for open files (ulimit -n)\n")); +#endif + s_cErrors++; + } + pParms->Result = SHFL_NO_RESULT; + break; + } + + default: + pParms->Result = SHFL_NO_RESULT; + } + } + else + { + switch (enmActionTaken) + { + default: + AssertFailed(); + RT_FALL_THRU(); + case RTFILEACTION_OPENED: + pParms->Result = SHFL_FILE_EXISTS; + break; + case RTFILEACTION_CREATED: + pParms->Result = SHFL_FILE_CREATED; + break; + case RTFILEACTION_REPLACED: + case RTFILEACTION_TRUNCATED: /* not quite right */ + pParms->Result = SHFL_FILE_REPLACED; + break; + } + + if ( (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_REPLACE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_OVERWRITE_IF_EXISTS) + { + /* For now, we do not treat a failure here as fatal. */ + /** @todo Also set the size for SHFL_CF_ACT_CREATE_IF_NEW if SHFL_CF_ACT_FAIL_IF_EXISTS is set. */ + /** @todo r=bird: Exactly document cbObject usage and see what we can get + * away with here. I suspect it is only needed for windows and only + * with SHFL_FILE_CREATED and SHFL_FILE_REPLACED, and only if + * cbObject is non-zero. */ + RTFileSetSize(pHandle->file.Handle, pParms->Info.cbObject); + } +#if 0 + /** @todo */ + /* Set new attributes. */ + if ( ( SHFL_CF_ACT_REPLACE_IF_EXISTS + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS)) + || ( SHFL_CF_ACT_CREATE_IF_NEW + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW))) + { + RTFileSetTimes(pHandle->file.Handle, + &pParms->Info.AccessTime, + &pParms->Info.ModificationTime, + &pParms->Info.ChangeTime, + &pParms->Info.BirthTime + ); + + RTFileSetMode (pHandle->file.Handle, pParms->Info.Attr.fMode); + } +#endif + RTFSOBJINFO info; + + /* Get file information */ + rc = RTFileQueryInfo(pHandle->file.Handle, &info, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + } + /* Free resources if any part of the function has failed. */ + if (RT_FAILURE(rc)) + { + if ( (0 != pHandle) + && (NIL_RTFILE != pHandle->file.Handle) + && (0 != pHandle->file.Handle)) + { + RTFileClose(pHandle->file.Handle); + pHandle->file.Handle = NIL_RTFILE; + } + if (SHFL_HANDLE_NIL != handle) + { + vbsfFreeFileHandle(pClient, handle); + } + pParms->Handle = SHFL_HANDLE_NIL; + } + else + { + pParms->Handle = handle; + } + + /* Report the driver that all is okay, we're done here */ + if (fNoError) + rc = VINF_SUCCESS; + + LogFlow(("vbsfOpenFile: rc = %Rrc\n", rc)); + return rc; +} + +/** + * Open a folder or create and open a new one. + * + * @returns IPRT status code + * @param pClient Data structure describing the client accessing the shared + * folder + * @param root The index of the shared folder in the table of mappings. + * @param pszPath Path to the file or folder on the host. + * @param pParms Input: @a CreateFlags Creation or open parameters, see + * include/VBox/shflsvc.h + * Output: + * - @a Result: Shared folder status code, see include/VBox/shflsvc.h + * - @a Handle: On success the (shared folder) handle of the folder opened or + * created + * - @a Info: On success the parameters of the folder opened or created + * + * @note folders are created with fMode = 0777 + */ +static int vbsfOpenDir(SHFLCLIENTDATA *pClient, SHFLROOT root, char *pszPath, + SHFLCREATEPARMS *pParms) +{ + LogFlow(("vbsfOpenDir: pszPath = %s, pParms = %p\n", pszPath, pParms)); + Log(("SHFL create flags %08x\n", pParms->CreateFlags)); + + int rc = VERR_NO_MEMORY; + SHFLHANDLE handle = vbsfAllocDirHandle(pClient); + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, handle); + if (0 != pHandle) + { + pHandle->root = root; + pParms->Result = SHFL_FILE_EXISTS; /* May be overwritten with SHFL_FILE_CREATED. */ + /** @todo Can anyone think of a sensible, race-less way to do this? Although + I suspect that the race is inherent, due to the API available... */ + /* Try to create the folder first if "create if new" is specified. If this + fails, and "open if exists" is specified, then we ignore the failure and try + to open the folder anyway. */ + if ( SHFL_CF_ACT_CREATE_IF_NEW + == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW)) + { + /** @todo render supplied attributes. + * bird: The guest should specify this. For windows guests RTFS_DOS_DIRECTORY should suffice. */ + RTFMODE fMode = 0777; + + pParms->Result = SHFL_FILE_CREATED; + rc = RTDirCreate(pszPath, fMode, 0); + if (RT_FAILURE(rc)) + { + /** @todo we still return 'rc' as failure here, so this is mostly pointless. */ + switch (rc) + { + case VERR_ALREADY_EXISTS: + pParms->Result = SHFL_FILE_EXISTS; + break; + case VERR_PATH_NOT_FOUND: + pParms->Result = SHFL_PATH_NOT_FOUND; + break; + case VERR_FILE_NOT_FOUND: /* may happen on posix */ + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VERR_PATH_NOT_FOUND; + } +#endif + break; + default: + pParms->Result = SHFL_NO_RESULT; + } + } + } + else + rc = VINF_SUCCESS; + if ( RT_SUCCESS(rc) + || (SHFL_CF_ACT_OPEN_IF_EXISTS == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS))) + { + /* Open the directory now */ + rc = RTDirOpenFiltered(&pHandle->dir.Handle, pszPath, RTDIRFILTER_NONE, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO info; + + rc = RTDirQueryInfo(pHandle->dir.Handle, &info, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + } + } + else + { + /** @todo we still return 'rc' as failure here, so this is mostly pointless. */ + switch (rc) + { + case VERR_FILE_NOT_FOUND: + pParms->Result = SHFL_FILE_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszPath)) + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VERR_PATH_NOT_FOUND; + } +#endif + break; + case VERR_PATH_NOT_FOUND: + pParms->Result = SHFL_PATH_NOT_FOUND; +#ifndef RT_OS_WINDOWS + if ( SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsNotADirectory(pszPath)) + { + pParms->Result = SHFL_FILE_EXISTS; + rc = VERR_NOT_A_DIRECTORY; + break; + } +#endif + break; + case VERR_ACCESS_DENIED: + pParms->Result = SHFL_FILE_EXISTS; + break; + default: + pParms->Result = SHFL_NO_RESULT; + } + } + } + } + if (RT_FAILURE(rc)) + { + if ( (0 != pHandle) + && (0 != pHandle->dir.Handle)) + { + RTDirClose(pHandle->dir.Handle); + pHandle->dir.Handle = 0; + } + if (SHFL_HANDLE_NIL != handle) + { + vbsfFreeFileHandle(pClient, handle); + } + pParms->Handle = SHFL_HANDLE_NIL; + } + else + { + pParms->Handle = handle; + } + LogFlow(("vbsfOpenDir: rc = %Rrc\n", rc)); + return rc; +} + +static int vbsfCloseDir(SHFLFILEHANDLE *pHandle) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCloseDir: Handle = %08X Search Handle = %08X\n", + pHandle->dir.Handle, pHandle->dir.SearchHandle)); + + RTDirClose(pHandle->dir.Handle); + + if (pHandle->dir.SearchHandle) + RTDirClose(pHandle->dir.SearchHandle); + + if (pHandle->dir.pLastValidEntry) + { + RTMemFree(pHandle->dir.pLastValidEntry); + pHandle->dir.pLastValidEntry = NULL; + } + + LogFlow(("vbsfCloseDir: rc = %d\n", rc)); + + return rc; +} + + +static int vbsfCloseFile(SHFLFILEHANDLE *pHandle) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCloseFile: Handle = %08X\n", + pHandle->file.Handle)); + + rc = RTFileClose(pHandle->file.Handle); + + LogFlow(("vbsfCloseFile: rc = %d\n", rc)); + + return rc; +} + +/** + * Look up file or folder information by host path. + * + * @returns iprt status code (currently VINF_SUCCESS) + * @param pClient client data + * @param pszPath The path of the file to be looked up + * @param pParms Output: + * - @a Result: Status of the operation (success or error) + * - @a Info: On success, information returned about the + * file + */ +static int vbsfLookupFile(SHFLCLIENTDATA *pClient, char *pszPath, SHFLCREATEPARMS *pParms) +{ + RTFSOBJINFO info; + int rc; + + rc = RTPathQueryInfoEx(pszPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + LogFlow(("SHFL_CF_LOOKUP\n")); + /* Client just wants to know if the object exists. */ + switch (rc) + { + case VINF_SUCCESS: + { +#ifdef RT_OS_WINDOWS + info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pParms->Info, &info); + pParms->Result = SHFL_FILE_EXISTS; + break; + } + + case VERR_FILE_NOT_FOUND: + { + pParms->Result = SHFL_FILE_NOT_FOUND; + rc = VINF_SUCCESS; + break; + } + + case VERR_PATH_NOT_FOUND: + { + pParms->Result = SHFL_PATH_NOT_FOUND; + rc = VINF_SUCCESS; + break; + } + } + pParms->Handle = SHFL_HANDLE_NIL; + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_CREATE API. Located here as a form of API + * documentation. */ +void testCreate(RTTEST hTest) +{ + /* Simple opening of an existing file. */ + testCreateFileSimple(hTest); + testCreateFileSimpleCaseInsensitive(hTest); + /* Simple opening of an existing directory. */ + /** @todo How do wildcards in the path name work? */ + testCreateDirSimple(hTest); + /* If the number or types of parameters are wrong the API should fail. */ + testCreateBadParameters(hTest); + /* Add tests as required... */ +} +#endif + +/** + * Create or open a file or folder. Perform character set and case + * conversion on the file name if necessary. + * + * @returns IPRT status code, but see note below + * @param pClient Data structure describing the client accessing the + * shared folder + * @param root The index of the shared folder in the table of mappings. + * The host path of the shared folder is found using this. + * @param pPath The path of the file or folder relative to the host path + * indexed by root. + * @param cbPath Presumably the length of the path in pPath. Actually + * ignored, as pPath contains a length parameter. + * @param pParms Input: If a new file is created or an old one + * overwritten, set the @a Info attribute. + * + * Output: + * - @a Result Shared folder result code, see include/VBox/shflsvc.h + * - @a Handle Shared folder handle to the newly opened file + * - @a Info Attributes of the file or folder opened + * + * @note This function returns success if a "non-exceptional" error occurred, + * such as "no such file". In this case, the caller should check the + * pParms->Result return value and whether pParms->Handle is valid. + */ +int vbsfCreate(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, SHFLCREATEPARMS *pParms) +{ + int rc = VINF_SUCCESS; + + LogFlow(("vbsfCreate: pClient = %p, pPath = %p, cbPath = %d, pParms = %p CreateFlags=%x\n", + pClient, pPath, cbPath, pParms, pParms->CreateFlags)); + + /* Check the client access rights to the root. */ + /** @todo */ + + /* Build a host full path for the given path, handle file name case issues (if the guest + * expects case-insensitive paths but the host is case-sensitive) and convert ucs2 to utf8 if + * necessary. + */ + char *pszFullPath = NULL; + uint32_t cbFullPathRoot = 0; + + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, &cbFullPathRoot); + if (RT_SUCCESS(rc)) + { + /* Reset return value in case client forgot to do so. + * pParms->Handle must not be reset here, as it is used + * in vbsfOpenFile to detect old additions. + */ + pParms->Result = SHFL_NO_RESULT; + + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_LOOKUP)) + { + rc = vbsfLookupFile(pClient, pszFullPath, pParms); + } + else + { + /* Query path information. */ + RTFSOBJINFO info; + + rc = RTPathQueryInfoEx(pszFullPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + LogFlow(("RTPathQueryInfoEx returned %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + /* Mark it as a directory in case the caller didn't. */ + /** + * @todo I left this in in order not to change the behaviour of the + * function too much. Is it really needed, and should it really be + * here? + */ + if (BIT_FLAG(info.Attr.fMode, RTFS_DOS_DIRECTORY)) + { + pParms->CreateFlags |= SHFL_CF_DIRECTORY; + } + + /** + * @todo This should be in the Windows Guest Additions, as no-one else + * needs it. + */ + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_OPEN_TARGET_DIRECTORY)) + { + vbsfStripLastComponent(pszFullPath, cbFullPathRoot); + pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_EXISTS; + pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_NEW; + pParms->CreateFlags |= SHFL_CF_DIRECTORY; + pParms->CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS; + pParms->CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW; + } + } + + rc = VINF_SUCCESS; + + /* Note: do not check the SHFL_CF_ACCESS_WRITE here, only check if the open operation + * will cause changes. + * + * Actual operations (write, set attr, etc), which can write to a shared folder, have + * the check and will return VERR_WRITE_PROTECT if the folder is not writable. + */ + if ( (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_REPLACE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_EXISTS) == SHFL_CF_ACT_OVERWRITE_IF_EXISTS + || (pParms->CreateFlags & SHFL_CF_ACT_MASK_IF_NEW) == SHFL_CF_ACT_CREATE_IF_NEW + ) + { + /* is the guest allowed to write to this share? */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc) || !fWritable) + rc = VERR_WRITE_PROTECT; + } + + if (RT_SUCCESS(rc)) + { + if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_DIRECTORY)) + { + rc = vbsfOpenDir(pClient, root, pszFullPath, pParms); + } + else + { + rc = vbsfOpenFile(pClient, root, pszFullPath, pParms); + } + } + else + { + pParms->Handle = SHFL_HANDLE_NIL; + } + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + + Log(("vbsfCreate: handle = %RX64 rc = %Rrc result=%x\n", (uint64_t)pParms->Handle, rc, pParms->Result)); + + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_CLOSE API. Located here as a form of API + * documentation. */ +void testClose(RTTEST hTest) +{ + /* If the API parameters are invalid the API should fail. */ + testCloseBadParameters(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfClose(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle) +{ + LogFunc(("pClient = %p, root 0x%RX32, Handle = 0x%RX64\n", + pClient, root, Handle)); + + int rc = VERR_INVALID_HANDLE; + uint32_t type = vbsfQueryHandleType(pClient, Handle); + Assert((type & ~(SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) == 0); + switch (type & (SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) + { + case SHFL_HF_TYPE_DIR: + { + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + if (RT_LIKELY(pHandle && root == pHandle->root)) + { + rc = vbsfCloseDir(pHandle); + vbsfFreeFileHandle(pClient, Handle); + } + break; + } + case SHFL_HF_TYPE_FILE: + { + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + if (RT_LIKELY(pHandle && root == pHandle->root)) + { + rc = vbsfCloseFile(pHandle); + vbsfFreeFileHandle(pClient, Handle); + } + break; + } + default: + break; + } + + LogFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** + * Helper for vbsfReadPages and vbsfWritePages that creates a S/G buffer from a + * pages parameter. + */ +static int vbsfPagesToSgBuf(VBOXHGCMSVCPARMPAGES const *pPages, uint32_t cbLeft, PRTSGBUF pSgBuf) +{ + PRTSGSEG paSegs = (PRTSGSEG)RTMemTmpAlloc(sizeof(paSegs[0]) * pPages->cPages); + if (paSegs) + { + /* + * Convert the pages to segments. + */ + uint32_t iSeg = 0; + uint32_t iPage = 0; + for (;;) + { + Assert(iSeg < pPages->cPages); + Assert(iPage < pPages->cPages); + + /* Current page. */ + void *pvSeg; + paSegs[iSeg].pvSeg = pvSeg = pPages->papvPages[iPage]; + uint32_t cbSeg = PAGE_SIZE - (uint32_t)((uintptr_t)pvSeg & PAGE_OFFSET_MASK); + iPage++; + + /* Adjacent to the next page? */ + while ( iPage < pPages->cPages + && (uintptr_t)pvSeg + cbSeg == (uintptr_t)pPages->papvPages[iPage]) + { + iPage++; + cbSeg += PAGE_SIZE; + } + + /* Adjust for max size. */ + if (cbLeft <= cbSeg) + { + paSegs[iSeg++].cbSeg = cbLeft; + break; + } + paSegs[iSeg++].cbSeg = cbSeg; + cbLeft -= cbSeg; + } + + /* + * Initialize the s/g buffer and execute the read. + */ + RTSgBufInit(pSgBuf, paSegs, iSeg); + return VINF_SUCCESS; + } + pSgBuf->paSegs = NULL; + return VERR_NO_TMP_MEMORY; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_READ API. Located here as a form of API + * documentation. */ +void testRead(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testReadBadParameters(hTest); + /* Basic reading from a file. */ + testReadFileSimple(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRead(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64, offset 0x%RX64, bytes 0x%RX32\n", + pClient, root, Handle, offset, pcbBuffer? *pcbBuffer: 0)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + size_t const cbToRead = *pcbBuffer; + if (RT_LIKELY(cbToRead > 0)) + { + size_t cbActual = 0; + rc = RTFileReadAt(pHandle->file.Handle, offset, pBuffer, cbToRead, &cbActual); + *pcbBuffer = (uint32_t)cbActual; + } + else + { + /* Reading zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbBuffer = 0; + + LogFunc(("%Rrc bytes read 0x%RX32\n", rc, *pcbBuffer)); + return rc; +} + +/** + * SHFL_FN_READ w/o bounce buffering. + */ +int vbsfReadPages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t offFile, + uint32_t *pcbRead, PVBOXHGCMSVCPARMPAGES pPages) +{ + LogFunc(("pClient %p, idRoot %#RX32, hFile %#RX64, offFile %#RX64, cbRead %#RX32, cPages %#x\n", + pClient, idRoot, hFile, offFile, *pcbRead, pPages->cPages)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + size_t cbTotal = 0; + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + uint32_t const cbToRead = *pcbRead; + if (cbToRead > 0) + { + ASSERT_GUEST_RETURN(pPages->cPages > 0, VERR_INTERNAL_ERROR_3); + + /* + * Convert to a scatter-gather buffer. + * + * We need not do any platform specific code here as the RTSGBUF + * segment array maps directly onto the posix iovec structure. + * Windows does currently benefit much from this conversion, but + * so be it. + */ + RTSGBUF SgBuf; + rc = vbsfPagesToSgBuf(pPages, cbToRead, &SgBuf); + if (RT_SUCCESS(rc)) + { + rc = RTFileSgReadAt(pHandle->file.Handle, offFile, &SgBuf, cbToRead, &cbTotal); + while (rc == VERR_INTERRUPTED) + { + RTSgBufReset(&SgBuf); + rc = RTFileSgReadAt(pHandle->file.Handle, offFile, &SgBuf, cbToRead, &cbTotal); + } + + RTMemTmpFree((void *)SgBuf.paSegs); + } + else + rc = VERR_NO_TMP_MEMORY; + + *pcbRead = (uint32_t)cbTotal; + } + else + { + /* Reading zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbRead = 0; + + LogFunc(("%Rrc bytes read %#zx\n", rc, cbTotal)); + return rc; +} + +/** + * Helps with writes to RTFILE_O_APPEND files. + */ +static uint64_t vbsfWriteCalcPostAppendFilePosition(RTFILE hFile, uint64_t offGuessed) +{ + RTFSOBJINFO ObjInfo; + int rc2 = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc2) && (uint64_t)ObjInfo.cbObject >= offGuessed) + return ObjInfo.cbObject; + return offGuessed; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_WRITE API. Located here as a form of API + * documentation. */ +void testWrite(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testWriteBadParameters(hTest); + /* Simple test of writing to a file. */ + testWriteFileSimple(hTest); + /* Add tests as required... */ +} +#endif +int vbsfWrite(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + uint64_t offFile = *poffFile; + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64, offFile 0x%RX64, bytes 0x%RX32\n", + pClient, idRoot, hFile, offFile, *pcbBuffer)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + size_t const cbToWrite = *pcbBuffer; + if (RT_LIKELY(cbToWrite != 0)) + { + size_t cbWritten = 0; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + rc = RTFileWriteAt(pHandle->file.Handle, offFile, pBuffer, cbToWrite, &cbWritten); + else + { + rc = RTFileSeek(pHandle->file.Handle, offFile, RTFILE_SEEK_BEGIN, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(pHandle->file.Handle, pBuffer, cbToWrite, &cbWritten); + *pcbBuffer = (uint32_t)cbWritten; + } + } + + /* Update the file offset (mainly for RTFILE_O_APPEND), */ + if (RT_SUCCESS(rc)) + { + offFile += cbWritten; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + *poffFile = offFile; + else + *poffFile = vbsfWriteCalcPostAppendFilePosition(pHandle->file.Handle, offFile); + } + } + else + { + /** @todo What writing zero bytes should do? */ + rc = VINF_SUCCESS; + } + } + else + *pcbBuffer = 0; + LogFunc(("%Rrc bytes written 0x%RX32\n", rc, *pcbBuffer)); + return rc; +} + +/** + * SHFL_FN_WRITE w/o bounce buffering. + */ +int vbsfWritePages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbWrite, PVBOXHGCMSVCPARMPAGES pPages) +{ + uint64_t offFile = *poffFile; + LogFunc(("pClient %p, idRoot %#RX32, hFile %#RX64, offFile %#RX64, cbWrite %#RX32, cPages %#x\n", + pClient, idRoot, hFile, offFile, *pcbWrite, pPages->cPages)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + size_t cbTotal = 0; + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hFile); + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + uint32_t const cbToWrite = *pcbWrite; + if (cbToWrite > 0) + { + ASSERT_GUEST_RETURN(pPages->cPages > 0, VERR_INTERNAL_ERROR_3); + + /* + * Convert to a scatter-gather buffer. + * + * We need not do any platform specific code here as the RTSGBUF + * segment array maps directly onto the posix iovec structure. + * Windows does currently benefit much from this conversion, but + * so be it. + */ + RTSGBUF SgBuf; + rc = vbsfPagesToSgBuf(pPages, cbToWrite, &SgBuf); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_LINUX + /* Cannot use RTFileSgWriteAt or RTFileWriteAt when opened with + RTFILE_O_APPEND, except for on linux where the offset is + then ignored by the low level kernel API. */ + if (pHandle->file.fOpenFlags & RTFILE_O_APPEND) + { + /* paranoia */ + RTFileSeek(pHandle->file.Handle, 0, RTFILE_SEEK_END, NULL); + + for (size_t iSeg = 0; iSeg < SgBuf.cSegs; iSeg++) + { + size_t cbWrittenNow = 0; + do + rc = RTFileWrite(pHandle->file.Handle, SgBuf.paSegs[iSeg].pvSeg, + SgBuf.paSegs[iSeg].cbSeg, &cbWrittenNow); + while (rc == VERR_INTERRUPTED); + if (RT_SUCCESS(rc)) + { + cbTotal += cbWrittenNow; + if (cbWrittenNow < SgBuf.paSegs[iSeg].cbSeg) + break; + } + else + { + if (cbTotal > 0) + rc = VINF_SUCCESS; + break; + } + } + } + else +#endif + { + rc = RTFileSgWriteAt(pHandle->file.Handle, offFile, &SgBuf, cbToWrite, &cbTotal); + while (rc == VERR_INTERRUPTED) + { + RTSgBufReset(&SgBuf); + rc = RTFileSgWriteAt(pHandle->file.Handle, offFile, &SgBuf, cbToWrite, &cbTotal); + } + } + + RTMemTmpFree((void *)SgBuf.paSegs); + + /* Update the file offset (mainly for RTFILE_O_APPEND), */ + if (RT_SUCCESS(rc)) + { + offFile += cbTotal; + if (!(pHandle->file.fOpenFlags & RTFILE_O_APPEND)) + *poffFile = offFile; + else + *poffFile = vbsfWriteCalcPostAppendFilePosition(pHandle->file.Handle, offFile); + } + } + else + rc = VERR_NO_TMP_MEMORY; + + *pcbWrite = (uint32_t)cbTotal; + } + else + { + /* Writing zero bytes always succeeds. */ + rc = VINF_SUCCESS; + } + } + else + *pcbWrite = 0; + + LogFunc(("%Rrc bytes written %#zx\n", rc, cbTotal)); + return rc; +} + +/** + * Implements SHFL_FN_COPY_FILE_PART (wrapping RTFileCopyPart). + */ +int vbsfCopyFilePart(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, SHFLHANDLE hFileSrc, uint64_t offSrc, + SHFLROOT idRootDst, SHFLHANDLE hFileDst, uint64_t offDst, uint64_t *pcbToCopy, uint32_t fFlags) +{ + /* + * Validate and translates handles. + */ + uint64_t const cbToCopy = *pcbToCopy; + *pcbToCopy = 0; + LogFunc(("pClient %p, idRootSrc %#RX32, hFileSrc %#RX64, offSrc %#RX64, idRootSrc %#RX32, hFileSrc %#RX64, offSrc %#RX64, cbToCopy %#RX64, fFlags %#x\n", + pClient, idRootSrc, hFileSrc, offSrc, idRootDst, hFileDst, offDst, cbToCopy, fFlags)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + uint64_t cbTotal = 0; + + SHFLFILEHANDLE *pHandleSrc = vbsfQueryFileHandle(pClient, hFileSrc); + int rc = vbsfCheckHandleAccess(pClient, idRootSrc, pHandleSrc, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { + SHFLFILEHANDLE *pHandleDst = vbsfQueryFileHandle(pClient, hFileDst); + rc = vbsfCheckHandleAccess(pClient, idRootDst, pHandleDst, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + /* + * Do the job. + */ + rc = RTFileCopyPart(pHandleSrc->file.Handle, offSrc, pHandleDst->file.Handle, offDst, cbToCopy, 0, &cbTotal); + *pcbToCopy = cbTotal; + } + } + + RT_NOREF(fFlags); + LogFunc(("%Rrc bytes written %#zx\n", rc, cbTotal)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_FLUSH API. Located here as a form of API + * documentation. */ +void testFlush(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testFlushBadParameters(hTest); + /* Simple opening and flushing of a file. */ + testFlushFileSimple(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfFlush(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle) +{ + LogFunc(("pClient %p, root 0x%RX32, Handle 0x%RX64\n", + pClient, root, Handle)); + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + rc = RTFileFlush(pHandle->file.Handle); + + LogFunc(("%Rrc\n", rc)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_LIST API. Located here as a form of API + * documentation. */ +void testDirList(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testDirListBadParameters(hTest); + /* Test listing an empty directory (simple edge case). */ + testDirListEmpty(hTest); + /* Add tests as required... */ +} +#endif +int vbsfDirList(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, SHFLSTRING *pPath, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer, uint32_t *pIndex, uint32_t *pcFiles) +{ + PRTDIRENTRYEX pDirEntry = 0, pDirEntryOrg; + uint32_t cbDirEntry, cbBufferOrg; + PSHFLDIRINFO pSFDEntry; + PRTUTF16 pwszString; + RTDIR hDir; + const bool fUtf8 = BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8) != 0; + + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + Assert(*pIndex == 0); + + cbDirEntry = 4096; + pDirEntryOrg = pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry); + if (pDirEntry == 0) + { + AssertFailed(); + return VERR_NO_MEMORY; + } + + cbBufferOrg = *pcbBuffer; + *pcbBuffer = 0; + pSFDEntry = (PSHFLDIRINFO)pBuffer; + + *pIndex = 1; /* not yet complete */ + *pcFiles = 0; + + if (!pPath) + hDir = pHandle->dir.Handle; + else + { + if (pHandle->dir.SearchHandle == 0) + { + /* Build a host full path for the given path + * and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + + Assert(pHandle->dir.pLastValidEntry == 0); + + rc = vbsfBuildFullPath(pClient, root, pPath, pPath->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPath, NULL, true); + + if (RT_SUCCESS(rc)) + { + rc = RTDirOpenFiltered(&pHandle->dir.SearchHandle, pszFullPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + + if (RT_FAILURE(rc)) + goto end; + } + else + goto end; + flags &= ~SHFL_LIST_RESTART; + } + Assert(pHandle->dir.SearchHandle); + hDir = pHandle->dir.SearchHandle; + } + + if (flags & SHFL_LIST_RESTART) + { + rc = RTDirRewind(hDir); + if (RT_FAILURE(rc)) + goto end; + } + + while (cbBufferOrg) + { + size_t cbDirEntrySize = cbDirEntry; + uint32_t cbNeeded; + + /* Do we still have a valid last entry for the active search? If so, then return it here */ + if (pHandle->dir.pLastValidEntry) + { + pDirEntry = pHandle->dir.pLastValidEntry; + } + else + { + pDirEntry = pDirEntryOrg; + + rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + if (rc == VERR_NO_MORE_FILES) + { + *pIndex = 0; /* listing completed */ + break; + } + + if ( rc != VINF_SUCCESS + && rc != VWRN_NO_DIRENT_INFO) + { + //AssertFailed(); + if ( rc == VERR_NO_TRANSLATION + || rc == VERR_INVALID_UTF8_ENCODING) + continue; + break; + } + } + + cbNeeded = RT_OFFSETOF(SHFLDIRINFO, name.String); + if (fUtf8) + cbNeeded += pDirEntry->cbName + 1; + else + /* Overestimating, but that's ok */ + cbNeeded += (pDirEntry->cbName + 1) * 2; + + if (cbBufferOrg < cbNeeded) + { + /* No room, so save this directory entry, or else it's lost forever */ + pHandle->dir.pLastValidEntry = pDirEntry; + + if (*pcFiles == 0) + { + AssertFailed(); + return VINF_BUFFER_OVERFLOW; /* Return directly and don't free pDirEntry */ + } + return VINF_SUCCESS; /* Return directly and don't free pDirEntry */ + } + +#ifdef RT_OS_WINDOWS + pDirEntry->Info.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(&pSFDEntry->Info, &pDirEntry->Info); + + /* The shortname (only used by OS/2 atm): */ + Assert(pDirEntry->cwcShortName < RT_ELEMENTS(pSFDEntry->uszShortName)); + Assert(pDirEntry->wszShortName[pDirEntry->cwcShortName] == '\0'); + pSFDEntry->cucShortName = pDirEntry->cwcShortName; + if (pDirEntry->cwcShortName) + memcpy(pSFDEntry->uszShortName, pDirEntry->wszShortName, sizeof(pSFDEntry->uszShortName)); + + /* The name: */ + if (fUtf8) + { + void *src, *dst; + + src = &pDirEntry->szName[0]; + dst = &pSFDEntry->name.String.utf8[0]; + + memcpy(dst, src, pDirEntry->cbName + 1); + + pSFDEntry->name.u16Size = pDirEntry->cbName + 1; + pSFDEntry->name.u16Length = pDirEntry->cbName; + } + else + { + pSFDEntry->name.String.ucs2[0] = 0; + pwszString = pSFDEntry->name.String.ucs2; + int rc2 = RTStrToUtf16Ex(pDirEntry->szName, RTSTR_MAX, &pwszString, pDirEntry->cbName+1, NULL); + AssertRC(rc2); + +#ifdef RT_OS_DARWIN +/** @todo This belongs in rtPathToNative or in the windows shared folder file system driver... + * The question is simply whether the NFD normalization is actually applied on a (virtual) file + * system level in darwin, or just by the user mode application libs. */ + { + // Convert to + // Normalization Form C (composed Unicode). We need this because + // Mac OS X file system uses NFD (Normalization Form D :decomposed Unicode) + // while most other OS', server-side programs usually expect NFC. + uint16_t ucs2Length; + CFRange rangeCharacters; + CFMutableStringRef inStr = ::CFStringCreateMutable(NULL, 0); + + ::CFStringAppendCharacters(inStr, (UniChar *)pwszString, RTUtf16Len(pwszString)); + ::CFStringNormalize(inStr, kCFStringNormalizationFormC); + ucs2Length = ::CFStringGetLength(inStr); + + rangeCharacters.location = 0; + rangeCharacters.length = ucs2Length; + ::CFStringGetCharacters(inStr, rangeCharacters, pwszString); + pwszString[ucs2Length] = 0x0000; // NULL terminated + + CFRelease(inStr); + } +#endif + pSFDEntry->name.u16Length = (uint32_t)RTUtf16Len(pSFDEntry->name.String.ucs2) * 2; + pSFDEntry->name.u16Size = pSFDEntry->name.u16Length + 2; + + Log(("SHFL: File name size %d\n", pSFDEntry->name.u16Size)); + Log(("SHFL: File name %ls\n", &pSFDEntry->name.String.ucs2)); + + // adjust cbNeeded (it was overestimated before) + cbNeeded = RT_OFFSETOF(SHFLDIRINFO, name.String) + pSFDEntry->name.u16Size; + } + + /* Advance */ + pSFDEntry = (PSHFLDIRINFO)((uintptr_t)pSFDEntry + cbNeeded); + *pcbBuffer += cbNeeded; + cbBufferOrg-= cbNeeded; + + *pcFiles += 1; + + /* Free the saved last entry, that we've just returned */ + if (pHandle->dir.pLastValidEntry) + { + RTMemFree(pHandle->dir.pLastValidEntry); + pHandle->dir.pLastValidEntry = NULL; + + /* And use the newly allocated buffer from now. */ + pDirEntry = pDirEntryOrg; + } + + if (flags & SHFL_LIST_RETURN_ONE) + break; /* we're done */ + } + Assert(rc != VINF_SUCCESS || *pcbBuffer > 0); + +end: + if (pDirEntry) + RTMemFree(pDirEntry); + + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_READLINK API. Located here as a form of API + * documentation. */ +void testReadLink(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testReadLinkBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfReadLink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, uint8_t *pBuffer, uint32_t cbBuffer) +{ + int rc = VINF_SUCCESS; + + if (pPath == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Build a host full path for the given path, handle file name case issues + * (if the guest expects case-insensitive paths but the host is + * case-sensitive) and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + uint32_t cbFullPathRoot = 0; + + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, &cbFullPathRoot); + + if (RT_SUCCESS(rc)) + { + rc = RTSymlinkRead(pszFullPath, (char *) pBuffer, cbBuffer, 0); + if (RT_SUCCESS(rc)) + { + /* Convert the slashes in the link target to the guest path separator characters. */ + /** @todo r=bird: for some messed up reason, we return UTF-8 here rather than + * the character set selected by the client. We also don't return the + * length, so the clients are paranoid about the zero termination behavior. */ + char ch; + char *psz = (char *)pBuffer; + while ((ch = *psz) != '\0') + { + if (RTPATH_IS_SLASH(ch)) + *psz = pClient->PathDelimiter; + psz++; + } + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + + return rc; +} + +int vbsfQueryFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + uint32_t type = vbsfQueryHandleType(pClient, Handle); + int rc = VINF_SUCCESS; + SHFLFSOBJINFO *pObjInfo = (SHFLFSOBJINFO *)pBuffer; + RTFSOBJINFO fileinfo; + + + AssertReturn(type == SHFL_HF_TYPE_DIR || type == SHFL_HF_TYPE_FILE, VERR_INVALID_PARAMETER); + AssertReturn(pcbBuffer != NULL, VERR_INVALID_PARAMETER); + AssertReturn(pObjInfo != NULL, VERR_INVALID_PARAMETER); + AssertReturn(*pcbBuffer >= sizeof(SHFLFSOBJINFO), VERR_INVALID_PARAMETER); + + /** @todo other options */ + Assert(flags == (SHFL_INFO_GET|SHFL_INFO_FILE)); + + *pcbBuffer = 0; + + if (type == SHFL_HF_TYPE_DIR) + { + SHFLFILEHANDLE *pHandle = vbsfQueryDirHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + rc = RTDirQueryInfo(pHandle->dir.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); + } + else + { + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + rc = RTFileQueryInfo(pHandle->file.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); +#ifdef RT_OS_WINDOWS + if (RT_SUCCESS(rc) && RTFS_IS_FILE(pObjInfo->Attr.fMode)) + pObjInfo->Attr.fMode |= 0111; +#endif + } + if (rc == VINF_SUCCESS) + { + vbfsCopyFsObjInfoFromIprt(pObjInfo, &fileinfo); + *pcbBuffer = sizeof(SHFLFSOBJINFO); + } + else + AssertFailed(); + + return rc; +} + +static int vbsfSetFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + uint32_t type = vbsfQueryHandleType(pClient, Handle); + int rc = VINF_SUCCESS; + SHFLFSOBJINFO *pSFDEntry; + + if ( !(type == SHFL_HF_TYPE_DIR || type == SHFL_HF_TYPE_FILE) + || pcbBuffer == 0 + || pBuffer == 0 + || *pcbBuffer < sizeof(SHFLFSOBJINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + *pcbBuffer = 0; + pSFDEntry = (SHFLFSOBJINFO *)pBuffer; + + Assert(flags == (SHFL_INFO_SET | SHFL_INFO_FILE)); + + /* + * Get the handle. + */ + SHFLFILEHANDLE *pHandle; + if (type == SHFL_HF_TYPE_FILE) + { + pHandle = vbsfQueryFileHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + } + else + { + Assert(type == SHFL_HF_TYPE_DIR); + pHandle = vbsfQueryDirHandle(pClient, Handle); + rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + } + if (RT_SUCCESS(rc)) + { + /* + * Any times to set? + */ + if ( RTTimeSpecGetNano(&pSFDEntry->AccessTime) + || RTTimeSpecGetNano(&pSFDEntry->ModificationTime) + || RTTimeSpecGetNano(&pSFDEntry->ChangeTime) + || RTTimeSpecGetNano(&pSFDEntry->BirthTime)) + { + + /* Change only the time values that are not zero */ + if (type == SHFL_HF_TYPE_FILE) + rc = RTFileSetTimes(pHandle->file.Handle, + RTTimeSpecGetNano(&pSFDEntry->AccessTime) ? &pSFDEntry->AccessTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ModificationTime) ? &pSFDEntry->ModificationTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ChangeTime) ? &pSFDEntry->ChangeTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->BirthTime) ? &pSFDEntry->BirthTime : NULL); + else + rc = RTDirSetTimes( pHandle->dir.Handle, + RTTimeSpecGetNano(&pSFDEntry->AccessTime) ? &pSFDEntry->AccessTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ModificationTime) ? &pSFDEntry->ModificationTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->ChangeTime) ? &pSFDEntry->ChangeTime : NULL, + RTTimeSpecGetNano(&pSFDEntry->BirthTime) ? &pSFDEntry->BirthTime : NULL); + if (RT_FAILURE(rc)) + { + Log(("RT%sSetTimes failed with %Rrc\n", type == SHFL_HF_TYPE_FILE ? "File" : "Dir", rc)); + Log(("AccessTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->AccessTime))); + Log(("ModificationTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->ModificationTime))); + Log(("ChangeTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->ChangeTime))); + Log(("BirthTime %#RX64\n", RTTimeSpecGetNano(&pSFDEntry->BirthTime))); + /* "temporary" hack */ + rc = VINF_SUCCESS; + } + } + + /* + * Any mode changes? + */ + if (pSFDEntry->Attr.fMode) + { + RTFMODE fMode = pSFDEntry->Attr.fMode; + + if (type == SHFL_HF_TYPE_FILE) + { +#ifndef RT_OS_WINDOWS + /* Don't allow the guest to clear the read own bit, otherwise the guest wouldn't + * be able to access this file anymore. Only for guests, which set the UNIX mode. + * Also, clear bits which we don't pass through for security reasons. */ + if (fMode & RTFS_UNIX_MASK) + { + fMode |= RTFS_UNIX_IRUSR; + fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT); + } +#endif + rc = RTFileSetMode(pHandle->file.Handle, fMode); + } + else + { +#ifndef RT_OS_WINDOWS + /* Don't allow the guest to clear the read+execute own bits, otherwise the guest + * wouldn't be able to access this directory anymore. Only for guests, which set + * the UNIX mode. Also, clear bits which we don't pass through for security reasons. */ + if (fMode & RTFS_UNIX_MASK) + { + fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IXUSR; + fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT /*?*/); + } +#endif + rc = RTDirSetMode(pHandle->dir.Handle, fMode); + } + if (RT_FAILURE(rc)) + { + Log(("RT%sSetMode %#x (%#x) failed with %Rrc\n", type == SHFL_HF_TYPE_FILE ? "File" : "Dir", + fMode, pSFDEntry->Attr.fMode, rc)); + /* silent failure, because this tends to fail with e.g. windows guest & linux host */ + rc = VINF_SUCCESS; + } + } + + /* + * Return the current file info on success. + */ + if (RT_SUCCESS(rc)) + { + uint32_t bufsize = sizeof(*pSFDEntry); + rc = vbsfQueryFileInfo(pClient, root, Handle, SHFL_INFO_GET | SHFL_INFO_FILE, &bufsize, (uint8_t *)pSFDEntry); + if (RT_SUCCESS(rc)) + *pcbBuffer = sizeof(SHFLFSOBJINFO); + else + AssertFailed(); + } + } + return rc; +} + + +/** + * Handles SHFL_FN_SET_FILE_SIZE. + */ +int vbsfSetFileSize(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hHandle, uint64_t cbNewSize) +{ + /* + * Resolve handle and validate write access. + */ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, hHandle); + ASSERT_GUEST_RETURN(pHandle, VERR_INVALID_HANDLE); + + int rc = vbsfCheckHandleAccess(pClient, idRoot, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { + /* + * Execute the request. + */ + rc = RTFileSetSize(pHandle->file.Handle, cbNewSize); + } + return rc; +} + + +static int vbsfSetEndOfFile(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, + uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF1(flags); + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + SHFLFSOBJINFO *pSFDEntry; + + if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(SHFLFSOBJINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_WRITE); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + *pcbBuffer = 0; + pSFDEntry = (SHFLFSOBJINFO *)pBuffer; + + if (flags & SHFL_INFO_SIZE) + { + rc = RTFileSetSize(pHandle->file.Handle, pSFDEntry->cbObject); + if (rc != VINF_SUCCESS) + AssertFailed(); + } + else + AssertFailed(); + + if (rc == VINF_SUCCESS) + { + RTFSOBJINFO fileinfo; + + /* Query the new object info and return it */ + rc = RTFileQueryInfo(pHandle->file.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING); + if (rc == VINF_SUCCESS) + { +#ifdef RT_OS_WINDOWS + fileinfo.Attr.fMode |= 0111; +#endif + vbfsCopyFsObjInfoFromIprt(pSFDEntry, &fileinfo); + *pcbBuffer = sizeof(SHFLFSOBJINFO); + } + else + AssertFailed(); + } + + return rc; +} + +int vbsfQueryVolumeInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + RT_NOREF2(root, flags); + int rc = VINF_SUCCESS; + SHFLVOLINFO *pSFDEntry; + char *pszFullPath = NULL; + union + { + SHFLSTRING Dummy; + uint8_t abDummy[SHFLSTRING_HEADER_SIZE + sizeof(RTUTF16)]; + } Buf; + + if (pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(SHFLVOLINFO)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /** @todo other options */ + Assert(flags == (SHFL_INFO_GET|SHFL_INFO_VOLUME)); + + *pcbBuffer = 0; + pSFDEntry = (PSHFLVOLINFO)pBuffer; + + ShflStringInitBuffer(&Buf.Dummy, sizeof(Buf)); + Buf.Dummy.String.ucs2[0] = '\0'; + rc = vbsfBuildFullPath(pClient, root, &Buf.Dummy, sizeof(Buf), &pszFullPath, NULL); + + if (RT_SUCCESS(rc)) + { + rc = RTFsQuerySizes(pszFullPath, &pSFDEntry->ullTotalAllocationBytes, &pSFDEntry->ullAvailableAllocationBytes, &pSFDEntry->ulBytesPerAllocationUnit, &pSFDEntry->ulBytesPerSector); + if (rc != VINF_SUCCESS) + goto exit; + + rc = RTFsQuerySerial(pszFullPath, &pSFDEntry->ulSerial); + if (rc != VINF_SUCCESS) + goto exit; + + RTFSPROPERTIES FsProperties; + rc = RTFsQueryProperties(pszFullPath, &FsProperties); + if (rc != VINF_SUCCESS) + goto exit; + vbfsCopyFsPropertiesFromIprt(&pSFDEntry->fsProperties, &FsProperties); + + *pcbBuffer = sizeof(SHFLVOLINFO); + } + else AssertFailed(); + +exit: + AssertMsg(rc == VINF_SUCCESS, ("failure: rc = %Rrc\n", rc)); + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + return rc; +} + +int vbsfQueryFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + if (pcbBuffer == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + if (flags & SHFL_INFO_FILE) + return vbsfQueryFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + + if (flags & SHFL_INFO_VOLUME) + return vbsfQueryVolumeInfo(pClient, root, flags, pcbBuffer, pBuffer); + + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_INFORMATION API. Located here as a form of API + * documentation. */ +void testFSInfo(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testFSInfoBadParameters(hTest); + /* Basic get and set file size test. */ + testFSInfoQuerySetFMode(hTest); + /* Basic get and set dir atime test. */ + testFSInfoQuerySetDirATime(hTest); + /* Basic get and set file atime test. */ + testFSInfoQuerySetFileATime(hTest); + /* Basic set end of file. */ + testFSInfoQuerySetEndOfFile(hTest); + /* Add tests as required... */ +} +#endif +int vbsfSetFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer) +{ + uint32_t type = vbsfQueryHandleType(pClient, Handle) + & (SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE|SHFL_HF_TYPE_VOLUME); + + if (type == 0 || pcbBuffer == 0 || pBuffer == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + if (flags & SHFL_INFO_FILE) + return vbsfSetFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + + if (flags & SHFL_INFO_SIZE) + return vbsfSetEndOfFile(pClient, root, Handle, flags, pcbBuffer, pBuffer); + +// if (flags & SHFL_INFO_VOLUME) +// return vbsfVolumeInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer); + AssertFailed(); + return VERR_INVALID_PARAMETER; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_LOCK API. Located here as a form of API + * documentation. */ +void testLock(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testLockBadParameters(hTest); + /* Simple file locking and unlocking test. */ + testLockFileSimple(hTest); + /* Add tests as required... */ +} +#endif + +int vbsfLock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags) +{ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + uint32_t fRTLock = 0; + + Assert((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL); + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + if ( ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL) + || (flags & SHFL_LOCK_ENTIRE) + ) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Lock type */ + switch(flags & SHFL_LOCK_MODE_MASK) + { + case SHFL_LOCK_SHARED: + fRTLock = RTFILE_LOCK_READ; + break; + + case SHFL_LOCK_EXCLUSIVE: + fRTLock = RTFILE_LOCK_READ | RTFILE_LOCK_WRITE; + break; + + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Lock wait type */ + if (flags & SHFL_LOCK_WAIT) + fRTLock |= RTFILE_LOCK_WAIT; + else + fRTLock |= RTFILE_LOCK_IMMEDIATELY; + +#ifdef RT_OS_WINDOWS + rc = RTFileLock(pHandle->file.Handle, fRTLock, offset, length); + if (rc != VINF_SUCCESS) + Log(("RTFileLock %RTfile %RX64 %RX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc)); +#else + Log(("vbsfLock: Pretend success handle=%x\n", Handle)); + rc = VINF_SUCCESS; + RT_NOREF2(offset, length); +#endif + return rc; +} + +int vbsfUnlock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags) +{ + SHFLFILEHANDLE *pHandle = vbsfQueryFileHandle(pClient, Handle); + + Assert((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL); + + int rc = vbsfCheckHandleAccess(pClient, root, pHandle, VBSF_CHECK_ACCESS_READ); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + return rc; + + if ( ((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL) + || (flags & SHFL_LOCK_ENTIRE) + ) + { + return VERR_INVALID_PARAMETER; + } + +#ifdef RT_OS_WINDOWS + rc = RTFileUnlock(pHandle->file.Handle, offset, length); + if (rc != VINF_SUCCESS) + Log(("RTFileUnlock %RTfile %RX64 %RTX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc)); +#else + Log(("vbsfUnlock: Pretend success handle=%x\n", Handle)); + rc = VINF_SUCCESS; + RT_NOREF2(offset, length); +#endif + + return rc; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_REMOVE API. Located here as a form of API + * documentation. */ +void testRemove(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testRemoveBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRemove(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, uint32_t cbPath, uint32_t flags, SHFLHANDLE hToClose) +{ + + /* Validate input */ + Assert(pPath); + AssertReturn(pPath->u16Size > 0, VERR_INVALID_PARAMETER); + + /* + * Close the handle if specified. + */ + int rc = VINF_SUCCESS; + if (hToClose != SHFL_HANDLE_NIL) + rc = vbsfClose(pClient, root, hToClose); + if (RT_SUCCESS(rc)) + { + /* + * Build a host full path for the given path and convert ucs2 to utf8 if necessary. + */ + char *pszFullPath = NULL; + rc = vbsfBuildFullPath(pClient, root, pPath, cbPath, &pszFullPath, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Is the guest allowed to write to this share? + */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_SUCCESS(rc) && fWritable) + { + /* + * Do the removal/deletion according to the type flags. + */ + if (flags & SHFL_REMOVE_SYMLINK) + rc = RTSymlinkDelete(pszFullPath, 0); + else if (flags & SHFL_REMOVE_FILE) + rc = RTFileDelete(pszFullPath); + else + rc = RTDirRemove(pszFullPath); + +#if 0 //ndef RT_OS_WINDOWS + /* There are a few adjustments to be made here: */ + if ( rc == VERR_FILE_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound(pszFullPath)) + rc = VERR_PATH_NOT_FOUND; + else if ( rc == VERR_PATH_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient)) + { + if (flags & (SHFL_REMOVE_FILE | SHFL_REMOVE_SYMLINK)) + { + size_t cchFullPath = strlen(pszFullPath); + if (cchFullPath > 0 && RTPATH_IS_SLASH(pszFullPath[cchFullPath - 1])) + rc = VERR_INVALID_NAME; + } + else if (vbsfErrorStyleIsWindowsNotADirectory(pszFullPath)) + rc = VERR_NOT_A_DIRECTORY; + } +#endif + } + else + rc = VERR_WRITE_PROTECT; + + /* free the path string */ + vbsfFreeFullPath(pszFullPath); + } + } + return rc; +} + + +#ifdef UNITTEST +/** Unit test the SHFL_FN_RENAME API. Located here as a form of API + * documentation. */ +void testRename(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testRenameBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfRename(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pSrc, SHFLSTRING *pDest, uint32_t flags) +{ + int rc = VINF_SUCCESS; + + /* Validate input */ + if ( flags & ~(SHFL_RENAME_FILE|SHFL_RENAME_DIR|SHFL_RENAME_REPLACE_IF_EXISTS) + || pSrc == 0 + || pDest == 0) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Build a host full path for the given path + * and convert ucs2 to utf8 if necessary. + */ + char *pszFullPathSrc = NULL; + char *pszFullPathDest = NULL; + + rc = vbsfBuildFullPath(pClient, root, pSrc, pSrc->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPathSrc, NULL); + if (rc != VINF_SUCCESS) + return rc; + + rc = vbsfBuildFullPath(pClient, root, pDest, pDest->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullPathDest, NULL, false, true); + if (RT_SUCCESS (rc)) + { + Log(("Rename %s to %s\n", pszFullPathSrc, pszFullPathDest)); + + /* is the guest allowed to write to this share? */ + bool fWritable; + rc = vbsfMappingsQueryWritable(pClient, root, &fWritable); + if (RT_FAILURE(rc) || !fWritable) + rc = VERR_WRITE_PROTECT; + + if (RT_SUCCESS(rc)) + { + if ((flags & (SHFL_RENAME_FILE | SHFL_RENAME_DIR)) == (SHFL_RENAME_FILE | SHFL_RENAME_DIR)) + { + rc = RTPathRename(pszFullPathSrc, pszFullPathDest, + flags & SHFL_RENAME_REPLACE_IF_EXISTS ? RTPATHRENAME_FLAGS_REPLACE : 0); + } + else if (flags & SHFL_RENAME_FILE) + { + rc = RTFileMove(pszFullPathSrc, pszFullPathDest, + ((flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTFILEMOVE_FLAGS_REPLACE : 0)); + } + else + { + /* NT ignores the REPLACE flag and simply return and already exists error. */ + rc = RTDirRename(pszFullPathSrc, pszFullPathDest, + ((flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTPATHRENAME_FLAGS_REPLACE : 0)); + } +#ifndef RT_OS_WINDOWS + if ( rc == VERR_FILE_NOT_FOUND + && SHFL_CLIENT_NEED_WINDOWS_ERROR_STYLE_ADJUST_ON_POSIX(pClient) + && vbsfErrorStyleIsWindowsPathNotFound2(pszFullPathSrc, pszFullPathDest)) + rc = VERR_PATH_NOT_FOUND; +#endif + } + + /* free the path string */ + vbsfFreeFullPath(pszFullPathDest); + } + /* free the path string */ + vbsfFreeFullPath(pszFullPathSrc); + return rc; +} + +/** + * Implements SHFL_FN_COPY_FILE (wrapping RTFileCopy). + */ +int vbsfCopyFile(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, PCSHFLSTRING pStrPathSrc, + SHFLROOT idRootDst, PCSHFLSTRING pStrPathDst, uint32_t fFlags) +{ + AssertPtrReturn(pClient, VERR_INVALID_PARAMETER); + if (pClient->fu32Flags & SHFL_CF_UTF8) + LogFunc(("pClient %p, idRootSrc %#RX32, '%.*s', idRootSrc %#RX32, '%.*s', fFlags %#x\n", pClient, idRootSrc, + pStrPathSrc->u16Length, pStrPathSrc->String.ach, idRootDst, pStrPathDst->u16Length, pStrPathDst->String.ach, fFlags)); + else + LogFunc(("pClient %p, idRootSrc %#RX32, '%.*ls', idRootSrc %#RX32, '%.*ls', fFlags %#x\n", pClient, + idRootSrc, pStrPathSrc->u16Length / sizeof(RTUTF16), pStrPathSrc->String.ach, + idRootDst, pStrPathDst->u16Length / sizeof(RTUTF16), pStrPathDst->String.ach, fFlags)); + + /* + * Build host paths. + */ + char *pszPathSrc = NULL; + int rc = vbsfBuildFullPath(pClient, idRootSrc, pStrPathSrc, pStrPathSrc->u16Size + SHFLSTRING_HEADER_SIZE, &pszPathSrc, NULL); + if (RT_SUCCESS(rc)) + { + char *pszPathDst = NULL; + rc = vbsfBuildFullPath(pClient, idRootDst, pStrPathDst, pStrPathDst->u16Size + SHFLSTRING_HEADER_SIZE, &pszPathDst, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Do the job. + */ + rc = RTFileCopy(pszPathSrc, pszPathDst); + + vbsfFreeFullPath(pszPathDst); + } + vbsfFreeFullPath(pszPathSrc); + } + + RT_NOREF(fFlags); + LogFunc(("returns %Rrc\n", rc)); + return rc; +} + +#ifdef UNITTEST +/** Unit test the SHFL_FN_SYMLINK API. Located here as a form of API + * documentation. */ +void testSymlink(RTTEST hTest) +{ + /* If the number or types of parameters are wrong the API should fail. */ + testSymlinkBadParameters(hTest); + /* Add tests as required... */ +} +#endif +int vbsfSymlink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pNewPath, SHFLSTRING *pOldPath, SHFLFSOBJINFO *pInfo) +{ + int rc = VINF_SUCCESS; + + char *pszFullNewPath = NULL; + char *pszFullOldPath = NULL; + + /* XXX: no support for UCS2 at the moment. */ + if (!BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + return VERR_NOT_IMPLEMENTED; + + bool fSymlinksCreate; + rc = vbsfMappingsQuerySymlinksCreate(pClient, root, &fSymlinksCreate); + AssertRCReturn(rc, rc); + if (!fSymlinksCreate) + return VERR_WRITE_PROTECT; /* XXX or VERR_TOO_MANY_SYMLINKS? */ + + rc = vbsfBuildFullPath(pClient, root, pNewPath, pNewPath->u16Size + SHFLSTRING_HEADER_SIZE, &pszFullNewPath, NULL); + AssertRCReturn(rc, rc); + + /* Verify that the link target can be a valid host path, i.e. does not contain invalid characters. */ + uint32_t fu32PathFlags = 0; + uint32_t fu32Options = 0; + rc = vbsfPathGuestToHost(pClient, root, pOldPath, pOldPath->u16Size + SHFLSTRING_HEADER_SIZE, + &pszFullOldPath, NULL, fu32Options, &fu32PathFlags); + if (RT_FAILURE(rc)) + { + vbsfFreeFullPath(pszFullNewPath); + return rc; + } + + /** @todo r=bird: We _must_ perform slash conversion on the target (what this + * code calls 'pOldPath' for some peculiar reason)! */ + + rc = RTSymlinkCreate(pszFullNewPath, (const char *)pOldPath->String.utf8, + RTSYMLINKTYPE_UNKNOWN, 0); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO info; + rc = RTPathQueryInfoEx(pszFullNewPath, &info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + vbfsCopyFsObjInfoFromIprt(pInfo, &info); + } + + vbsfFreeFullPath(pszFullOldPath); + vbsfFreeFullPath(pszFullNewPath); + + return rc; +} + +/* + * Clean up our mess by freeing all handles that are still valid. + * + */ +int vbsfDisconnect(SHFLCLIENTDATA *pClient) +{ + for (int i = 0; i < SHFLHANDLE_MAX; ++i) + { + SHFLFILEHANDLE *pHandle = NULL; + SHFLHANDLE Handle = (SHFLHANDLE)i; + + uint32_t type = vbsfQueryHandleType(pClient, Handle); + switch (type & (SHFL_HF_TYPE_DIR | SHFL_HF_TYPE_FILE)) + { + case SHFL_HF_TYPE_DIR: + { + pHandle = vbsfQueryDirHandle(pClient, Handle); + break; + } + case SHFL_HF_TYPE_FILE: + { + pHandle = vbsfQueryFileHandle(pClient, Handle); + break; + } + default: + break; + } + + if (pHandle) + { + LogFunc(("Opened handle 0x%08x\n", i)); + vbsfClose(pClient, pHandle->root, Handle); + } + } + + for (uint32_t i = 0; i < RT_ELEMENTS(pClient->acMappings); i++) + if (pClient->acMappings[i]) + { + uint16_t cMappings = pClient->acMappings[i]; + while (cMappings-- > 0) + vbsfUnmapFolder(pClient, i); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostServices/SharedFolders/vbsf.h b/src/VBox/HostServices/SharedFolders/vbsf.h new file mode 100644 index 00000000..04ef280a --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsf.h @@ -0,0 +1,68 @@ +/* $Id: vbsf.h $ */ +/** @file + * VBox Shared Folders header. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_vbsf_h +#define VBOX_INCLUDED_SRC_SharedFolders_vbsf_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include + +int vbsfCreate(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, SHFLCREATEPARMS *pParms); + +int vbsfClose(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle); + +int vbsfRead(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfReadPages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t offFile, + uint32_t *pcbBuffer, PVBOXHGCMSVCPARMPAGES pPages); +int vbsfWrite(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t *poffFile, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfWritePages(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hFile, uint64_t *poffFile, + uint32_t *pcbBuffer, PVBOXHGCMSVCPARMPAGES pPages); +int vbsfCopyFilePart(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, SHFLHANDLE hFileSrc, uint64_t offSrc, + SHFLROOT idRootDst, SHFLHANDLE hFileDst, uint64_t offDst, uint64_t *pcbToCopy, uint32_t fFlags); + +int vbsfLock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags); +int vbsfUnlock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags); +int vbsfRemove(SHFLCLIENTDATA *pClient, SHFLROOT root, PCSHFLSTRING pPath, uint32_t cbPath, uint32_t flags, SHFLHANDLE hToClose); +int vbsfRename(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pSrc, SHFLSTRING *pDest, uint32_t flags); +int vbsfCopyFile(SHFLCLIENTDATA *pClient, SHFLROOT idRootSrc, PCSHFLSTRING pStrPathSrc, + SHFLROOT idRootDst, PCSHFLSTRING pStrPathDst, uint32_t fFlags); +int vbsfDirList(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, SHFLSTRING *pPath, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer, uint32_t *pIndex, uint32_t *pcFiles); +int vbsfFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfSetFileSize(SHFLCLIENTDATA *pClient, SHFLROOT idRoot, SHFLHANDLE hHandle, uint64_t cbNewSize); +int vbsfQueryFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfSetFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfFlush(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle); +int vbsfDisconnect(SHFLCLIENTDATA *pClient); +int vbsfQueryFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer); +int vbsfReadLink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, uint8_t *pBuffer, uint32_t cbBuffer); +int vbsfSymlink(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pNewPath, SHFLSTRING *pOldPath, SHFLFSOBJINFO *pInfo); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_vbsf_h */ + diff --git a/src/VBox/HostServices/SharedFolders/vbsfpath.cpp b/src/VBox/HostServices/SharedFolders/vbsfpath.cpp new file mode 100644 index 00000000..afd13c4d --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpath.cpp @@ -0,0 +1,709 @@ +/* $Id: vbsfpath.cpp $ */ +/** @file + * Shared Folders Service - guest/host path convertion and verification. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#ifdef UNITTEST +# include "testcase/tstSharedFolderService.h" +#endif + +#include "vbsfpath.h" +#include "mappings.h" +#include "vbsf.h" +#include "shflhandle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef RT_OS_DARWIN +# include +#endif + +#ifdef UNITTEST +# include "teststubs.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SHFL_RT_LINK(pClient) ((pClient)->fu32Flags & SHFL_CF_SYMLINKS ? RTPATH_F_ON_LINK : RTPATH_F_FOLLOW_LINK) + + +/** + * @todo find a better solution for supporting the execute bit for non-windows + * guests on windows host. Search for "0111" to find all the relevant places. + */ + +/** + * Corrects the casing of the final component + * + * @returns + * @param pClient . + * @param pszFullPath . + * @param pszStartComponent . + */ +static int vbsfCorrectCasing(SHFLCLIENTDATA *pClient, char *pszFullPath, char *pszStartComponent) +{ + Log2(("vbsfCorrectCasing: %s %s\n", pszFullPath, pszStartComponent)); + + AssertReturn((uintptr_t)pszFullPath < (uintptr_t)pszStartComponent - 1U, VERR_INTERNAL_ERROR_2); + AssertReturn(pszStartComponent[-1] == RTPATH_DELIMITER, VERR_INTERNAL_ERROR_5); + + /* + * Allocate a buffer that can hold really long file name entries as well as + * the initial search pattern. + */ + size_t cchComponent = strlen(pszStartComponent); + size_t cchParentDir = pszStartComponent - pszFullPath; + size_t cchFullPath = cchParentDir + cchComponent; + Assert(strlen(pszFullPath) == cchFullPath); + + size_t cbDirEntry = 4096; + if (cchFullPath + 4 > cbDirEntry - RT_OFFSETOF(RTDIRENTRYEX, szName)) + cbDirEntry = RT_OFFSETOF(RTDIRENTRYEX, szName) + cchFullPath + 4; + + PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry); + if (pDirEntry == NULL) + return VERR_NO_MEMORY; + + /* + * Construct the search criteria in the szName member of pDirEntry. + */ + /** @todo This is quite inefficient, especially for directories with many + * files. If any of the typically case sensitive host systems start + * supporting opendir wildcard filters, it would make sense to build + * one here with '?' for case foldable charaters. */ + /** @todo Use RTDirOpen here and drop the whole uncessary path copying? */ + int rc = RTPathJoinEx(pDirEntry->szName, cbDirEntry - RT_OFFSETOF(RTDIRENTRYEX, szName), + pszFullPath, cchParentDir, + RT_STR_TUPLE("*"), RTPATH_STR_F_STYLE_HOST); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RTDIR hSearch = NULL; + rc = RTDirOpenFiltered(&hSearch, pDirEntry->szName, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + for (;;) + { + size_t cbDirEntrySize = cbDirEntry; + + rc = RTDirReadEx(hSearch, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); + if (rc == VERR_NO_MORE_FILES) + break; + + if ( rc != VINF_SUCCESS + && rc != VWRN_NO_DIRENT_INFO) + { + if ( rc == VERR_NO_TRANSLATION + || rc == VERR_INVALID_UTF8_ENCODING) + continue; + AssertMsgFailed(("%Rrc\n", rc)); + break; + } + + Log2(("vbsfCorrectCasing: found %s\n", &pDirEntry->szName[0])); + if ( pDirEntry->cbName == cchComponent + && !RTStrICmp(pszStartComponent, &pDirEntry->szName[0])) + { + Log(("Found original name %s (%s)\n", &pDirEntry->szName[0], pszStartComponent)); + strcpy(pszStartComponent, &pDirEntry->szName[0]); + rc = VINF_SUCCESS; + break; + } + } + + RTDirClose(hSearch); + } + } + + if (RT_FAILURE(rc)) + Log(("vbsfCorrectCasing %s failed with %Rrc\n", pszStartComponent, rc)); + + RTMemFree(pDirEntry); + + return rc; +} + +/* Temporary stand-in for RTPathExistEx. */ +static int vbsfQueryExistsEx(const char *pszPath, uint32_t fFlags) +{ +#if 0 /** @todo Fix the symlink issue on windows! */ + return RTPathExistsEx(pszPath, fFlags); +#else + RTFSOBJINFO IgnInfo; + return RTPathQueryInfoEx(pszPath, &IgnInfo, RTFSOBJATTRADD_NOTHING, fFlags); +#endif +} + +/** + * Helper for vbsfBuildFullPath that performs case corrections on the path + * that's being build. + * + * @returns VINF_SUCCESS at the moment. + * @param pClient The client data. + * @param pszFullPath Pointer to the full path. This is the path + * which may need case corrections. The + * corrections will be applied in place. + * @param cchFullPath The length of the full path. + * @param fWildCard Whether the last component may contain + * wildcards and thus might require exclusion + * from the case correction. + * @param fPreserveLastComponent Always exclude the last component from case + * correction if set. + */ +static int vbsfCorrectPathCasing(SHFLCLIENTDATA *pClient, char *pszFullPath, size_t cchFullPath, + bool fWildCard, bool fPreserveLastComponent) +{ + /* + * Hide the last path component if it needs preserving. This is required + * in the following cases: + * - Contains the wildcard(s). + * - Is a 'rename' target. + */ + char *pszLastComponent = NULL; + if (fWildCard || fPreserveLastComponent) + { + char *pszSrc = pszFullPath + cchFullPath - 1; + Assert(strchr(pszFullPath, '\0') == pszSrc + 1); + while ((uintptr_t)pszSrc > (uintptr_t)pszFullPath) + { + if (*pszSrc == RTPATH_DELIMITER) + break; + pszSrc--; + } + if (*pszSrc == RTPATH_DELIMITER) + { + if ( fPreserveLastComponent + /* Or does it really have wildcards? */ + || strchr(pszSrc + 1, '*') != NULL + || strchr(pszSrc + 1, '?') != NULL + || strchr(pszSrc + 1, '>') != NULL + || strchr(pszSrc + 1, '<') != NULL + || strchr(pszSrc + 1, '"') != NULL ) + { + pszLastComponent = pszSrc; + *pszLastComponent = '\0'; + } + } + } + + /* + * If the path/file doesn't exist, we need to attempt case correcting it. + */ + /** @todo Don't check when creating files or directories; waste of time. */ + int rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + Log(("Handle case insensitive guest fs on top of host case sensitive fs for %s\n", pszFullPath)); + + /* + * Work from the end of the path to find a partial path that's valid. + */ + char *pszSrc = pszLastComponent ? pszLastComponent - 1 : pszFullPath + cchFullPath - 1; + Assert(strchr(pszFullPath, '\0') == pszSrc + 1); + + while ((uintptr_t)pszSrc > (uintptr_t)pszFullPath) + { + if (*pszSrc == RTPATH_DELIMITER) + { + *pszSrc = '\0'; + rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); + *pszSrc = RTPATH_DELIMITER; + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + *pszSrc = '\0'; + Log(("Found valid partial path %s\n", pszFullPath)); + *pszSrc = RTPATH_DELIMITER; +#endif + break; + } + } + + pszSrc--; + } + Assert(*pszSrc == RTPATH_DELIMITER && RT_SUCCESS(rc)); + if ( *pszSrc == RTPATH_DELIMITER + && RT_SUCCESS(rc)) + { + /* + * Turn around and work the other way case correcting the components. + */ + pszSrc++; + for (;;) + { + bool fEndOfString = true; + + /* Find the end of the component. */ + char *pszEnd = pszSrc; + while (*pszEnd) + { + if (*pszEnd == RTPATH_DELIMITER) + break; + pszEnd++; + } + + if (*pszEnd == RTPATH_DELIMITER) + { + fEndOfString = false; + *pszEnd = '\0'; +#if 0 /** @todo Please, double check this. The original code is in the #if 0, what I hold as correct is in the #else. */ + rc = RTPathQueryInfoEx(pszSrc, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); +#else + rc = vbsfQueryExistsEx(pszFullPath, SHFL_RT_LINK(pClient)); +#endif + Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND); + } + else if (pszEnd == pszSrc) + rc = VINF_SUCCESS; /* trailing delimiter */ + else + rc = VERR_FILE_NOT_FOUND; + + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + { + /* Path component is invalid; try to correct the casing. */ + rc = vbsfCorrectCasing(pClient, pszFullPath, pszSrc); + if (RT_FAILURE(rc)) + { + /* Failed, so don't bother trying any further components. */ + if (!fEndOfString) + *pszEnd = RTPATH_DELIMITER; /* Restore the original full path. */ + break; + } + } + + /* Next (if any). */ + if (fEndOfString) + break; + + *pszEnd = RTPATH_DELIMITER; + pszSrc = pszEnd + 1; + } + if (RT_FAILURE(rc)) + Log(("Unable to find suitable component rc=%d\n", rc)); + } + else + rc = VERR_FILE_NOT_FOUND; + + } + + /* Restore the final component if it was dropped. */ + if (pszLastComponent) + *pszLastComponent = RTPATH_DELIMITER; + + /* might be a new file so don't fail here! */ + return VINF_SUCCESS; +} + + +#ifdef RT_OS_DARWIN +/* Misplaced hack! See todo! */ + +/** Normalize the string using kCFStringNormalizationFormD. + * + * @param pwszSrc The input UTF-16 string. + * @param cwcSrc Length of the input string in characters. + * @param ppwszDst Where to store the pointer to the resulting normalized string. + * @param pcwcDst Where to store length of the normalized string in characters (without the trailing nul). + */ +static int vbsfNormalizeStringDarwin(PCRTUTF16 pwszSrc, uint32_t cwcSrc, PRTUTF16 *ppwszDst, uint32_t *pcwcDst) +{ + /** @todo This belongs in rtPathToNative or in the windows shared folder file system driver... + * The question is simply whether the NFD normalization is actually applied on a (virtual) file + * system level in darwin, or just by the user mode application libs. */ + + PRTUTF16 pwszNFD; + uint32_t cwcNFD; + + CFMutableStringRef inStr = ::CFStringCreateMutable(NULL, 0); + + /* Is 8 times length enough for decomposed in worst case...? */ + size_t cbNFDAlloc = cwcSrc * 8 + 2; + pwszNFD = (PRTUTF16)RTMemAllocZ(cbNFDAlloc); + if (!pwszNFD) + { + return VERR_NO_MEMORY; + } + + ::CFStringAppendCharacters(inStr, (UniChar*)pwszSrc, cwcSrc); + ::CFStringNormalize(inStr, kCFStringNormalizationFormD); + cwcNFD = ::CFStringGetLength(inStr); + + CFRange rangeCharacters; + rangeCharacters.location = 0; + rangeCharacters.length = cwcNFD; + ::CFStringGetCharacters(inStr, rangeCharacters, pwszNFD); + + pwszNFD[cwcNFD] = 0x0000; /* NULL terminated */ + + CFRelease(inStr); + + *ppwszDst = pwszNFD; + *pcwcDst = cwcNFD; + return VINF_SUCCESS; +} +#endif + + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +/* See MSDN "Naming Files, Paths, and Namespaces". + * '<', '>' and '"' are allowed as possible wildcards (see ANSI_DOS_STAR, etc in ntifs.h) + */ +static const char sachCharBlackList[] = ":/\\|"; +#else +/* Something else. */ +static const char sachCharBlackList[] = "/"; +#endif + +/** Verify if the character can be used in a host file name. + * Wildcard characters ('?', '*') are allowed. + * + * @param c Character to verify. + */ +static bool vbsfPathIsValidNameChar(unsigned char c) +{ + /* Character 0 is not allowed too. */ + if (c == 0 || strchr(sachCharBlackList, c)) + { + return false; + } + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + /* Characters less than 32 are not allowed. */ + if (c < 32) + { + return false; + } +#endif + + return true; +} + +/** Verify if the character is a wildcard. + * + * @param c Character to verify. + */ +static bool vbsfPathIsWildcardChar(char c) +{ + if ( c == '*' + || c == '?' +#ifdef RT_OS_WINDOWS /* See ntifs.h */ + || c == '<' /* ANSI_DOS_STAR */ + || c == '>' /* ANSI_DOS_QM */ + || c == '"' /* ANSI_DOS_DOT */ +#endif + ) + { + return true; + } + + return false; +} + +int vbsfPathGuestToHost(SHFLCLIENTDATA *pClient, SHFLROOT hRoot, + PCSHFLSTRING pGuestString, uint32_t cbGuestString, + char **ppszHostPath, uint32_t *pcbHostPathRoot, + uint32_t fu32Options, + uint32_t *pfu32PathFlags) +{ +#ifdef VBOX_STRICT + /* + * Check that the pGuestPath has correct size and encoding. + */ + if (ShflStringIsValidIn(pGuestString, cbGuestString, RT_BOOL(pClient->fu32Flags & SHFL_CF_UTF8)) == false) + { + LogFunc(("Invalid input string\n")); + return VERR_INTERNAL_ERROR; + } +#else + NOREF(cbGuestString); +#endif + + /* + * Resolve the root handle into a string. + */ + uint32_t cbRootLen = 0; + const char *pszRoot = NULL; + int rc = vbsfMappingsQueryHostRootEx(hRoot, &pszRoot, &cbRootLen); + if (RT_FAILURE(rc)) + { + LogFunc(("invalid root\n")); + return rc; + } + + AssertReturn(cbRootLen > 0, VERR_INTERNAL_ERROR_2); /* vbsfMappingsQueryHostRootEx ensures this. */ + + /* + * Get the UTF8 string with the relative path provided by the guest. + * If guest uses UTF-16 then convert it to UTF-8. + */ + uint32_t cbGuestPath = 0; /* Shut up MSC */ + const char *pchGuestPath = NULL; /* Ditto. */ + char *pchGuestPathAllocated = NULL; /* Converted from UTF-16. */ + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + { + /* UTF-8 */ + cbGuestPath = pGuestString->u16Length; + pchGuestPath = pGuestString->String.ach; + } + else + { + /* UTF-16 */ + +#ifdef RT_OS_DARWIN /* Misplaced hack! See todo! */ + uint32_t cwcSrc = 0; + PRTUTF16 pwszSrc = NULL; + rc = vbsfNormalizeStringDarwin(&pGuestString->String.ucs2[0], + pGuestString->u16Length / sizeof(RTUTF16), + &pwszSrc, &cwcSrc); +#else + uint32_t const cwcSrc = pGuestString->u16Length / sizeof(RTUTF16); + PCRTUTF16 const pwszSrc = &pGuestString->String.ucs2[0]; +#endif + + if (RT_SUCCESS(rc)) + { + size_t cbPathAsUtf8 = RTUtf16CalcUtf8Len(pwszSrc); + if (cbPathAsUtf8 >= cwcSrc) + { + /* Allocate buffer that will be able to contain the converted UTF-8 string. */ + pchGuestPathAllocated = (char *)RTMemAlloc(cbPathAsUtf8 + 1); + if (RT_LIKELY(pchGuestPathAllocated != NULL)) + { + if (RT_LIKELY(cbPathAsUtf8)) + { + size_t cchActual; + char *pszDst = pchGuestPathAllocated; + rc = RTUtf16ToUtf8Ex(pwszSrc, cwcSrc, &pszDst, cbPathAsUtf8 + 1, &cchActual); + AssertRC(rc); + AssertStmt(RT_FAILURE(rc) || cchActual == cbPathAsUtf8, rc = VERR_INTERNAL_ERROR_4); + Assert(strlen(pszDst) == cbPathAsUtf8); + } + + if (RT_SUCCESS(rc)) + { + /* Terminate the string. */ + pchGuestPathAllocated[cbPathAsUtf8] = '\0'; + + cbGuestPath = (uint32_t)cbPathAsUtf8; Assert(cbGuestPath == cbPathAsUtf8); + pchGuestPath = pchGuestPathAllocated; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + else + { + AssertFailed(); + rc = VERR_INTERNAL_ERROR_3; + } + +#ifdef RT_OS_DARWIN + RTMemFree(pwszSrc); +#endif + } + } + + char *pszFullPath = NULL; + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Root %s path %.*s\n", pszRoot, cbGuestPath, pchGuestPath)); + + /* + * Allocate enough memory to build the host full path from the root and the relative path. + */ + const uint32_t cbFullPathAlloc = cbRootLen + 1 + cbGuestPath + 1; /* root + possible_slash + relative + 0 */ + pszFullPath = (char *)RTMemAlloc(cbFullPathAlloc); + if (RT_LIKELY(pszFullPath != NULL)) + { + /* Buffer for the verified guest path. */ + char *pchVerifiedPath = (char *)RTMemAlloc(cbGuestPath + 1); + if (RT_LIKELY(pchVerifiedPath != NULL)) + { + /* Init the pointer for the guest relative path. */ + uint32_t cbSrc = cbGuestPath; + const char *pchSrc = pchGuestPath; + + /* Strip leading delimiters from the path the guest specified. */ + while ( cbSrc > 0 + && *pchSrc == pClient->PathDelimiter) + { + ++pchSrc; + --cbSrc; + } + + /* + * Iterate the guest path components, verify each of them replacing delimiters with the host slash. + */ + char *pchDst = pchVerifiedPath; + bool fLastComponentHasWildcard = false; + for (; cbSrc > 0; --cbSrc, ++pchSrc) + { + if (RT_LIKELY(*pchSrc != pClient->PathDelimiter)) + { + if (RT_LIKELY(vbsfPathIsValidNameChar(*pchSrc))) + { + if (pfu32PathFlags && vbsfPathIsWildcardChar(*pchSrc)) + { + fLastComponentHasWildcard = true; + } + + *pchDst++ = *pchSrc; + } + else + { + rc = VERR_INVALID_NAME; + break; + } + } + else + { + /* Replace with the host slash. */ + *pchDst++ = RTPATH_SLASH; + + if (pfu32PathFlags && fLastComponentHasWildcard && cbSrc > 1) + { + /* Processed component has a wildcard and there are more characters in the path. */ + *pfu32PathFlags |= VBSF_F_PATH_HAS_WILDCARD_IN_PREFIX; + } + fLastComponentHasWildcard = false; + } + } + + if (RT_SUCCESS(rc)) + { + *pchDst++ = 0; + + /* Construct the full host path removing '.' and '..'. */ + rc = vbsfPathAbs(pszRoot, pchVerifiedPath, pszFullPath, cbFullPathAlloc); + if (RT_SUCCESS(rc)) + { + if (pfu32PathFlags && fLastComponentHasWildcard) + { + *pfu32PathFlags |= VBSF_F_PATH_HAS_WILDCARD_IN_LAST; + } + + /* Check if the full path is still within the shared folder. */ + if (fu32Options & VBSF_O_PATH_CHECK_ROOT_ESCAPE) + { + if (!RTPathStartsWith(pszFullPath, pszRoot)) + { + rc = VERR_INVALID_NAME; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * If the host file system is case sensitive and the guest expects + * a case insensitive fs, then correct the path components casing. + */ + if ( vbsfIsHostMappingCaseSensitive(hRoot) + && !vbsfIsGuestMappingCaseSensitive(hRoot)) + { + const bool fWildCard = RT_BOOL(fu32Options & VBSF_O_PATH_WILDCARD); + const bool fPreserveLastComponent = RT_BOOL(fu32Options & VBSF_O_PATH_PRESERVE_LAST_COMPONENT); + rc = vbsfCorrectPathCasing(pClient, pszFullPath, strlen(pszFullPath), + fWildCard, fPreserveLastComponent); + } + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("%s\n", pszFullPath)); + + /* Return the full host path. */ + *ppszHostPath = pszFullPath; + + if (pcbHostPathRoot) + { + /* Return the length of the root path without the trailing slash. */ + *pcbHostPathRoot = RTPATH_IS_SLASH(pszFullPath[cbRootLen - 1]) ? + cbRootLen - 1 : /* pszRoot already had the trailing slash. */ + cbRootLen; /* pszRoot did not have the trailing slash. */ + } + } + } + } + else + { + LogFunc(("vbsfPathAbs %Rrc\n", rc)); + } + } + + RTMemFree(pchVerifiedPath); + } + else + { + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + + /* + * Cleanup. + */ + RTMemFree(pchGuestPathAllocated); + + if (RT_SUCCESS(rc)) + { + return rc; + } + + /* + * Cleanup on failure. + */ + RTMemFree(pszFullPath); + + LogFunc(("%Rrc\n", rc)); + return rc; +} + +void vbsfFreeHostPath(char *pszHostPath) +{ + RTMemFree(pszHostPath); +} + diff --git a/src/VBox/HostServices/SharedFolders/vbsfpath.h b/src/VBox/HostServices/SharedFolders/vbsfpath.h new file mode 100644 index 00000000..f2b4e1a6 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpath.h @@ -0,0 +1,80 @@ +/* $Id: vbsfpath.h $ */ +/** @file + * Shared Folders Service - Guest/host path convertion and verification. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h +#define VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "shfl.h" +#include + +#define VBSF_O_PATH_WILDCARD UINT32_C(0x00000001) +#define VBSF_O_PATH_PRESERVE_LAST_COMPONENT UINT32_C(0x00000002) +#define VBSF_O_PATH_CHECK_ROOT_ESCAPE UINT32_C(0x00000004) + +#define VBSF_F_PATH_HAS_WILDCARD_IN_PREFIX UINT32_C(0x00000001) /* A component before the last one contains a wildcard. */ +#define VBSF_F_PATH_HAS_WILDCARD_IN_LAST UINT32_C(0x00000002) /* The last component contains a wildcard. */ + +/** + * + * @param pClient Shared folder client. + * @param hRoot Root handle. + * @param pGuestString Guest want to access the path. + * @param cbGuestString Size of pGuestString memory buffer. + * @param ppszHostPath Returned full host path: root prefix + guest path. + * @param pcbHostPathRoot Length of the root prefix in bytes. Optional, can be NULL. + * @param fu32Options Options. + * @param pfu32PathFlags VBSF_F_PATH_* flags. Optional, can be NULL. + */ +int vbsfPathGuestToHost(SHFLCLIENTDATA *pClient, SHFLROOT hRoot, + PCSHFLSTRING pGuestString, uint32_t cbGuestString, + char **ppszHostPath, uint32_t *pcbHostPathRoot, + uint32_t fu32Options, uint32_t *pfu32PathFlags); + +/** Free the host path returned by vbsfPathGuestToHost. + * + * @param pszHostPath Host path string. + */ +void vbsfFreeHostPath(char *pszHostPath); + +/** + * Build the absolute path by combining an absolute pszRoot and a relative pszPath. + * The resulting path does not contain '.' and '..' components. + * Similar to RTPathAbsEx but with support for Windows extended-length paths ("\\?\" prefix). + * Uses RTPathAbsEx for regular paths and on non-Windows hosts. + * + * @param pszRoot The absolute prefix. It is copied to the pszAbsPath without any processing. + * If NULL then the pszPath must be converted to the absolute path. + * @param pszPath The relative path to be appended to pszRoot. Already has correct delimiters (RTPATH_SLASH). + * @param pszAbsPath Where to store the resulting absolute path. + * @param cbAbsPath Size of pszAbsBuffer in bytes. + */ +int vbsfPathAbs(const char *pszRoot, const char *pszPath, char *pszAbsPath, size_t cbAbsPath); + +#endif /* !VBOX_INCLUDED_SRC_SharedFolders_vbsfpath_h */ diff --git a/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp b/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp new file mode 100644 index 00000000..ce6f9391 --- /dev/null +++ b/src/VBox/HostServices/SharedFolders/vbsfpathabs.cpp @@ -0,0 +1,200 @@ +/* $Id: vbsfpathabs.cpp $ */ +/** @file + * Shared Folders Service - guest/host path convertion and verification. + */ + +/* + * Copyright (C) 2017-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include +#include +#include + + +#if defined(RT_OS_WINDOWS) +static void vbsfPathResolveRelative(char *pszPathBegin) +{ + char *pszCur = pszPathBegin; + char * const pszTop = pszCur; + + /* + * Get rid of double dot path components by evaluating them. + */ + for (;;) + { + char const chFirst = pszCur[0]; + if ( chFirst == '.' + && pszCur[1] == '.' + && (!pszCur[2] || pszCur[2] == RTPATH_SLASH)) + { + /* rewind to the previous component if any */ + char *pszPrev = pszCur; + if ((uintptr_t)pszPrev > (uintptr_t)pszTop) + { + pszPrev--; + while ( (uintptr_t)pszPrev > (uintptr_t)pszTop + && pszPrev[-1] != RTPATH_SLASH) + pszPrev--; + } + if (!pszCur[2]) + { + if (pszPrev != pszTop) + pszPrev[-1] = '\0'; + else + *pszPrev = '\0'; + break; + } + Assert(pszPrev[-1] == RTPATH_SLASH); + memmove(pszPrev, pszCur + 3, strlen(pszCur + 3) + 1); + pszCur = pszPrev - 1; + } + else if ( chFirst == '.' + && (!pszCur[1] || pszCur[1] == RTPATH_SLASH)) + { + /* remove unnecessary '.' */ + if (!pszCur[1]) + { + if (pszCur != pszTop) + pszCur[-1] = '\0'; + else + *pszCur = '\0'; + break; + } + memmove(pszCur, pszCur + 2, strlen(pszCur + 2) + 1); + continue; + } + else + { + /* advance to end of component. */ + while (*pszCur && *pszCur != RTPATH_SLASH) + pszCur++; + } + + if (!*pszCur) + break; + + /* skip the slash */ + ++pszCur; + } +} +#endif /* RT_OS_WINDOWS */ + +int vbsfPathAbs(const char *pszRoot, const char *pszPath, char *pszAbsPath, size_t cbAbsPath) +{ +#if defined(RT_OS_WINDOWS) + /** @todo This code is not needed in 6.0 and later as IPRT translates paths + * to //./ (inverted slashes for doxygen) format if they're too long. */ + const char *pszPathStart = pszRoot? pszRoot: pszPath; + + /* Windows extended-length paths. */ + if ( RTPATH_IS_SLASH(pszPathStart[0]) + && RTPATH_IS_SLASH(pszPathStart[1]) + && pszPathStart[2] == '?' + && RTPATH_IS_SLASH(pszPathStart[3]) + ) + { + /* Maximum total path length of 32,767 characters. */ + if (cbAbsPath > _32K) + cbAbsPath = _32K; + + /* Copy the root to pszAbsPath buffer. */ + size_t cchRoot = pszRoot? strlen(pszRoot): 0; + if (cchRoot >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + if (pszRoot) + { + /* Caller must ensure that the path is relative, without the leading path separator. */ + if (RTPATH_IS_SLASH(pszPath[0])) + return VERR_INVALID_PARAMETER; + + if (cchRoot) + memcpy(pszAbsPath, pszRoot, cchRoot); + + if (cchRoot == 0 || !RTPATH_IS_SLASH(pszAbsPath[cchRoot - 1])) + { + /* Append path separator after the root. */ + ++cchRoot; + if (cchRoot >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + pszAbsPath[cchRoot - 1] = RTPATH_SLASH; + } + } + + /* Append the path to the pszAbsPath buffer. */ + const size_t cchPath = strlen(pszPath); + if (cchRoot + cchPath >= cbAbsPath) + return VERR_FILENAME_TOO_LONG; + + memcpy(&pszAbsPath[cchRoot], pszPath, cchPath + 1); /* Including trailing 0. */ + + /* Find out where the actual path begins, i.e. skip the root spec. */ + char *pszPathBegin = &pszAbsPath[4]; /* Skip the extended-length path prefix "\\?\" */ + if ( pszPathBegin[0] + && RTPATH_IS_VOLSEP(pszPathBegin[1]) + && pszPathBegin[2] == RTPATH_SLASH) + { + /* "\\?\C:\" */ + pszPathBegin += 3; + } + else if ( pszPathBegin[0] == 'U' + && pszPathBegin[1] == 'N' + && pszPathBegin[2] == 'C' + && pszPathBegin[3] == RTPATH_SLASH) + { + /* "\\?\UNC\server\share" */ + pszPathBegin += 4; + + /* Skip "server\share" too. */ + while (*pszPathBegin != RTPATH_SLASH && *pszPathBegin) + ++pszPathBegin; + if (*pszPathBegin == RTPATH_SLASH) + { + ++pszPathBegin; + while (*pszPathBegin != RTPATH_SLASH && *pszPathBegin) + ++pszPathBegin; + if (*pszPathBegin == RTPATH_SLASH) + ++pszPathBegin; + } + } + else + return VERR_INVALID_NAME; + + /* Process pszAbsPath in place. */ + vbsfPathResolveRelative(pszPathBegin); + + return VINF_SUCCESS; + } +#endif /* RT_OS_WINDOWS */ + + /* Fallback for the common paths. */ + + if (*pszPath != '\0') + return RTPathAbsEx(pszRoot, pszPath, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath); + return RTPathAbsEx(NULL, pszRoot, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath); +} -- cgit v1.2.3