diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Additions/linux/sharedfolders/lnkops.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/src/VBox/Additions/linux/sharedfolders/lnkops.c b/src/VBox/Additions/linux/sharedfolders/lnkops.c new file mode 100644 index 00000000..0d37a155 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/lnkops.c @@ -0,0 +1,305 @@ +/* $Id: lnkops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, operations for symbolic links. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "vfsmod.h" + + +/** + * Converts error codes as best we can. + */ +DECLINLINE(int) vbsf_convert_symlink_error(int vrc) +{ + if ( vrc == VERR_IS_A_DIRECTORY + || vrc == VERR_IS_A_FIFO + || vrc == VERR_IS_A_FILE + || vrc == VERR_IS_A_BLOCK_DEVICE + || vrc == VERR_IS_A_CHAR_DEVICE + || vrc == VERR_IS_A_SOCKET + || vrc == VERR_NOT_SYMLINK) + return -EINVAL; + if (vrc == VERR_PATH_NOT_FOUND) + return -ENOTDIR; + if (vrc == VERR_FILE_NOT_FOUND) + return -ENOENT; + return -EPROTO; +} + + +/** + * Does the NLS conversion of the symlink target. + */ +static int vbsf_symlink_nls_convert_slow(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf) +{ + int rc; + size_t const cchUtf8 = RTStrNLen(pszTarget, cbTargetBuf); + if (cchUtf8 < cbTargetBuf) { + /* + * If the target is short and there is a lot of space left in the target + * buffer (typically PAGE_SIZE in size), we move the input to the end + * instead of allocating a temporary buffer for it. This works because + * there shouldn't be anything that is more than 8x worse than UTF-8 + * when it comes to efficiency. + */ + char *pszFree = NULL; + char *pszUtf8; + if (cchUtf8 - 1 <= cbTargetBuf / 8) { + pszUtf8 = &pszTarget[cbTargetBuf - cchUtf8 - 1]; + cbTargetBuf -= cchUtf8 - 1; + } else { + pszFree = pszUtf8 = kmalloc(cchUtf8 + 1, GFP_KERNEL); + if (RT_UNLIKELY(!pszUtf8)) { + LogRelMax(50, ("vbsf_symlink_nls_convert_slow: failed to allocate %u bytes\n", cchUtf8 + 1)); + return -ENOMEM; + } + } + memcpy(pszUtf8, pszTarget, cchUtf8); + pszUtf8[cchUtf8] = '\0'; + + rc = vbsf_nlscpy(pSuperInfo, pszTarget, cbTargetBuf, pszUtf8, cchUtf8); + if (pszFree) + kfree(pszFree); + } else { + SFLOGFLOW(("vbsf_symlink_nls_convert_slow: Impossible! Unterminated target!\n")); + rc = -ENAMETOOLONG; + } + return rc; +} + + +/** + * Does NLS conversion if needed. + */ +DECLINLINE(int) vbsf_symlink_nls_convert(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf) +{ + if (pSuperInfo->fNlsIsUtf8) + return 0; + return vbsf_symlink_nls_convert_slow(pSuperInfo, pszTarget, cbTargetBuf); +} + +#if RTLNX_VER_MIN(4,5,0) + +/** + * Get symbolic link. + */ +static const char *vbsf_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) +{ + char *pszTarget; + if (dentry) { + pszTarget = (char *)kzalloc(PAGE_SIZE, GFP_KERNEL); + if (pszTarget) { + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb); + struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode); + int rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length, + pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1)); + if (RT_SUCCESS(rc)) { + pszTarget[PAGE_SIZE - 1] = '\0'; + SFLOGFLOW(("vbsf_get_link: %s -> %s\n", sf_i->path->String.ach, pszTarget)); + rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE); + if (rc == 0) { + vbsf_dentry_chain_increase_ttl(dentry); + set_delayed_call(done, kfree_link, pszTarget); + return pszTarget; + } + } else { + SFLOGFLOW(("vbsf_get_link: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n", + sf_i->path->String.ach, rc)); + } + kfree(pszTarget); + pszTarget = ERR_PTR(vbsf_convert_symlink_error(rc)); + } else + pszTarget = ERR_PTR(-ENOMEM); + } else + pszTarget = ERR_PTR(-ECHILD); + return pszTarget; +} + +#else /* < 4.5 */ + +# if RTLNX_VER_MAX(2,6,8) +/** + * Reads the link into the given buffer. + */ +static int vbsf_readlink(struct dentry *dentry, char *buffer, int len) +{ + int rc; + char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL); + if (pszTarget) { + struct inode *inode = dentry->d_inode; + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb); + struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode); + rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length, + pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1)); + if (RT_SUCCESS(rc)) { + pszTarget[PAGE_SIZE - 1] = '\0'; + SFLOGFLOW(("vbsf_readlink: %s -> %*s\n", sf_i->path->String.ach, pszTarget)); + rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE); + if (rc == 0) { + vbsf_dentry_chain_increase_ttl(dentry); + rc = vfs_readlink(dentry, buffer, len, pszTarget); + } + } else { + SFLOGFLOW(("vbsf_readlink: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n", sf_i->path->String.ach, rc)); + rc = vbsf_convert_symlink_error(rc); + } + free_page((unsigned long)pszTarget); + } else + rc = -ENOMEM; + return rc; +} +# endif /* < 2.6.8 */ + +/** + * Follow link in dentry. + */ +# if RTLNX_VER_MIN(4,2,0) +static const char *vbsf_follow_link(struct dentry *dentry, void **cookie) +# elif RTLNX_VER_MIN(2,6,13) +static void *vbsf_follow_link(struct dentry *dentry, struct nameidata *nd) +# else +static int vbsf_follow_link(struct dentry *dentry, struct nameidata *nd) +# endif +{ + int rc; + char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL); + if (pszTarget) { + struct inode *inode = dentry->d_inode; + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb); + struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode); + + rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length, + pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1)); + if (RT_SUCCESS(rc)) { + pszTarget[PAGE_SIZE - 1] = '\0'; + SFLOGFLOW(("vbsf_follow_link: %s -> %s\n", sf_i->path->String.ach, pszTarget)); + rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE); + if (rc == 0) { + /* + * Succeeded. For 2.6.8 and later the page gets associated + * with the caller-cookie or nameidata structure and freed + * later by vbsf_put_link(). On earlier kernels we have to + * call vfs_follow_link() which will try continue the walking + * using the buffer we pass it here. + */ + vbsf_dentry_chain_increase_ttl(dentry); +# if RTLNX_VER_MIN(4,2,0) + *cookie = pszTarget; + return pszTarget; +# elif RTLNX_VER_MIN(2,6,8) + nd_set_link(nd, pszTarget); +# if RTLNX_VER_MIN(2,6,13) + return NULL; +# else + return 0; +# endif +# else /* < 2.6.8 */ + rc = vfs_follow_link(nd, pszTarget); + free_page((unsigned long)pszTarget); + return rc; +# endif + } + + /* + * Failed. + */ + } else { + LogFunc(("VbglR0SfReadLink failed, caller=%s, rc=%Rrc\n", __func__, rc)); + rc = vbsf_convert_symlink_error(rc); + } + free_page((unsigned long)pszTarget); + } else { + rc = -ENOMEM; + } +# if RTLNX_VER_MIN(4,2,0) + *cookie = ERR_PTR(rc); + return (const char *)ERR_PTR(rc); +# elif RTLNX_VER_MIN(2,6,8) + nd_set_link(nd, (char *)ERR_PTR(rc)); +# if RTLNX_VER_MIN(2,6,13) + return NULL; +# else + return 0; +# endif +# else /* < 2.6.8 */ + return rc; +# endif /* < 2.6.8 */ +} + +# if RTLNX_VER_MIN(2,6,8) +/** + * For freeing target link buffer allocated by vbsf_follow_link. + * + * For kernels before 2.6.8 memory isn't being kept around. + */ +# if RTLNX_VER_MIN(4,2,0) +static void vbsf_put_link(struct inode *inode, void *cookie) +# elif RTLNX_VER_MIN(2,6,13) +static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) +# else +static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd) +# endif +{ +# if RTLNX_VER_MIN(2,6,13) + char *page = cookie; +# else + char *page = nd_get_link(nd); +# endif + SFLOGFLOW(("vbsf_put_link: page=%p\n", page)); + if (!IS_ERR(page)) + free_page((unsigned long)page); +} +# endif /* >= 2.6.8 */ + +#endif /* < 4.5.0 */ + +/** + * Symlink inode operations. + */ +struct inode_operations vbsf_lnk_iops = { +#if RTLNX_VER_MAX(4,10,0) +# if RTLNX_VER_MIN(2,6,8) + .readlink = generic_readlink, +# else + .readlink = vbsf_readlink, +# endif +#endif +#if RTLNX_VER_MIN(4,5,0) + .get_link = vbsf_get_link +#else + .follow_link = vbsf_follow_link, +# if RTLNX_VER_MIN(2,6,8) + .put_link = vbsf_put_link, +# endif +#endif +}; + |