summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedFolders/vbsf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostServices/SharedFolders/vbsf.cpp')
-rw-r--r--src/VBox/HostServices/SharedFolders/vbsf.cpp2690
1 files changed, 2690 insertions, 0 deletions
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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#ifdef UNITTEST
+# include "testcase/tstSharedFolderService.h"
+#endif
+
+#include "vbsfpath.h"
+#include "mappings.h"
+#include "vbsf.h"
+#include "shflhandle.h"
+
+#include <VBox/AssertGuest.h>
+#include <VBox/param.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/fs.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+#include <iprt/uni.h>
+#include <iprt/stream.h>
+#ifdef RT_OS_DARWIN
+# include <Carbon/Carbon.h>
+#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;
+}
+