summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp')
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp608
1 files changed, 608 insertions, 0 deletions
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp
new file mode 100644
index 00000000..feac592a
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp
@@ -0,0 +1,608 @@
+/* $Id: VBoxSF-Utils.cpp $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, Utility Functions.
+ */
+
+/*
+ * Copyright (C) 2013-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
+#include "VBoxSFInternal.h"
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <VBox/log.h>
+
+#if 0
+/**
+ * Helper function to create XNU VFS vnode object.
+ *
+ * @param mp Mount data structure
+ * @param type vnode type (directory, regular file, etc)
+ * @param pParent Parent vnode object (NULL for VBoxVFS root vnode)
+ * @param fIsRoot Flag that indicates if created vnode object is
+ * VBoxVFS root vnode (TRUE for VBoxVFS root vnode, FALSE
+ * for all aother vnodes)
+ * @param Path within Shared Folder
+ * @param ret Returned newly created vnode
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_create_vnode_internal(struct mount *mp, enum vtype type, vnode_t pParent, int fIsRoot, PSHFLSTRING Path, vnode_t *ret)
+{
+ int rc;
+ vnode_t vnode;
+
+ vboxvfs_vnode_t *pVnodeData;
+ vboxvfs_mount_t *pMount;
+
+ AssertReturn(mp, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp);
+ AssertReturn(pMount, EINVAL);
+ AssertReturn(pMount->pLockGroup, EINVAL);
+
+ AssertReturn(Path, EINVAL);
+
+ pVnodeData = (vboxvfs_vnode_t *)RTMemAllocZ(sizeof(vboxvfs_vnode_t));
+ AssertReturn(pVnodeData, ENOMEM);
+
+ /* Initialize private data */
+ pVnodeData->pHandle = SHFL_HANDLE_NIL;
+ pVnodeData->pPath = Path;
+
+ pVnodeData->pLockAttr = lck_attr_alloc_init();
+ if (pVnodeData->pLockAttr)
+ {
+ pVnodeData->pLock = lck_rw_alloc_init(pMount->pLockGroup, pVnodeData->pLockAttr);
+ if (pVnodeData->pLock)
+ {
+ struct vnode_fsparam vnode_params;
+
+ vnode_params.vnfs_mp = mp;
+ vnode_params.vnfs_vtype = type;
+ vnode_params.vnfs_str = NULL;
+ vnode_params.vnfs_dvp = pParent;
+ vnode_params.vnfs_fsnode = pVnodeData; /** Private data attached per xnu's vnode object */
+ vnode_params.vnfs_vops = g_papfnVBoxVFSVnodeDirOpsVector;
+
+ vnode_params.vnfs_markroot = fIsRoot;
+ vnode_params.vnfs_marksystem = FALSE;
+ vnode_params.vnfs_rdev = 0;
+ vnode_params.vnfs_filesize = 0;
+ vnode_params.vnfs_cnp = NULL;
+
+ vnode_params.vnfs_flags = VNFS_ADDFSREF | VNFS_NOCACHE;
+
+ rc = vnode_create(VNCREATE_FLAVOR, sizeof(vnode_params), &vnode_params, &vnode);
+ if (rc == 0)
+ *ret = vnode;
+
+ return 0;
+ }
+ else
+ {
+ PDEBUG("Unable to allocate lock");
+ rc = ENOMEM;
+ }
+
+ lck_attr_free(pVnodeData->pLockAttr);
+ }
+ else
+ {
+ PDEBUG("Unable to allocate lock attr");
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Convert guest absolute VFS path (starting from VFS root) to a host path
+ * within mounted shared folder (returning it as a char *).
+ *
+ * @param mp Mount data structure
+ * @param pszGuestPath Guest absolute VFS path (starting from VFS root)
+ * @param cbGuestPath Size of pszGuestPath
+ * @param pszHostPath Returned char * wich contains host path
+ * @param cbHostPath Returned pszHostPath size
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_guest_path_to_char_path_internal(mount_t mp, char *pszGuestPath, int cbGuestPath, char **pszHostPath, int *cbHostPath)
+{
+ vboxvfs_mount_t *pMount;
+
+ /* Guest side: mount point path buffer and its size */
+ char *pszMntPointPath;
+ int cbMntPointPath = MAXPATHLEN;
+
+ /* Host side: path within mounted shared folder and its size */
+ char *pszHostPathInternal;
+ size_t cbHostPathInternal;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pszGuestPath, EINVAL); AssertReturn(cbGuestPath >= 0, EINVAL);
+ AssertReturn(pszHostPath, EINVAL); AssertReturn(cbHostPath, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL); AssertReturn(pMount->pRootVnode, EINVAL);
+
+ /* Get mount point path */
+ pszMntPointPath = (char *)RTMemAllocZ(cbMntPointPath);
+ if (pszMntPointPath)
+ {
+ rc = vn_getpath(pMount->pRootVnode, pszMntPointPath, &cbMntPointPath);
+ if (rc == 0 && cbGuestPath >= cbMntPointPath)
+ {
+ cbHostPathInternal = cbGuestPath - cbMntPointPath + 1;
+ pszHostPathInternal = (char *)RTMemAllocZ(cbHostPathInternal);
+ if (pszHostPathInternal)
+ {
+ memcpy(pszHostPathInternal, pszGuestPath + cbMntPointPath, cbGuestPath - cbMntPointPath);
+ PDEBUG("guest<->host path converion result: '%s' mounted to '%s'", pszHostPathInternal, pszMntPointPath);
+
+ RTMemFree(pszMntPointPath);
+
+ *pszHostPath = pszHostPathInternal;
+ *cbHostPath = cbGuestPath - cbMntPointPath;
+
+ return 0;
+
+ }
+ else
+ {
+ PDEBUG("No memory to allocate buffer for guest<->host path conversion (cbHostPathInternal)");
+ rc = ENOMEM;
+ }
+
+ }
+ else
+ {
+ PDEBUG("Unable to get guest vnode path: %d", rc);
+ }
+
+ RTMemFree(pszMntPointPath);
+ }
+ else
+ {
+ PDEBUG("No memory to allocate buffer for guest<->host path conversion (pszMntPointPath)");
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Convert guest absolute VFS path (starting from VFS root) to a host path
+ * within mounted shared folder.
+ *
+ * @param mp Mount data structure
+ * @param pszGuestPath Guest absolute VFS path (starting from VFS root)
+ * @param cbGuestPath Size of pszGuestPath
+ * @param ppResult Returned PSHFLSTRING object wich contains host path
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_guest_path_to_shflstring_path_internal(mount_t mp, char *pszGuestPath, int cbGuestPath, PSHFLSTRING *ppResult)
+{
+ vboxvfs_mount_t *pMount;
+
+ /* Guest side: mount point path buffer and its size */
+ char *pszMntPointPath;
+ int cbMntPointPath = MAXPATHLEN;
+
+ /* Host side: path within mounted shared folder and its size */
+ PSHFLSTRING pSFPath;
+ size_t cbSFPath;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pszGuestPath, EINVAL);
+ AssertReturn(cbGuestPath >= 0, EINVAL);
+
+ char *pszHostPath;
+ int cbHostPath;
+
+ rc = vboxvfs_guest_path_to_char_path_internal(mp, pszGuestPath, cbGuestPath, &pszHostPath, &cbHostPath);
+ if (rc == 0)
+ {
+ cbSFPath = offsetof(SHFLSTRING, String.utf8) + (size_t)cbHostPath + 1;
+ pSFPath = (PSHFLSTRING)RTMemAllocZ(cbSFPath);
+ if (pSFPath)
+ {
+ pSFPath->u16Length = cbHostPath;
+ pSFPath->u16Size = cbHostPath + 1;
+ memcpy(pSFPath->String.utf8, pszHostPath, cbHostPath);
+ vboxvfs_put_path_internal((void **)&pszHostPath);
+
+ *ppResult = pSFPath;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Wrapper function for vboxvfs_guest_path_to_char_path_internal() which
+ * converts guest path to host path using vnode object information.
+ *
+ * @param vnode Guest's VFS object
+ * @param ppHostPath Allocated char * which contain a path
+ * @param pcbPath Size of ppPath
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_guest_vnode_to_char_path_internal(vnode_t vnode, char **ppHostPath, int *pcbHostPath)
+{
+ mount_t mp;
+ int rc;
+
+ char *pszPath;
+ int cbPath = MAXPATHLEN;
+
+ AssertReturn(ppHostPath, EINVAL);
+ AssertReturn(pcbHostPath, EINVAL);
+ AssertReturn(vnode, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+
+ pszPath = (char *)RTMemAllocZ(cbPath);
+ if (pszPath)
+ {
+ rc = vn_getpath(vnode, pszPath, &cbPath);
+ if (rc == 0)
+ {
+ return vboxvfs_guest_path_to_char_path_internal(mp, pszPath, cbPath, ppHostPath, pcbHostPath);
+ }
+ }
+ else
+ {
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Wrapper function for vboxvfs_guest_path_to_shflstring_path_internal() which
+ * converts guest path to host path using vnode object information.
+ *
+ * @param vnode Guest's VFS object
+ * @param ppResult Allocated PSHFLSTRING object which contain a path
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_guest_vnode_to_shflstring_path_internal(vnode_t vnode, PSHFLSTRING *ppResult)
+{
+ mount_t mp;
+ int rc;
+
+ char *pszPath;
+ int cbPath = MAXPATHLEN;
+
+ AssertReturn(ppResult, EINVAL);
+ AssertReturn(vnode, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+
+ pszPath = (char *)RTMemAllocZ(cbPath);
+ if (pszPath)
+ {
+ rc = vn_getpath(vnode, pszPath, &cbPath);
+ if (rc == 0)
+ {
+ return vboxvfs_guest_path_to_shflstring_path_internal(mp, pszPath, cbPath, ppResult);
+ }
+ }
+ else
+ {
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Free resources allocated by vboxvfs_path_internal() and vboxvfs_guest_vnode_to_shflstring_path_internal().
+ *
+ * @param ppHandle Reference to object to be freed.
+ */
+void
+vboxvfs_put_path_internal(void **ppHandle)
+{
+ AssertReturnVoid(ppHandle);
+ AssertReturnVoid(*ppHandle);
+ RTMemFree(*ppHandle);
+}
+
+static void
+vboxvfs_g2h_mode_dump_inernal(uint32_t fHostMode)
+{
+ PDEBUG("Host VFS object flags (0x%X) dump:", (int)fHostMode);
+
+ if (fHostMode & SHFL_CF_ACCESS_READ) PDEBUG("SHFL_CF_ACCESS_READ");
+ if (fHostMode & SHFL_CF_ACCESS_WRITE) PDEBUG("SHFL_CF_ACCESS_WRITE");
+ if (fHostMode & SHFL_CF_ACCESS_APPEND) PDEBUG("SHFL_CF_ACCESS_APPEND");
+
+ if ((fHostMode & (SHFL_CF_ACT_FAIL_IF_EXISTS |
+ SHFL_CF_ACT_REPLACE_IF_EXISTS |
+ SHFL_CF_ACT_OVERWRITE_IF_EXISTS)) == 0)
+ PDEBUG("SHFL_CF_ACT_OPEN_IF_EXISTS");
+
+ if (fHostMode & SHFL_CF_ACT_CREATE_IF_NEW) PDEBUG("SHFL_CF_ACT_CREATE_IF_NEW");
+ if (fHostMode & SHFL_CF_ACT_FAIL_IF_NEW) PDEBUG("SHFL_CF_ACT_FAIL_IF_NEW");
+ if (fHostMode & SHFL_CF_ACT_OVERWRITE_IF_EXISTS) PDEBUG("SHFL_CF_ACT_OVERWRITE_IF_EXISTS");
+ if (fHostMode & SHFL_CF_DIRECTORY) PDEBUG("SHFL_CF_DIRECTORY");
+
+ PDEBUG("Done");
+}
+
+
+/**
+ * Open existing VBoxVFS object and return its handle.
+ *
+ * @param pMount Mount session data.
+ * @param pPath VFS path to the object relative to mount point.
+ * @param fFlags For directory object it should be
+ * SHFL_CF_DIRECTORY and 0 for any other object.
+ * @param pHandle Returned handle.
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_open_internal(vboxvfs_mount_t *pMount, PSHFLSTRING pPath, uint32_t fFlags, SHFLHANDLE *pHandle)
+{
+ SHFLCREATEPARMS parms;
+
+ int rc;
+
+ AssertReturn(pMount, EINVAL);
+ AssertReturn(pPath, EINVAL);
+ AssertReturn(pHandle, EINVAL);
+
+ bzero(&parms, sizeof(parms));
+
+ vboxvfs_g2h_mode_dump_inernal(fFlags);
+
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = fFlags;
+
+ rc = VbglR0SfCreate(&g_SfClientDarwin, &pMount->pMap, pPath, &parms);
+ if (RT_SUCCESS(rc))
+ {
+ *pHandle = parms.Handle;
+ }
+ else
+ {
+ PDEBUG("vboxvfs_open_internal() failed: %d", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Release VBoxVFS object handle openned by vboxvfs_open_internal().
+ *
+ * @param pMount Mount session data.
+ * @param pHandle Handle to close.
+ *
+ * @return 0 on success, IPRT error code otherwise.
+ */
+int
+vboxvfs_close_internal(vboxvfs_mount_t *pMount, SHFLHANDLE pHandle)
+{
+ AssertReturn(pMount, EINVAL);
+ return VbglR0SfClose(&g_SfClientDarwin, &pMount->pMap, pHandle);
+}
+
+/**
+ * Get information about host VFS object.
+ *
+ * @param mp Mount point data
+ * @param pSHFLDPath Path to VFS object within mounted shared folder
+ * @param Info Returned info
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_get_info_internal(mount_t mp, PSHFLSTRING pSHFLDPath, PSHFLFSOBJINFO Info)
+{
+ vboxvfs_mount_t *pMount;
+ SHFLCREATEPARMS parms;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pSHFLDPath, EINVAL);
+ AssertReturn(Info, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ parms.Handle = 0;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = VbglR0SfCreate(&g_SfClientDarwin, &pMount->pMap, pSHFLDPath, &parms);
+ if (rc == 0)
+ *Info = parms.Info;
+
+ return rc;
+}
+
+/**
+ * Check if VFS object exists on a host side.
+ *
+ * @param vnode Guest VFS vnode that corresponds to host VFS object
+ *
+ * @return 1 if exists, 0 otherwise.
+ */
+int
+vboxvfs_exist_internal(vnode_t vnode)
+{
+ int rc;
+
+ PSHFLSTRING pSFPath = NULL;
+ SHFLHANDLE handle;
+ uint32_t fFlags;
+
+ vboxvfs_mount_t *pMount;
+ mount_t mp;
+
+ /* Return FALSE if invalid parameter given */
+ AssertReturn(vnode, 0);
+
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ fFlags = (vnode_isdir(vnode)) ? SHFL_CF_DIRECTORY : 0;
+ fFlags |= SHFL_CF_ACCESS_READ | SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = vboxvfs_guest_vnode_to_shflstring_path_internal(vnode, &pSFPath); AssertReturn(rc == 0, rc);
+ if (rc == 0)
+ {
+ rc = vboxvfs_open_internal(pMount, pSFPath, fFlags, &handle);
+ if (rc == 0)
+ {
+ rc = vboxvfs_close_internal(pMount, handle);
+ if (rc != 0)
+ {
+ PDEBUG("Unable to close() VBoxVFS object handle while checking if object exist on host: %d", rc);
+ }
+ }
+ }
+
+ vboxvfs_put_path_internal((void **)&pSFPath);
+
+ return (rc == 0);
+}
+
+/**
+ * Convert host VFS object mode flags into guest ones.
+ *
+ * @param fHostMode Host flags
+ *
+ * @return Guest flags
+ */
+mode_t
+vboxvfs_h2g_mode_inernal(RTFMODE fHostMode)
+{
+ mode_t fGuestMode = 0;
+
+ fGuestMode = /* Owner */
+ ((fHostMode & RTFS_UNIX_IRUSR) ? S_IRUSR : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWUSR) ? S_IWUSR : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXUSR) ? S_IXUSR : 0 ) |
+ /* Group */
+ ((fHostMode & RTFS_UNIX_IRGRP) ? S_IRGRP : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWGRP) ? S_IWGRP : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXGRP) ? S_IXGRP : 0 ) |
+ /* Other */
+ ((fHostMode & RTFS_UNIX_IROTH) ? S_IROTH : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWOTH) ? S_IWOTH : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXOTH) ? S_IXOTH : 0 ) |
+ /* SUID, SGID, SVTXT */
+ ((fHostMode & RTFS_UNIX_ISUID) ? S_ISUID : 0 ) |
+ ((fHostMode & RTFS_UNIX_ISGID) ? S_ISGID : 0 ) |
+ ((fHostMode & RTFS_UNIX_ISTXT) ? S_ISVTX : 0 ) |
+ /* VFS object types */
+ ((RTFS_IS_FIFO(fHostMode)) ? S_IFIFO : 0 ) |
+ ((RTFS_IS_DEV_CHAR(fHostMode)) ? S_IFCHR : 0 ) |
+ ((RTFS_IS_DIRECTORY(fHostMode)) ? S_IFDIR : 0 ) |
+ ((RTFS_IS_DEV_BLOCK(fHostMode)) ? S_IFBLK : 0 ) |
+ ((RTFS_IS_FILE(fHostMode)) ? S_IFREG : 0 ) |
+ ((RTFS_IS_SYMLINK(fHostMode)) ? S_IFLNK : 0 ) |
+ ((RTFS_IS_SOCKET(fHostMode)) ? S_IFSOCK : 0 );
+
+ return fGuestMode;
+}
+
+/**
+ * Convert guest VFS object mode flags into host ones.
+ *
+ * @param fGuestMode Host flags
+ *
+ * @return Host flags
+ */
+uint32_t
+vboxvfs_g2h_mode_inernal(mode_t fGuestMode)
+{
+ uint32_t fHostMode = 0;
+
+ fHostMode = ((fGuestMode & FREAD) ? SHFL_CF_ACCESS_READ : 0 ) |
+ ((fGuestMode & FWRITE) ? SHFL_CF_ACCESS_WRITE : 0 ) |
+ /* skipped: O_NONBLOCK */
+ ((fGuestMode & O_APPEND) ? SHFL_CF_ACCESS_APPEND : 0 ) |
+ /* skipped: O_SYNC */
+ /* skipped: O_SHLOCK */
+ /* skipped: O_EXLOCK */
+ /* skipped: O_ASYNC */
+ /* skipped: O_FSYNC */
+ /* skipped: O_NOFOLLOW */
+ ((fGuestMode & O_CREAT) ? SHFL_CF_ACT_CREATE_IF_NEW | (!(fGuestMode & O_TRUNC) ? SHFL_CF_ACT_OPEN_IF_EXISTS : 0) : SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW ) |
+ ((fGuestMode & O_TRUNC) ? SHFL_CF_ACT_OVERWRITE_IF_EXISTS | SHFL_CF_ACCESS_WRITE : 0 );
+ /* skipped: O_EXCL */
+
+ return fHostMode;
+}
+
+/**
+ * Mount helper: Contruct SHFLSTRING which contains VBox share name or path.
+ *
+ * @returns Initialize string buffer on success, NULL if out of memory.
+ * @param pachName The string to pack in a buffer. Does not need to be
+ * zero terminated.
+ * @param cchName The length of pachName to use. RTSTR_MAX for strlen.
+ */
+SHFLSTRING *
+vboxvfs_construct_shflstring(const char *pachName, size_t cchName)
+{
+ AssertReturn(pachName, NULL);
+
+ if (cchName == RTSTR_MAX)
+ cchName = strlen(pachName);
+
+ SHFLSTRING *pSHFLString = (SHFLSTRING *)RTMemAlloc(SHFLSTRING_HEADER_SIZE + cchName + 1);
+ if (pSHFLString)
+ {
+ pSHFLString->u16Length = cchName;
+ pSHFLString->u16Size = cchName + 1;
+ memcpy(pSHFLString->String.utf8, pachName, cchName);
+ pSHFLString->String.utf8[cchName] = '\0';
+
+ return pSHFLString;
+ }
+ return NULL;
+}
+
+#endif