diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Additions/linux/sharedfolders/vfsmod.c | |
parent | Initial commit. (diff) | |
download | virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.tar.xz virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/linux/sharedfolders/vfsmod.c')
-rw-r--r-- | src/VBox/Additions/linux/sharedfolders/vfsmod.c | 1753 |
1 files changed, 1753 insertions, 0 deletions
diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.c b/src/VBox/Additions/linux/sharedfolders/vfsmod.c new file mode 100644 index 00000000..6167683a --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.c @@ -0,0 +1,1753 @@ +/* $Id: vfsmod.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, module init/term, super block management. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + */ + +/** + * @note Anyone wishing to make changes here might wish to take a look at + * https://github.com/torvalds/linux/blob/master/Documentation/filesystems/vfs.txt + * which seems to be the closest there is to official documentation on + * writing filesystem drivers for Linux. + * + * See also: http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.odp + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "vfsmod.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" +#if RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,4) +# include <uapi/linux/mount.h> /* for MS_REMOUNT */ +#elif RTLNX_VER_MAX(3,3,0) +# include <linux/mount.h> +#endif +#include <linux/seq_file.h> +#include <linux/vfs.h> +#if RTLNX_VER_RANGE(2,5,62, 5,8,0) +# include <linux/vermagic.h> +#endif +#include <VBox/err.h> +#include <iprt/path.h> +#if RTLNX_VER_MIN(5,1,0) +# include <linux/fs_context.h> +# include <linux/fs_parser.h> +#elif RTLNX_VER_MIN(2,6,0) +# include <linux/parser.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBSF_DEFAULT_MAX_IO_PAGES RT_MIN(_16K / sizeof(RTGCPHYS64) /* => 8MB buffer */, VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT) +#define VBSF_DEFAULT_DIR_BUF_SIZE _64K + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +VBGLSFCLIENT g_SfClient; +uint32_t g_fHostFeatures = 0; +/** Last valid shared folders function number. */ +uint32_t g_uSfLastFunction = SHFL_FN_SET_FILE_SIZE; +/** Shared folders features (SHFL_FEATURE_XXX). */ +uint64_t g_fSfFeatures = 0; + +/** Protects all the vbsf_inode_info::HandleList lists. */ +spinlock_t g_SfHandleLock; + +/** The 'follow_symlinks' module parameter. + * @todo Figure out how do this for 2.4.x! */ +static int g_fFollowSymlinks = 0; + +/* forward declaration */ +static struct super_operations g_vbsf_super_ops; + + + +/** + * Copies options from the mount info structure into @a pSuperInfo. + * + * This is used both by vbsf_super_info_alloc_and_map_it() and + * vbsf_remount_fs(). + */ +static void vbsf_super_info_copy_remount_options(struct vbsf_super_info *pSuperInfo, struct vbsf_mount_info_new *info) +{ + pSuperInfo->uid = info->uid; + pSuperInfo->gid = info->gid; + + if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, szTag)) { + /* new fields */ + pSuperInfo->dmode = info->dmode; + pSuperInfo->fmode = info->fmode; + pSuperInfo->dmask = info->dmask; + pSuperInfo->fmask = info->fmask; + } else { + pSuperInfo->dmode = ~0; + pSuperInfo->fmode = ~0; + } + + if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cMaxIoPages)) { + AssertCompile(sizeof(pSuperInfo->szTag) >= sizeof(info->szTag)); + memcpy(pSuperInfo->szTag, info->szTag, sizeof(info->szTag)); + pSuperInfo->szTag[sizeof(pSuperInfo->szTag) - 1] = '\0'; + } else { + pSuperInfo->szTag[0] = '\0'; + } + + /* The max number of pages in an I/O request. This must take into + account that the physical heap generally grows in 64 KB chunks, + so we should not try push that limit. It also needs to take + into account that the host will allocate temporary heap buffers + for the I/O bytes we send/receive, so don't push the host heap + too hard as we'd have to retry with smaller requests when this + happens, which isn't too efficient. */ + pSuperInfo->cMaxIoPages = VBSF_DEFAULT_MAX_IO_PAGES; + if ( (unsigned)info->length >= sizeof(struct vbsf_mount_info_new) + && info->cMaxIoPages > 0) { + if (info->cMaxIoPages <= VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT) + pSuperInfo->cMaxIoPages = RT_MAX(info->cMaxIoPages, 2); /* read_iter/write_iter requires a minimum of 2. */ + else + printk(KERN_WARNING "vboxsf: max I/O page count (%#x) is out of range, using default (%#x) instead.\n", + info->cMaxIoPages, pSuperInfo->cMaxIoPages); + } + + pSuperInfo->cbDirBuf = VBSF_DEFAULT_DIR_BUF_SIZE; + if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cbDirBuf) + && info->cbDirBuf > 0) { + if (info->cbDirBuf <= _16M) + pSuperInfo->cbDirBuf = RT_ALIGN_32(info->cbDirBuf, PAGE_SIZE); + else + printk(KERN_WARNING "vboxsf: max directory buffer size (%#x) is out of range, using default (%#x) instead.\n", + info->cMaxIoPages, pSuperInfo->cMaxIoPages); + } + + /* + * TTLs. + */ + pSuperInfo->msTTL = info->ttl; + if (info->ttl > 0) + pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->ttl); + else if (info->ttl == 0 || info->ttl != -1) + pSuperInfo->cJiffiesDirCacheTTL = pSuperInfo->msTTL = 0; + else + pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(VBSF_DEFAULT_TTL_MS); + pSuperInfo->cJiffiesInodeTTL = pSuperInfo->cJiffiesDirCacheTTL; + + pSuperInfo->msDirCacheTTL = -1; + if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msDirCacheTTL) + && info->msDirCacheTTL >= 0) { + if (info->msDirCacheTTL > 0) { + pSuperInfo->msDirCacheTTL = info->msDirCacheTTL; + pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->msDirCacheTTL); + } else { + pSuperInfo->msDirCacheTTL = 0; + pSuperInfo->cJiffiesDirCacheTTL = 0; + } + } + + pSuperInfo->msInodeTTL = -1; + if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msInodeTTL) + && info->msInodeTTL >= 0) { + if (info->msInodeTTL > 0) { + pSuperInfo->msInodeTTL = info->msInodeTTL; + pSuperInfo->cJiffiesInodeTTL = msecs_to_jiffies(info->msInodeTTL); + } else { + pSuperInfo->msInodeTTL = 0; + pSuperInfo->cJiffiesInodeTTL = 0; + } + } + + /* + * Caching. + */ + pSuperInfo->enmCacheMode = kVbsfCacheMode_Strict; + if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, enmCacheMode)) { + switch (info->enmCacheMode) { + case kVbsfCacheMode_Default: + case kVbsfCacheMode_Strict: + break; + case kVbsfCacheMode_None: + case kVbsfCacheMode_Read: + case kVbsfCacheMode_ReadWrite: + pSuperInfo->enmCacheMode = info->enmCacheMode; + break; + default: + printk(KERN_WARNING "vboxsf: cache mode (%#x) is out of range, using default instead.\n", info->enmCacheMode); + break; + } + } +} + +/** + * Allocate the super info structure and try map the host share. + */ +static int vbsf_super_info_alloc_and_map_it(struct vbsf_mount_info_new *info, struct vbsf_super_info **sf_gp) +{ + int rc; + SHFLSTRING *str_name; + size_t name_len, str_len; + struct vbsf_super_info *pSuperInfo; + + TRACE(); + *sf_gp = NULL; /* (old gcc maybe used initialized) */ + + name_len = RTStrNLen(info->name, sizeof(info->name)); + if (name_len >= sizeof(info->name)) { + SFLOGRELBOTH(("vboxsf: Specified shared folder name is not zero terminated!\n")); + return -EINVAL; + } + if (RTStrNLen(info->nls_name, sizeof(info->nls_name)) >= sizeof(info->nls_name)) { + SFLOGRELBOTH(("vboxsf: Specified nls name is not zero terminated!\n")); + return -EINVAL; + } + + /* + * Allocate memory. + */ + str_len = offsetof(SHFLSTRING, String.utf8) + name_len + 1; + str_name = (PSHFLSTRING)kmalloc(str_len, GFP_KERNEL); + pSuperInfo = (struct vbsf_super_info *)kmalloc(sizeof(*pSuperInfo), GFP_KERNEL); + if (pSuperInfo && str_name) { + RT_ZERO(*pSuperInfo); + + str_name->u16Length = name_len; + str_name->u16Size = name_len + 1; + memcpy(str_name->String.utf8, info->name, name_len + 1); + + /* + * Init the NLS support, if needed. + */ + rc = 0; +#define _IS_UTF8(_str) (strcmp(_str, "utf8") == 0) +#define _IS_EMPTY(_str) (strcmp(_str, "") == 0) + + /* Check if NLS charset is valid and not points to UTF8 table */ + pSuperInfo->fNlsIsUtf8 = true; + if (info->nls_name[0]) { + if (_IS_UTF8(info->nls_name)) { + SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8\n")); + pSuperInfo->nls = NULL; + } else { + pSuperInfo->fNlsIsUtf8 = false; + pSuperInfo->nls = load_nls(info->nls_name); + if (pSuperInfo->nls) { + SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=%s -> %p\n", info->nls_name, pSuperInfo->nls)); + } else { + SFLOGRELBOTH(("vboxsf: Failed to load nls '%s'!\n", info->nls_name)); + rc = -EINVAL; + } + } + } else { +#ifdef CONFIG_NLS_DEFAULT + /* If no NLS charset specified, try to load the default + * one if it's not points to UTF8. */ + if (!_IS_UTF8(CONFIG_NLS_DEFAULT) + && !_IS_EMPTY(CONFIG_NLS_DEFAULT)) { + pSuperInfo->fNlsIsUtf8 = false; + pSuperInfo->nls = load_nls_default(); + SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: CONFIG_NLS_DEFAULT=%s -> %p\n", CONFIG_NLS_DEFAULT, pSuperInfo->nls)); + } else { + SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (default %s)\n", CONFIG_NLS_DEFAULT)); + pSuperInfo->nls = NULL; + } +#else + SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (no default)\n")); + pSuperInfo->nls = NULL; +#endif + } +#undef _IS_UTF8 +#undef _IS_EMPTY + if (rc == 0) { + /* + * Try mount it. + */ + rc = VbglR0SfHostReqMapFolderWithContigSimple(str_name, virt_to_phys(str_name), RTPATH_DELIMITER, + true /*fCaseSensitive*/, &pSuperInfo->map.root); + if (RT_SUCCESS(rc)) { + kfree(str_name); + + /* The rest is shared with remount. */ + vbsf_super_info_copy_remount_options(pSuperInfo, info); + + *sf_gp = pSuperInfo; + return 0; + } + + /* + * bail out: + */ + if (rc == VERR_FILE_NOT_FOUND) { + LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': share not found\n", info->name)); + rc = -ENXIO; + } else { + LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': %Rrc\n", info->name, rc)); + rc = -EPROTO; + } + if (pSuperInfo->nls) + unload_nls(pSuperInfo->nls); + } + } else { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for super info!\n")); + rc = -ENOMEM; + } + if (str_name) + kfree(str_name); + if (pSuperInfo) + kfree(pSuperInfo); + return rc; +} + +/* unmap the share and free super info [pSuperInfo] */ +static void vbsf_super_info_free(struct vbsf_super_info *pSuperInfo) +{ + int rc; + + TRACE(); + rc = VbglR0SfHostReqUnmapFolderSimple(pSuperInfo->map.root); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfHostReqUnmapFolderSimple failed rc=%Rrc\n", rc)); + + if (pSuperInfo->nls) + unload_nls(pSuperInfo->nls); + + kfree(pSuperInfo); +} + + +/** + * Initialize backing device related matters. + */ +static int vbsf_init_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo) +{ + int rc = 0; +#if RTLNX_VER_MIN(2,6,0) + /* Each new shared folder map gets a new uint64_t identifier, + * allocated in sequence. We ASSUME the sequence will not wrap. */ +# if RTLNX_VER_MIN(2,6,26) + static uint64_t s_u64Sequence = 0; + uint64_t idSeqMine = ASMAtomicIncU64(&s_u64Sequence); +# endif + struct backing_dev_info *bdi; + +# if RTLNX_VER_RANGE(4,0,0, 4,2,0) + pSuperInfo->bdi_org = sb->s_bdi; +# endif + +# if RTLNX_VER_MIN(4,12,0) + rc = super_setup_bdi_name(sb, "vboxsf-%llu", (unsigned long long)idSeqMine); + if (!rc) + bdi = sb->s_bdi; + else + return rc; +# else + bdi = &pSuperInfo->bdi; +# endif + + bdi->ra_pages = 0; /* No readahead */ + +# if RTLNX_VER_MIN(2,6,12) + bdi->capabilities = 0 +# ifdef BDI_CAP_MAP_DIRECT + | BDI_CAP_MAP_DIRECT /* MAP_SHARED */ +# endif +# ifdef BDI_CAP_MAP_COPY + | BDI_CAP_MAP_COPY /* MAP_PRIVATE */ +# endif +# ifdef BDI_CAP_READ_MAP + | BDI_CAP_READ_MAP /* can be mapped for reading */ +# endif +# ifdef BDI_CAP_WRITE_MAP + | BDI_CAP_WRITE_MAP /* can be mapped for writing */ +# endif +# ifdef BDI_CAP_EXEC_MAP + | BDI_CAP_EXEC_MAP /* can be mapped for execution */ +# endif +# ifdef BDI_CAP_STRICTLIMIT +# if RTLNX_VER_MIN(4,19,0) /* Trouble with 3.16.x/debian8. Process stops after dirty page throttling. + * Only tested successfully with 4.19. Maybe skip altogether? */ + | BDI_CAP_STRICTLIMIT; +# endif +# endif + ; +# ifdef BDI_CAP_STRICTLIMIT + /* Smalles possible amount of dirty pages: %1 of RAM. We set this to + try reduce amount of data that's out of sync with the host side. + Besides, writepages isn't implemented, so flushing is extremely slow. + Note! Extremely slow linux 3.0.0 msync doesn't seem to be related to this setting. */ + bdi_set_max_ratio(bdi, 1); +# endif +# endif /* >= 2.6.12 */ + +# if RTLNX_VER_RANGE(2,6,24, 4,12,0) + rc = bdi_init(&pSuperInfo->bdi); +# if RTLNX_VER_MIN(2,6,26) + if (!rc) + rc = bdi_register(&pSuperInfo->bdi, NULL, "vboxsf-%llu", (unsigned long long)idSeqMine); +# endif /* >= 2.6.26 */ +# endif /* 4.11.0 > version >= 2.6.24 */ + +# if RTLNX_VER_RANGE(2,6,34, 4,12,0) + if (!rc) + sb->s_bdi = bdi; +# endif + +#endif /* >= 2.6.0 */ + return rc; +} + + +/** + * Undoes what vbsf_init_backing_dev did. + */ +static void vbsf_done_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo) +{ +#if RTLNX_VER_RANGE(2,6,24, 4,12,0) + bdi_destroy(&pSuperInfo->bdi); /* includes bdi_unregister() */ + + /* Paranoia: Make sb->s_bdi not point at pSuperInfo->bdi, in case someone + trouches it after this point (we may screw up something). */ +# if RTLNX_VER_RANGE(4,0,0, 4,2,0) + sb->s_bdi = pSuperInfo->bdi_org; /* (noop_backing_dev_info is not exported) */ +# elif RTLNX_VER_RANGE(2,6,34, 4,10,0) + sb->s_bdi = &noop_backing_dev_info; +# endif +#endif +} + + +/** + * Creates the root inode and attaches it to the super block. + * + * @returns 0 on success, negative errno on failure. + * @param sb The super block. + * @param pSuperInfo Our super block info. + */ +static int vbsf_create_root_inode(struct super_block *sb, struct vbsf_super_info *pSuperInfo) +{ + SHFLFSOBJINFO fsinfo; + int rc; + + /* + * Allocate and initialize the memory for our inode info structure. + */ + struct vbsf_inode_info *sf_i = kmalloc(sizeof(*sf_i), GFP_KERNEL); + SHFLSTRING *path = kmalloc(sizeof(SHFLSTRING) + 1, GFP_KERNEL); + if (sf_i && path) { + sf_i->handle = SHFL_HANDLE_NIL; + sf_i->force_restat = false; + RTListInit(&sf_i->HandleList); +#ifdef VBOX_STRICT + sf_i->u32Magic = SF_INODE_INFO_MAGIC; +#endif + sf_i->path = path; + + path->u16Length = 1; + path->u16Size = 2; + path->String.utf8[0] = '/'; + path->String.utf8[1] = 0; + + /* + * Stat the root directory (for inode info). + */ + rc = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0); + if (rc == 0) { + /* + * Create the actual inode structure. + * Note! ls -la does display '.' and '..' entries with st_ino == 0, so root is #1. + */ +#if RTLNX_VER_MIN(2,4,25) + struct inode *iroot = iget_locked(sb, 1); +#else + struct inode *iroot = iget(sb, 1); +#endif + if (iroot) { + vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo); + VBSF_SET_INODE_INFO(iroot, sf_i); + +#if RTLNX_VER_MIN(2,4,25) + unlock_new_inode(iroot); +#endif + + /* + * Now make it a root inode. + */ +#if RTLNX_VER_MIN(3,4,0) + sb->s_root = d_make_root(iroot); +#else + sb->s_root = d_alloc_root(iroot); +#endif + if (sb->s_root) { + + return 0; + } + + SFLOGRELBOTH(("vboxsf: d_make_root failed!\n")); +#if RTLNX_VER_MAX(3,4,0) /* d_make_root calls iput */ + iput(iroot); +#endif + /* iput() will call vbsf_evict_inode()/vbsf_clear_inode(). */ + sf_i = NULL; + path = NULL; + + rc = -ENOMEM; + } else { + SFLOGRELBOTH(("vboxsf: failed to allocate root inode!\n")); + rc = -ENOMEM; + } + } else + SFLOGRELBOTH(("vboxsf: could not stat root of share: %d\n", rc)); + } else { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for root inode info!\n")); + rc = -ENOMEM; + } + if (sf_i) + kfree(sf_i); + if (path) + kfree(path); + return rc; +} + + +#if RTLNX_VER_MAX(5,1,0) +static void vbsf_init_mount_info(struct vbsf_mount_info_new *mount_info, + const char *sf_name) +{ + mount_info->ttl = mount_info->msDirCacheTTL = mount_info->msInodeTTL = -1; + mount_info->dmode = mount_info->fmode = ~0U; + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + mount_info->length = sizeof(struct vbsf_mount_info_new); + if (sf_name) { +# if RTLNX_VER_MAX(2,5,69) + strncpy(mount_info->name, sf_name, sizeof(mount_info->name)); + mount_info->name[sizeof(mount_info->name)-1] = 0; +# else + strlcpy(mount_info->name, sf_name, sizeof(mount_info->name)); +# endif + } +} +#endif + +#if RTLNX_VER_RANGE(2,6,0, 5,1,0) +/** + * The following section of code uses the Linux match_token() family of + * routines to parse string-based mount options. + */ +enum { + Opt_iocharset, /* nls_name[] */ + Opt_nls, /* alias for iocharset */ + Opt_uid, + Opt_gid, + Opt_ttl, + Opt_dmode, + Opt_fmode, + Opt_dmask, + Opt_fmask, + Opt_umask, + Opt_maxiopages, + Opt_dirbuf, + Opt_dcachettl, + Opt_inodettl, + Opt_cachemode, /* enum vbsf_cache_mode */ + Opt_tag, + Opt_err +}; + +# if RTLNX_VER_MAX(2,6,28) +static match_table_t vbsf_tokens = { +# else +static const match_table_t vbsf_tokens = { +# endif + { Opt_iocharset, "iocharset=%s" }, + { Opt_nls, "nls=%s" }, + { Opt_uid, "uid=%u" }, + { Opt_gid, "gid=%u" }, + { Opt_ttl, "ttl=%u" }, + { Opt_dmode, "dmode=%o" }, + { Opt_fmode, "fmode=%o" }, + { Opt_dmask, "dmask=%o" }, + { Opt_fmask, "fmask=%o" }, + { Opt_umask, "umask=%o" }, + { Opt_maxiopages, "maxiopages=%u" }, + { Opt_dirbuf, "dirbuf=%u" }, + { Opt_dcachettl, "dcachettl=%u" }, + { Opt_inodettl, "inodettl=%u" }, + { Opt_cachemode, "cache=%s" }, + { Opt_tag, "tag=%s" }, /* private option for automounter */ + { Opt_err, NULL } +}; + +static int vbsf_parse_mount_options(char *options, + struct vbsf_mount_info_new *mount_info) +{ + substring_t args[MAX_OPT_ARGS]; + int option; + int token; + char *p; + char *iocharset; + char *cachemode; + char *tag; + + if (!options) + return -EINVAL; + + while ((p = strsep(&options, ",")) != NULL) { + if (!*p) + continue; + + token = match_token(p, vbsf_tokens, args); + switch (token) { + case Opt_iocharset: + case Opt_nls: + iocharset = match_strdup(&args[0]); + if (!iocharset) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for iocharset!\n")); + return -ENOMEM; + } + strlcpy(mount_info->nls_name, iocharset, + sizeof(mount_info->nls_name)); + kfree(iocharset); + break; + case Opt_uid: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->uid = option; + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->gid = option; + break; + case Opt_ttl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->ttl = option; + break; + case Opt_dmode: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmode = option; + break; + case Opt_fmode: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->fmode = option; + break; + case Opt_dmask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmask = option; + break; + case Opt_fmask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->fmask = option; + break; + case Opt_umask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmask = mount_info->fmask = option; + break; + case Opt_maxiopages: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->cMaxIoPages = option; + break; + case Opt_dirbuf: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->cbDirBuf = option; + break; + case Opt_dcachettl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->msDirCacheTTL = option; + break; + case Opt_inodettl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->msInodeTTL = option; + break; + case Opt_cachemode: { + cachemode = match_strdup(&args[0]); + if (!cachemode) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for cachemode!\n")); + return -ENOMEM; + } + if (!strcmp(cachemode, "default") || !strcmp(cachemode, "strict")) + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + else if (!strcmp(cachemode, "none")) + mount_info->enmCacheMode = kVbsfCacheMode_None; + else if (!strcmp(cachemode, "read")) + mount_info->enmCacheMode = kVbsfCacheMode_Read; + else if (!strcmp(cachemode, "readwrite")) + mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", cachemode); + kfree(cachemode); + break; + } + case Opt_tag: + tag = match_strdup(&args[0]); + if (!tag) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for automount tag!\n")); + return -ENOMEM; + } + strlcpy(mount_info->szTag, tag, sizeof(mount_info->szTag)); + kfree(tag); + break; + default: + printk(KERN_ERR "unrecognised mount option \"%s\"", p); + return -EINVAL; + } + } + + return 0; +} +#endif /* 5.1.0 > version >= 2.6.0 */ + + +#if RTLNX_VER_MAX(2,6,0) +/** + * Linux kernel versions older than 2.6.0 don't have the match_token() routines + * so we parse the string-based mount options manually here. + */ +static int vbsf_parse_mount_options(char *options, + struct vbsf_mount_info_new *mount_info) +{ + char *value; + char *option; + + if (!options) + return -EINVAL; + +# if RTLNX_VER_MIN(2,3,9) + while ((option = strsep(&options, ",")) != NULL) { +# else + for (option = strtok(options, ","); option; option = strtok(NULL, ",")) { +# endif + if (!*option) + continue; + + value = strchr(option, '='); + if (value) + *value++ = '\0'; + + if (!strcmp(option, "iocharset") || !strcmp(option, "nls")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->nls_name, value, sizeof(mount_info->nls_name)); + mount_info->nls_name[sizeof(mount_info->nls_name)-1] = 0; + } else if (!strcmp(option, "uid")) { + mount_info->uid = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "gid")) { + mount_info->gid = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "ttl")) { + mount_info->ttl = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dmode")) { + mount_info->dmode = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "fmode")) { + mount_info->fmode = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dmask")) { + mount_info->dmask = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "fmask")) { + mount_info->fmask = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "umask")) { + mount_info->dmask = mount_info->fmask = simple_strtoul(value, + &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "maxiopages")) { + mount_info->cMaxIoPages = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dirbuf")) { + mount_info->cbDirBuf = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dcachettl")) { + mount_info->msDirCacheTTL = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "inodettl")) { + mount_info->msInodeTTL = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "cache")) { + if (!value || !*value) + return -EINVAL; + if (!strcmp(value, "default") || !strcmp(value, "strict")) + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + else if (!strcmp(value, "none")) + mount_info->enmCacheMode = kVbsfCacheMode_None; + else if (!strcmp(value, "read")) + mount_info->enmCacheMode = kVbsfCacheMode_Read; + else if (!strcmp(value, "readwrite")) + mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", value); + } else if (!strcmp(option, "tag")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->szTag, value, sizeof(mount_info->szTag)); + mount_info->szTag[sizeof(mount_info->szTag)-1] = 0; + } else if (!strcmp(option, "sf_name")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->name, value, sizeof(mount_info->name)); + mount_info->name[sizeof(mount_info->name)-1] = 0; + } else { + printk(KERN_ERR "unrecognised mount option \"%s\"", option); + return -EINVAL; + } + } + + return 0; +} +#endif + + +/** + * This is called by vbsf_read_super_24(), vbsf_read_super_26(), and + * vbsf_get_tree() when vfs mounts the fs and wants to read the super_block. + * + * Calls vbsf_super_info_alloc_and_map_it() to map the folder and allocate super + * information structure. + * + * Initializes @a sb, initializes root inode and dentry. + * + * Should respect @a flags. + */ +#if RTLNX_VER_MIN(5,1,0) +static int vbsf_read_super_aux(struct super_block *sb, struct fs_context *fc) +#else +static int vbsf_read_super_aux(struct super_block *sb, void *data, int flags) +#endif +{ + int rc; + struct vbsf_super_info *pSuperInfo; + + TRACE(); +#if RTLNX_VER_MAX(5,1,0) + if (!data) { + SFLOGRELBOTH(("vboxsf: No mount data. Is mount.vboxsf installed (typically in /sbin)?\n")); + return -EINVAL; + } + + if (flags & MS_REMOUNT) { + SFLOGRELBOTH(("vboxsf: Remounting is not supported!\n")); + return -ENOSYS; + } +#endif + + /* + * Create our super info structure and map the shared folder. + */ +#if RTLNX_VER_MIN(5,1,0) + struct vbsf_mount_info_new *info = fc->fs_private; + rc = vbsf_super_info_alloc_and_map_it(info, &pSuperInfo); +#else + rc = vbsf_super_info_alloc_and_map_it((struct vbsf_mount_info_new *)data, &pSuperInfo); +#endif + if (rc == 0) { + /* + * Initialize the super block structure (must be done before + * root inode creation). + */ + sb->s_magic = 0xface; + sb->s_blocksize = 1024; +#if RTLNX_VER_MIN(2,4,3) + /* Required for seek/sendfile (see 'loff_t max' in fs/read_write.c / do_sendfile()). */ +# if defined MAX_LFS_FILESIZE + sb->s_maxbytes = MAX_LFS_FILESIZE; +# elif BITS_PER_LONG == 32 + sb->s_maxbytes = (loff_t)ULONG_MAX << PAGE_SHIFT; +# else + sb->s_maxbytes = INT64_MAX; +# endif +#endif +#if RTLNX_VER_MIN(2,6,11) + sb->s_time_gran = 1; /* This might be a little optimistic for windows hosts, where it should be 100. */ +#endif + sb->s_op = &g_vbsf_super_ops; +#if RTLNX_VER_MIN(2,6,38) + sb->s_d_op = &vbsf_dentry_ops; +#endif + + /* + * Initialize the backing device. This is important for memory mapped + * files among other things. + */ + rc = vbsf_init_backing_dev(sb, pSuperInfo); + if (rc == 0) { + /* + * Create the root inode and we're done. + */ + rc = vbsf_create_root_inode(sb, pSuperInfo); + if (rc == 0) { + VBSF_SET_SUPER_INFO(sb, pSuperInfo); + SFLOGFLOW(("vbsf_read_super_aux: returns successfully\n")); + return 0; + } + vbsf_done_backing_dev(sb, pSuperInfo); + } else + SFLOGRELBOTH(("vboxsf: backing device information initialization failed: %d\n", rc)); + vbsf_super_info_free(pSuperInfo); + } + return rc; +} + + +/** + * This is called when vfs is about to destroy the @a inode. + * + * We must free the inode info structure here. + */ +#if RTLNX_VER_MIN(2,6,36) +static void vbsf_evict_inode(struct inode *inode) +#else +static void vbsf_clear_inode(struct inode *inode) +#endif +{ + struct vbsf_inode_info *sf_i; + + TRACE(); + + /* + * Flush stuff. + */ +#if RTLNX_VER_MIN(2,6,36) + truncate_inode_pages(&inode->i_data, 0); +# if RTLNX_VER_MIN(3,5,0) + clear_inode(inode); +# else + end_writeback(inode); +# endif +#endif + /* + * Clean up our inode info. + */ + sf_i = VBSF_GET_INODE_INFO(inode); + if (sf_i) { + VBSF_SET_INODE_INFO(inode, NULL); + + Assert(sf_i->u32Magic == SF_INODE_INFO_MAGIC); + BUG_ON(!sf_i->path); + kfree(sf_i->path); + vbsf_handle_drop_chain(sf_i); +# ifdef VBOX_STRICT + sf_i->u32Magic = SF_INODE_INFO_MAGIC_DEAD; +# endif + kfree(sf_i); + } +} + + +/* this is called by vfs when it wants to populate [inode] with data. + the only thing that is known about inode at this point is its index + hence we can't do anything here, and let lookup/whatever with the + job to properly fill then [inode] */ +#if RTLNX_VER_MAX(2,6,25) +static void vbsf_read_inode(struct inode *inode) +{ +} +#endif + + +/* vfs is done with [sb] (umount called) call [vbsf_super_info_free] to unmap + the folder and free [pSuperInfo] */ +static void vbsf_put_super(struct super_block *sb) +{ + struct vbsf_super_info *pSuperInfo; + + pSuperInfo = VBSF_GET_SUPER_INFO(sb); + BUG_ON(!pSuperInfo); + vbsf_done_backing_dev(sb, pSuperInfo); + vbsf_super_info_free(pSuperInfo); +} + + +/** + * Get file system statistics. + */ +#if RTLNX_VER_MIN(2,6,18) +static int vbsf_statfs(struct dentry *dentry, struct kstatfs *stat) +#elif RTLNX_VER_MIN(2,5,73) +static int vbsf_statfs(struct super_block *sb, struct kstatfs *stat) +#else +static int vbsf_statfs(struct super_block *sb, struct statfs *stat) +#endif +{ +#if RTLNX_VER_MIN(2,6,18) + struct super_block *sb = dentry->d_inode->i_sb; +#endif + int rc; + VBOXSFVOLINFOREQ *pReq = (VBOXSFVOLINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq)); + if (pReq) { + SHFLVOLINFO *pVolInfo = &pReq->VolInfo; + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb); + rc = VbglR0SfHostReqQueryVolInfo(pSuperInfo->map.root, pReq, SHFL_HANDLE_ROOT); + if (RT_SUCCESS(rc)) { + stat->f_type = UINT32_C(0x786f4256); /* 'VBox' little endian */ + stat->f_bsize = pVolInfo->ulBytesPerAllocationUnit; +#if RTLNX_VER_MIN(2,5,73) + stat->f_frsize = pVolInfo->ulBytesPerAllocationUnit; +#endif + stat->f_blocks = pVolInfo->ullTotalAllocationBytes + / pVolInfo->ulBytesPerAllocationUnit; + stat->f_bfree = pVolInfo->ullAvailableAllocationBytes + / pVolInfo->ulBytesPerAllocationUnit; + stat->f_bavail = pVolInfo->ullAvailableAllocationBytes + / pVolInfo->ulBytesPerAllocationUnit; + stat->f_files = 1000; + stat->f_ffree = 1000000; /* don't return 0 here since the guest may think + * that it is not possible to create any more files */ + stat->f_fsid.val[0] = 0; + stat->f_fsid.val[1] = 0; + stat->f_namelen = 255; +#if RTLNX_VER_MIN(2,6,36) + stat->f_flags = 0; /* not valid */ +#endif + RT_ZERO(stat->f_spare); + rc = 0; + } else + rc = -RTErrConvertToErrno(rc); + VbglR0PhysHeapFree(pReq); + } else + rc = -ENOMEM; + return rc; +} + +#if RTLNX_VER_MIN(5,1,0) +static int vbsf_remount_fs(struct super_block *sb, + struct vbsf_mount_info_new *info) +#else +static int vbsf_remount_fs(struct super_block *sb, int *flags, char *data) +#endif +{ +#if RTLNX_VER_MIN(2,4,23) + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb); + struct vbsf_inode_info *sf_i; + struct inode *iroot; + SHFLFSOBJINFO fsinfo; + int err; + Assert(pSuperInfo); + +# if RTLNX_VER_MIN(5,1,0) + vbsf_super_info_copy_remount_options(pSuperInfo, info); +# else + if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + vbsf_super_info_copy_remount_options(pSuperInfo, (struct vbsf_mount_info_new *)data); + } else { + struct vbsf_mount_info_new mount_opts = { '\0' }; + vbsf_init_mount_info(&mount_opts, NULL); + err = vbsf_parse_mount_options(data, &mount_opts); + if (err) + return err; + vbsf_super_info_copy_remount_options(pSuperInfo, &mount_opts); + } +# endif + + /* '.' and '..' entries are st_ino == 0 so root is #1 */ + iroot = ilookup(sb, 1); + if (!iroot) + return -ENOSYS; + + sf_i = VBSF_GET_INODE_INFO(iroot); + err = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0); + BUG_ON(err != 0); + vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo); + iput(iroot); + return 0; +#else /* < 2.4.23 */ + return -ENOSYS; +#endif /* < 2.4.23 */ +} + + +/** + * Show mount options. + * + * This is needed by the VBoxService automounter in order for it to pick up + * the the 'szTag' option value it sets on its mount. + */ +#if RTLNX_VER_MAX(3,3,0) +static int vbsf_show_options(struct seq_file *m, struct vfsmount *mnt) +#else +static int vbsf_show_options(struct seq_file *m, struct dentry *root) +#endif +{ +#if RTLNX_VER_MAX(3,3,0) + struct super_block *sb = mnt->mnt_sb; +#else + struct super_block *sb = root->d_sb; +#endif + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb); + if (pSuperInfo) { + /* Performance related options: */ + if (pSuperInfo->msTTL != -1) + seq_printf(m, ",ttl=%d", pSuperInfo->msTTL); + if (pSuperInfo->msDirCacheTTL >= 0) + seq_printf(m, ",dcachettl=%d", pSuperInfo->msDirCacheTTL); + if (pSuperInfo->msInodeTTL >= 0) + seq_printf(m, ",inodettl=%d", pSuperInfo->msInodeTTL); + if (pSuperInfo->cMaxIoPages != VBSF_DEFAULT_MAX_IO_PAGES) + seq_printf(m, ",maxiopages=%u", pSuperInfo->cMaxIoPages); + if (pSuperInfo->cbDirBuf != VBSF_DEFAULT_DIR_BUF_SIZE) + seq_printf(m, ",dirbuf=%u", pSuperInfo->cbDirBuf); + switch (pSuperInfo->enmCacheMode) { + default: AssertFailed(); + case kVbsfCacheMode_Strict: + break; + case kVbsfCacheMode_None: seq_puts(m, ",cache=none"); break; + case kVbsfCacheMode_Read: seq_puts(m, ",cache=read"); break; + case kVbsfCacheMode_ReadWrite: seq_puts(m, ",cache=readwrite"); break; + } + + /* Attributes and NLS: */ + seq_printf(m, ",iocharset=%s", pSuperInfo->nls ? pSuperInfo->nls->charset : "utf8"); + seq_printf(m, ",uid=%u,gid=%u", pSuperInfo->uid, pSuperInfo->gid); + if (pSuperInfo->dmode != ~0) + seq_printf(m, ",dmode=0%o", pSuperInfo->dmode); + if (pSuperInfo->fmode != ~0) + seq_printf(m, ",fmode=0%o", pSuperInfo->fmode); + if (pSuperInfo->dmask != 0) + seq_printf(m, ",dmask=0%o", pSuperInfo->dmask); + if (pSuperInfo->fmask != 0) + seq_printf(m, ",fmask=0%o", pSuperInfo->fmask); + + /* Misc: */ + if (pSuperInfo->szTag[0] != '\0') { + seq_puts(m, ",tag="); + seq_escape(m, pSuperInfo->szTag, " \t\n\\"); + } + } + return 0; +} + + +/** + * Super block operations. + */ +static struct super_operations g_vbsf_super_ops = { +#if RTLNX_VER_MAX(2,6,36) + .clear_inode = vbsf_clear_inode, +#else + .evict_inode = vbsf_evict_inode, +#endif +#if RTLNX_VER_MAX(2,6,25) + .read_inode = vbsf_read_inode, +#endif + .put_super = vbsf_put_super, + .statfs = vbsf_statfs, +#if RTLNX_VER_MAX(5,1,0) + .remount_fs = vbsf_remount_fs, +#endif + .show_options = vbsf_show_options +}; + + + +/********************************************************************************************************************************* +* File system type related stuff. * +*********************************************************************************************************************************/ + +#if RTLNX_VER_RANGE(2,5,4, 5,1,0) + +static int vbsf_read_super_26(struct super_block *sb, void *data, int flags) +{ + int err; + + TRACE(); + err = vbsf_read_super_aux(sb, data, flags); + if (err) + printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err); + + return err; +} + +# if RTLNX_VER_MIN(2,6,39) +static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) +{ + TRACE(); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + return mount_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26); + } else { + return mount_nodev(fs_type, flags, data, vbsf_read_super_26); + } +} +# elif RTLNX_VER_MIN(2,6,18) +static int vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) +{ + TRACE(); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return rc; + return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26, + mnt); + } else { + return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26, mnt); + } +} +# else /* 2.6.18 > version >= 2.5.4 */ +static struct super_block *vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) +{ + TRACE(); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26); + } else { + return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26); + } +} +# endif +#endif /* 5.1.0 > version >= 2.5.4 */ + +#if RTLNX_VER_MAX(2,5,4) /* < 2.5.4 */ + +static struct super_block *vbsf_read_super_24(struct super_block *sb, void *data, int flags) +{ + int err; + + TRACE(); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, NULL); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + err = vbsf_read_super_aux(sb, &mount_opts, flags); + } else { + err = vbsf_read_super_aux(sb, data, flags); + } + if (err) { + printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err); + return NULL; + } + + return sb; +} + +static DECLARE_FSTYPE(g_vboxsf_fs_type, "vboxsf", vbsf_read_super_24, 0); + +#endif /* < 2.5.4 */ + +#if RTLNX_VER_MIN(5,1,0) + +/** + * The following section of code uses the Linux filesystem mount API (also + * known as the "filesystem context API") to parse string-based mount options. + * The API is described here: + * https://www.kernel.org/doc/Documentation/filesystems/mount_api.txt + */ +enum vbsf_cache_modes { + VBSF_CACHE_DEFAULT, + VBSF_CACHE_NONE, + VBSF_CACHE_STRICT, + VBSF_CACHE_READ, + VBSF_CACHE_RW +}; + +static const struct constant_table vbsf_param_cache_mode[] = { + { "default", VBSF_CACHE_DEFAULT }, + { "none", VBSF_CACHE_NONE }, + { "strict", VBSF_CACHE_STRICT }, + { "read", VBSF_CACHE_READ }, + { "readwrite", VBSF_CACHE_RW }, + {} +}; + +enum { + Opt_iocharset, /* nls_name[] */ + Opt_nls, /* alias for iocharset */ + Opt_uid, + Opt_gid, + Opt_ttl, + Opt_dmode, + Opt_fmode, + Opt_dmask, + Opt_fmask, + Opt_umask, + Opt_maxiopages, + Opt_dirbuf, + Opt_dcachettl, + Opt_inodettl, + Opt_cachemode, /* enum vbsf_cache_mode */ + Opt_tag +}; + +# if RTLNX_VER_MAX(5,6,0) +static const struct fs_parameter_spec vbsf_fs_specs[] = { +# else +static const struct fs_parameter_spec vbsf_fs_parameters[] = { +# endif + fsparam_string("iocharset", Opt_iocharset), + fsparam_string("nls", Opt_nls), + fsparam_u32 ("uid", Opt_uid), + fsparam_u32 ("gid", Opt_gid), + fsparam_u32 ("ttl", Opt_ttl), + fsparam_u32oct("dmode", Opt_dmode), + fsparam_u32oct("fmode", Opt_fmode), + fsparam_u32oct("dmask", Opt_dmask), + fsparam_u32oct("fmask", Opt_fmask), + fsparam_u32oct("umask", Opt_umask), + fsparam_u32 ("maxiopages", Opt_maxiopages), + fsparam_u32 ("dirbuf", Opt_dirbuf), + fsparam_u32 ("dcachettl", Opt_dcachettl), + fsparam_u32 ("inodettl", Opt_inodettl), +# if RTLNX_VER_MAX(5,6,0) + fsparam_enum ("cache", Opt_cachemode), +# else + fsparam_enum ("cache", Opt_cachemode, vbsf_param_cache_mode), +# endif + fsparam_string("tag", Opt_tag), + {} +}; + +# if RTLNX_VER_MAX(5,6,0) +static const struct fs_parameter_enum vbsf_fs_enums[] = { + { Opt_cachemode, "default", VBSF_CACHE_DEFAULT }, + { Opt_cachemode, "none", VBSF_CACHE_NONE }, + { Opt_cachemode, "strict", VBSF_CACHE_STRICT }, + { Opt_cachemode, "read", VBSF_CACHE_READ }, + { Opt_cachemode, "readwrite", VBSF_CACHE_RW }, + {} +}; + +static const struct fs_parameter_description vbsf_fs_parameters = { + .name = "vboxsf", + .specs = vbsf_fs_specs, + .enums = vbsf_fs_enums +}; +# endif + +/** + * Parse the (string-based) mount options passed in as -o foo,bar=123,etc. + */ +static int vbsf_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct fs_parse_result result; + struct vbsf_mount_info_new *info = fc->fs_private; + int opt; + +# if RTLNX_VER_MAX(5,6,0) + opt = fs_parse(fc, &vbsf_fs_parameters, param, &result); +# else + opt = fs_parse(fc, vbsf_fs_parameters, param, &result); +# endif + if (opt < 0) + return opt; + + switch (opt) { + case Opt_iocharset: + case Opt_nls: + strlcpy(info->nls_name, param->string, sizeof(info->nls_name)); + break; + case Opt_uid: + info->uid = result.uint_32; + break; + case Opt_gid: + info->gid = result.uint_32; + break; + case Opt_ttl: + info->ttl = result.uint_32; + break; + case Opt_dmode: + if (result.uint_32 & ~0777) + return invalf(fc, "Invalid dmode specified: '%o'", result.uint_32); + info->dmode = result.uint_32; + break; + case Opt_fmode: + if (result.uint_32 & ~0777) + return invalf(fc, "Invalid fmode specified: '%o'", result.uint_32); + info->fmode = result.uint_32; + break; + case Opt_dmask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid dmask specified: '%o'", result.uint_32); + info->dmask = result.uint_32; + break; + case Opt_fmask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid fmask specified: '%o'", result.uint_32); + info->fmask = result.uint_32; + break; + case Opt_umask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid umask specified: '%o'", result.uint_32); + info->dmask = info->fmask = result.uint_32; + break; + case Opt_maxiopages: + info->cMaxIoPages = result.uint_32; + break; + case Opt_dirbuf: + info->cbDirBuf = result.uint_32; + break; + case Opt_dcachettl: + info->msDirCacheTTL = result.uint_32; + break; + case Opt_inodettl: + info->msInodeTTL = result.uint_32; + break; + case Opt_cachemode: + if (result.uint_32 == VBSF_CACHE_DEFAULT || result.uint_32 == VBSF_CACHE_STRICT) + info->enmCacheMode = kVbsfCacheMode_Strict; + else if (result.uint_32 == VBSF_CACHE_NONE) + info->enmCacheMode = kVbsfCacheMode_None; + else if (result.uint_32 == VBSF_CACHE_READ) + info->enmCacheMode = kVbsfCacheMode_Read; + else if (result.uint_32 == VBSF_CACHE_RW) + info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%u) is out of range, using default instead.\n", result.uint_32); + break; + case Opt_tag: + strlcpy(info->szTag, param->string, sizeof(info->szTag)); + break; + default: + return invalf(fc, "Invalid mount option: '%s'", param->key); + } + + return 0; +} + +/** + * Parse the mount options provided whether by the mount.vboxsf utility + * which supplies the mount information as a page of data or else as a + * string in the following format: key[=val][,key[=val]]*. + */ +static int vbsf_parse_monolithic(struct fs_context *fc, void *data) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (data) { + if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + memcpy(info, data, sizeof(struct vbsf_mount_info_new)); + } else { + /* this will call vbsf_parse_param() */ + return generic_parse_monolithic(fc, data); + } + } + + return 0; +} + +/** + * Clean up the filesystem-specific part of the filesystem context. + */ +static void vbsf_free_ctx(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (info) { + kfree(info); + fc->fs_private = NULL; + } +} + +/** + * Create the mountable root and superblock which can then be used later for + * mounting the shared folder. The superblock is populated by + * vbsf_read_super_aux() which also sets up the shared folder mapping and the + * related paperwork in preparation for mounting the shared folder. + */ +static int vbsf_get_tree(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (!fc->source) { + SFLOGRELBOTH(("vboxsf: No shared folder specified\n")); + return invalf(fc, "vboxsf: No shared folder specified"); + } + + /* fc->source (the shared folder name) is set after vbsf_init_fs_ctx() */ + strlcpy(info->name, fc->source, sizeof(info->name)); + +# if RTLNX_VER_MAX(5,3,0) + return vfs_get_super(fc, vfs_get_independent_super, vbsf_read_super_aux); +# else + return get_tree_nodev(fc, vbsf_read_super_aux); +# endif +} + +/** + * Reconfigures the superblock based on the mount information stored in the + * filesystem context. Called via '-o remount' (aka mount(2) with MS_REMOUNT) + * and is the equivalent of .fs_remount. + */ +static int vbsf_reconfigure(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + struct super_block *sb = fc->root->d_sb; + + return vbsf_remount_fs(sb, info); +} + +static const struct fs_context_operations vbsf_context_ops = { + .parse_param = vbsf_parse_param, + .parse_monolithic = vbsf_parse_monolithic, + .free = vbsf_free_ctx, + .get_tree = vbsf_get_tree, + .reconfigure = vbsf_reconfigure +}; + +/** + * Set up the filesystem mount context. + */ +static int vbsf_init_fs_context(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for mount options\n")); + return -ENOMEM; + } + + /* set default values for the mount information structure */ + info->ttl = info->msDirCacheTTL = info->msInodeTTL = -1; + info->dmode = info->fmode = ~0U; + info->enmCacheMode = kVbsfCacheMode_Strict; + info->length = sizeof(struct vbsf_mount_info_new); + + fc->fs_private = info; + fc->ops = &vbsf_context_ops; + + return 0; +} +#endif /* >= 5.1.0 */ + + +#if RTLNX_VER_MIN(2,5,4) +/** + * File system registration structure. + */ +static struct file_system_type g_vboxsf_fs_type = { + .owner = THIS_MODULE, + .name = "vboxsf", +# if RTLNX_VER_MIN(5,1,0) + .init_fs_context = vbsf_init_fs_context, +# if RTLNX_VER_MAX(5,6,0) + .parameters = &vbsf_fs_parameters, +# else + .parameters = vbsf_fs_parameters, +# endif +# elif RTLNX_VER_MIN(2,6,39) + .mount = sf_mount, +# else + .get_sb = vbsf_get_sb, +# endif + .kill_sb = kill_anon_super +}; +#endif /* >= 2.5.4 */ + + +/********************************************************************************************************************************* +* Module stuff * +*********************************************************************************************************************************/ + +/** + * Called on module initialization. + */ +static int __init init(void) +{ + int rc; + SFLOGFLOW(("vboxsf: init\n")); + + /* + * Must be paranoid about the vbsf_mount_info_new size. + */ + AssertCompile(sizeof(struct vbsf_mount_info_new) <= PAGE_SIZE); + if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) { + printk(KERN_ERR + "vboxsf: Mount information structure is too large %lu\n" + "vboxsf: Must be less than or equal to %lu\n", + (unsigned long)sizeof(struct vbsf_mount_info_new), + (unsigned long)PAGE_SIZE); + return -EINVAL; + } + + /* + * Initialize stuff. + */ + spin_lock_init(&g_SfHandleLock); + rc = VbglR0SfInit(); + if (RT_SUCCESS(rc)) { + /* + * Try connect to the shared folder HGCM service. + * It is possible it is not there. + */ + rc = VbglR0SfConnect(&g_SfClient); + if (RT_SUCCESS(rc)) { + /* + * Query host HGCM features and afterwards (must be last) shared folder features. + */ + rc = VbglR0QueryHostFeatures(&g_fHostFeatures); + if (RT_FAILURE(rc)) + { + LogRel(("vboxsf: VbglR0QueryHostFeatures failed: rc=%Rrc (ignored)\n", rc)); + g_fHostFeatures = 0; + } + VbglR0SfHostReqQueryFeaturesSimple(&g_fSfFeatures, &g_uSfLastFunction); + LogRel(("vboxsf: g_fHostFeatures=%#x g_fSfFeatures=%#RX64 g_uSfLastFunction=%u\n", + g_fHostFeatures, g_fSfFeatures, g_uSfLastFunction)); + + /* + * Tell the shared folder service about our expectations: + * - UTF-8 strings (rather than UTF-16) + * - Wheter to return or follow (default) symbolic links. + */ + rc = VbglR0SfHostReqSetUtf8Simple(); + if (RT_SUCCESS(rc)) { + if (!g_fFollowSymlinks) { + rc = VbglR0SfHostReqSetSymlinksSimple(); + if (RT_FAILURE(rc)) + printk(KERN_WARNING "vboxsf: Host unable to enable showing symlinks, rc=%d\n", rc); + } + /* + * Now that we're ready for action, try register the + * file system with the kernel. + */ + rc = register_filesystem(&g_vboxsf_fs_type); + if (rc == 0) { + printk(KERN_INFO "vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n"); +#ifdef VERMAGIC_STRING + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n", + VERMAGIC_STRING, LINUX_VERSION_CODE)); +#elif defined(UTS_RELEASE) + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n", + UTS_RELEASE, LINUX_VERSION_CODE)); +#else + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (LINUX_VERSION_CODE=%#x)\n", LINUX_VERSION_CODE)); +#endif + return 0; + } + + /* + * Failed. Bail out. + */ + LogRel(("vboxsf: register_filesystem failed: rc=%d\n", rc)); + } else { + LogRel(("vboxsf: VbglR0SfSetUtf8 failed, rc=%Rrc\n", rc)); + rc = -EPROTO; + } + VbglR0SfDisconnect(&g_SfClient); + } else { + LogRel(("vboxsf: VbglR0SfConnect failed, rc=%Rrc\n", rc)); + rc = rc == VERR_HGCM_SERVICE_NOT_FOUND ? -EHOSTDOWN : -ECONNREFUSED; + } + VbglR0SfTerm(); + } else { + LogRel(("vboxsf: VbglR0SfInit failed, rc=%Rrc\n", rc)); + rc = -EPROTO; + } + return rc; +} + + +/** + * Called on module finalization. + */ +static void __exit fini(void) +{ + SFLOGFLOW(("vboxsf: fini\n")); + + unregister_filesystem(&g_vboxsf_fs_type); + VbglR0SfDisconnect(&g_SfClient); + VbglR0SfTerm(); +} + + +/* + * Module parameters. + */ +#if RTLNX_VER_MIN(2,5,52) +module_param_named(follow_symlinks, g_fFollowSymlinks, int, 0); +MODULE_PARM_DESC(follow_symlinks, + "Let host resolve symlinks rather than showing them"); +#endif + + +/* + * Module declaration related bits. + */ +module_init(init); +module_exit(fini); + +MODULE_DESCRIPTION(VBOX_PRODUCT " VFS Module for Host File System Access"); +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_LICENSE("GPL and additional rights"); +#ifdef MODULE_ALIAS_FS +MODULE_ALIAS_FS("vboxsf"); +#endif +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV)); +#endif + |