/* $Id: lnkops.c $ */ /** @file * vboxsf - VBox Linux Shared Folders VFS, operations for symbolic links. */ /* * Copyright (C) 2010-2022 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 };