summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/linux/sharedfolders/vfsmod.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Additions/linux/sharedfolders/vfsmod.c
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-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.c683
1 files changed, 683 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..209424a6
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.c
@@ -0,0 +1,683 @@
+/* $Id: vfsmod.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, module init/term, super block management.
+ */
+
+/*
+ * Copyright (C) 2006-2019 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.
+ */
+
+#include "vfsmod.h"
+#include "version-generated.h"
+#include "revision-generated.h"
+#include "product-generated.h"
+#include "VBoxGuestR0LibInternal.h"
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
+# include <linux/mount.h>
+#endif
+#include <linux/seq_file.h>
+
+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
+
+/* globals */
+VBGLSFCLIENT client_handle;
+
+/* forward declarations */
+static struct super_operations sf_super_ops;
+
+/* allocate global info, try to map host share */
+static int sf_glob_alloc(struct vbsf_mount_info_new *info,
+ struct sf_glob_info **sf_gp)
+{
+ int err, rc;
+ SHFLSTRING *str_name;
+ size_t name_len, str_len;
+ struct sf_glob_info *sf_g;
+
+ TRACE();
+ sf_g = kmalloc(sizeof(*sf_g), GFP_KERNEL);
+ if (!sf_g) {
+ err = -ENOMEM;
+ LogRelFunc(("could not allocate memory for global info\n"));
+ goto fail0;
+ }
+
+ RT_ZERO(*sf_g);
+
+ if (info->nullchar != '\0'
+ || info->signature[0] != VBSF_MOUNT_SIGNATURE_BYTE_0
+ || info->signature[1] != VBSF_MOUNT_SIGNATURE_BYTE_1
+ || info->signature[2] != VBSF_MOUNT_SIGNATURE_BYTE_2) {
+ err = -EINVAL;
+ goto fail1;
+ }
+
+ info->name[sizeof(info->name) - 1] = 0;
+ info->nls_name[sizeof(info->nls_name) - 1] = 0;
+
+ name_len = strlen(info->name);
+ str_len = offsetof(SHFLSTRING, String.utf8) + name_len + 1;
+ str_name = kmalloc(str_len, GFP_KERNEL);
+ if (!str_name) {
+ err = -ENOMEM;
+ LogRelFunc(("could not allocate memory for host name\n"));
+ goto fail1;
+ }
+
+ str_name->u16Length = name_len;
+ str_name->u16Size = name_len + 1;
+ memcpy(str_name->String.utf8, info->name, name_len + 1);
+
+#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 */
+ if (info->nls_name[0]) {
+ if (_IS_UTF8(info->nls_name))
+ sf_g->nls = NULL;
+ else {
+ sf_g->nls = load_nls(info->nls_name);
+ if (!sf_g->nls) {
+ err = -EINVAL;
+ LogFunc(("failed to load nls %s\n",
+ info->nls_name));
+ kfree(str_name);
+ goto fail1;
+ }
+ }
+ } 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))
+ sf_g->nls = load_nls_default();
+ else
+ sf_g->nls = NULL;
+#else
+ sf_g->nls = NULL;
+#endif
+
+#undef _IS_UTF8
+#undef _IS_EMPTY
+ }
+
+ rc = VbglR0SfMapFolder(&client_handle, str_name, &sf_g->map);
+ kfree(str_name);
+
+ if (RT_FAILURE(rc)) {
+ err = -EPROTO;
+ LogFunc(("VbglR0SfMapFolder failed rc=%d\n", rc));
+ goto fail2;
+ }
+
+ sf_g->ttl = info->ttl;
+ sf_g->uid = info->uid;
+ sf_g->gid = info->gid;
+
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, tag)) {
+ /* new fields */
+ sf_g->dmode = info->dmode;
+ sf_g->fmode = info->fmode;
+ sf_g->dmask = info->dmask;
+ sf_g->fmask = info->fmask;
+ } else {
+ sf_g->dmode = ~0;
+ sf_g->fmode = ~0;
+ }
+
+ if ((unsigned)info->length >= sizeof(struct vbsf_mount_info_new)) {
+ AssertCompile(sizeof(sf_g->tag) >= sizeof(info->tag));
+ memcpy(sf_g->tag, info->tag, sizeof(info->tag));
+ sf_g->tag[sizeof(sf_g->tag) - 1] = '\0';
+ } else {
+ sf_g->tag[0] = '\0';
+ }
+
+ *sf_gp = sf_g;
+ return 0;
+
+ fail2:
+ if (sf_g->nls)
+ unload_nls(sf_g->nls);
+
+ fail1:
+ kfree(sf_g);
+
+ fail0:
+ return err;
+}
+
+/* unmap the share and free global info [sf_g] */
+static void sf_glob_free(struct sf_glob_info *sf_g)
+{
+ int rc;
+
+ TRACE();
+ rc = VbglR0SfUnmapFolder(&client_handle, &sf_g->map);
+ if (RT_FAILURE(rc))
+ LogFunc(("VbglR0SfUnmapFolder failed rc=%d\n", rc));
+
+ if (sf_g->nls)
+ unload_nls(sf_g->nls);
+
+ kfree(sf_g);
+}
+
+/**
+ * This is called (by sf_read_super_[24|26] when vfs mounts the fs and
+ * wants to read super_block.
+ *
+ * calls [sf_glob_alloc] to map the folder and allocate global
+ * information structure.
+ *
+ * initializes [sb], initializes root inode and dentry.
+ *
+ * should respect [flags]
+ */
+static int sf_read_super_aux(struct super_block *sb, void *data, int flags)
+{
+ int err;
+ struct dentry *droot;
+ struct inode *iroot;
+ struct sf_inode_info *sf_i;
+ struct sf_glob_info *sf_g;
+ SHFLFSOBJINFO fsinfo;
+ struct vbsf_mount_info_new *info;
+ bool fInodePut = true;
+
+ TRACE();
+ if (!data) {
+ LogFunc(("no mount info specified\n"));
+ return -EINVAL;
+ }
+
+ info = data;
+
+ if (flags & MS_REMOUNT) {
+ LogFunc(("remounting is not supported\n"));
+ return -ENOSYS;
+ }
+
+ err = sf_glob_alloc(info, &sf_g);
+ if (err)
+ goto fail0;
+
+ sf_i = kmalloc(sizeof(*sf_i), GFP_KERNEL);
+ if (!sf_i) {
+ err = -ENOMEM;
+ LogRelFunc(("could not allocate memory for root inode info\n"));
+ goto fail1;
+ }
+
+ sf_i->handle = SHFL_HANDLE_NIL;
+ sf_i->path = kmalloc(sizeof(SHFLSTRING) + 1, GFP_KERNEL);
+ if (!sf_i->path) {
+ err = -ENOMEM;
+ LogRelFunc(("could not allocate memory for root inode path\n"));
+ goto fail2;
+ }
+
+ sf_i->path->u16Length = 1;
+ sf_i->path->u16Size = 2;
+ sf_i->path->String.utf8[0] = '/';
+ sf_i->path->String.utf8[1] = 0;
+ sf_i->force_reread = 0;
+
+ err = sf_stat(__func__, sf_g, sf_i->path, &fsinfo, 0);
+ if (err) {
+ LogFunc(("could not stat root of share\n"));
+ goto fail3;
+ }
+
+ sb->s_magic = 0xface;
+ sb->s_blocksize = 1024;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 3)
+ /* Required for seek/sendfile.
+ *
+ * Must by less than or equal to INT64_MAX despite the fact that the
+ * declaration of this variable is unsigned long long. See determination
+ * of 'loff_t max' in fs/read_write.c / do_sendfile(). I don't know the
+ * correct limit but MAX_LFS_FILESIZE (8TB-1 on 32-bit boxes) takes the
+ * page cache into account and is the suggested limit. */
+# if defined MAX_LFS_FILESIZE
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+# else
+ sb->s_maxbytes = 0x7fffffffffffffffULL;
+# endif
+#endif
+ sb->s_op = &sf_super_ops;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
+ iroot = iget_locked(sb, 0);
+#else
+ iroot = iget(sb, 0);
+#endif
+ if (!iroot) {
+ err = -ENOMEM; /* XXX */
+ LogFunc(("could not get root inode\n"));
+ goto fail3;
+ }
+
+ if (sf_init_backing_dev(sf_g)) {
+ err = -EINVAL;
+ LogFunc(("could not init bdi\n"));
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
+ unlock_new_inode(iroot);
+#endif
+ goto fail4;
+ }
+
+ sf_init_inode(sf_g, iroot, &fsinfo);
+ SET_INODE_INFO(iroot, sf_i);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
+ unlock_new_inode(iroot);
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)
+ droot = d_make_root(iroot);
+#else
+ droot = d_alloc_root(iroot);
+#endif
+ if (!droot) {
+ err = -ENOMEM; /* XXX */
+ LogFunc(("d_alloc_root failed\n"));
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)
+ fInodePut = false;
+#endif
+ goto fail5;
+ }
+
+ sb->s_root = droot;
+ SET_GLOB_INFO(sb, sf_g);
+ return 0;
+
+ fail5:
+ sf_done_backing_dev(sf_g);
+
+ fail4:
+ if (fInodePut)
+ iput(iroot);
+
+ fail3:
+ kfree(sf_i->path);
+
+ fail2:
+ kfree(sf_i);
+
+ fail1:
+ sf_glob_free(sf_g);
+
+ fail0:
+ return err;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
+static struct super_block *sf_read_super_24(struct super_block *sb, void *data,
+ int flags)
+{
+ int err;
+
+ TRACE();
+ err = sf_read_super_aux(sb, data, flags);
+ if (err)
+ return NULL;
+
+ return sb;
+}
+#endif
+
+/* this is called when vfs is about to destroy the [inode]. all
+ resources associated with this [inode] must be cleared here */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
+static void sf_clear_inode(struct inode *inode)
+{
+ struct sf_inode_info *sf_i;
+
+ TRACE();
+ sf_i = GET_INODE_INFO(inode);
+ if (!sf_i)
+ return;
+
+ BUG_ON(!sf_i->path);
+ kfree(sf_i->path);
+ kfree(sf_i);
+ SET_INODE_INFO(inode, NULL);
+}
+#else /* LINUX_VERSION_CODE >= 2.6.36 */
+static void sf_evict_inode(struct inode *inode)
+{
+ struct sf_inode_info *sf_i;
+
+ TRACE();
+ truncate_inode_pages(&inode->i_data, 0);
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
+ clear_inode(inode);
+# else
+ end_writeback(inode);
+# endif
+
+ sf_i = GET_INODE_INFO(inode);
+ if (!sf_i)
+ return;
+
+ BUG_ON(!sf_i->path);
+ kfree(sf_i->path);
+ kfree(sf_i);
+ SET_INODE_INFO(inode, NULL);
+}
+#endif /* LINUX_VERSION_CODE >= 2.6.36 */
+
+/* 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 LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+static void sf_read_inode(struct inode *inode)
+{
+}
+#endif
+
+/* vfs is done with [sb] (umount called) call [sf_glob_free] to unmap
+ the folder and free [sf_g] */
+static void sf_put_super(struct super_block *sb)
+{
+ struct sf_glob_info *sf_g;
+
+ sf_g = GET_GLOB_INFO(sb);
+ BUG_ON(!sf_g);
+ sf_done_backing_dev(sf_g);
+ sf_glob_free(sf_g);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+static int sf_statfs(struct super_block *sb, STRUCT_STATFS * stat)
+{
+ return sf_get_volume_info(sb, stat);
+}
+#else
+static int sf_statfs(struct dentry *dentry, STRUCT_STATFS * stat)
+{
+ struct super_block *sb = dentry->d_inode->i_sb;
+ return sf_get_volume_info(sb, stat);
+}
+#endif
+
+static int sf_remount_fs(struct super_block *sb, int *flags, char *data)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 23)
+ struct sf_glob_info *sf_g;
+ struct sf_inode_info *sf_i;
+ struct inode *iroot;
+ SHFLFSOBJINFO fsinfo;
+ int err;
+
+ sf_g = GET_GLOB_INFO(sb);
+ BUG_ON(!sf_g);
+ if (data && data[0] != 0) {
+ struct vbsf_mount_info_new *info =
+ (struct vbsf_mount_info_new *)data;
+ if (info->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0
+ && info->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1
+ && info->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2) {
+ sf_g->uid = info->uid;
+ sf_g->gid = info->gid;
+ sf_g->ttl = info->ttl;
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, tag)) {
+ sf_g->dmode = info->dmode;
+ sf_g->fmode = info->fmode;
+ sf_g->dmask = info->dmask;
+ sf_g->fmask = info->fmask;
+ } else {
+ sf_g->dmode = ~0;
+ sf_g->fmode = ~0;
+ }
+ if ((unsigned)info->length >= sizeof(struct vbsf_mount_info_new)) {
+ AssertCompile(sizeof(sf_g->tag) >= sizeof(info->tag));
+ memcpy(sf_g->tag, info->tag, sizeof(info->tag));
+ sf_g->tag[sizeof(sf_g->tag) - 1] = '\0';
+ } else {
+ sf_g->tag[0] = '\0';
+ }
+ }
+ }
+
+ iroot = ilookup(sb, 0);
+ if (!iroot)
+ return -ENOSYS;
+
+ sf_i = GET_INODE_INFO(iroot);
+ err = sf_stat(__func__, sf_g, sf_i->path, &fsinfo, 0);
+ BUG_ON(err != 0);
+ sf_init_inode(sf_g, iroot, &fsinfo);
+ /*unlock_new_inode(iroot); */
+ return 0;
+#else /* LINUX_VERSION_CODE < 2.4.23 */
+ return -ENOSYS;
+#endif /* LINUX_VERSION_CODE < 2.4.23 */
+}
+
+/** Show mount options. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
+static int sf_show_options(struct seq_file *m, struct vfsmount *mnt)
+#else
+static int sf_show_options(struct seq_file *m, struct dentry *root)
+#endif
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
+ struct super_block *sb = mnt->mnt_sb;
+#else
+ struct super_block *sb = root->d_sb;
+#endif
+ struct sf_glob_info *sf_g = GET_GLOB_INFO(sb);
+ if (sf_g) {
+ seq_printf(m, ",uid=%u,gid=%u,ttl=%u,dmode=0%o,fmode=0%o,dmask=0%o,fmask=0%o",
+ sf_g->uid, sf_g->gid, sf_g->ttl, sf_g->dmode, sf_g->fmode, sf_g->dmask, sf_g->fmask);
+ if (sf_g->tag[0] != '\0') {
+ seq_puts(m, ",tag=");
+ seq_escape(m, sf_g->tag, " \t\n\\");
+ }
+ }
+
+ return 0;
+}
+
+static struct super_operations sf_super_ops = {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
+ .clear_inode = sf_clear_inode,
+#else
+ .evict_inode = sf_evict_inode,
+#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+ .read_inode = sf_read_inode,
+#endif
+ .put_super = sf_put_super,
+ .statfs = sf_statfs,
+ .remount_fs = sf_remount_fs,
+ .show_options = sf_show_options
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
+static DECLARE_FSTYPE(vboxsf_fs_type, "vboxsf", sf_read_super_24, 0);
+#else
+static int sf_read_super_26(struct super_block *sb, void *data, int flags)
+{
+ int err;
+
+ TRACE();
+ err = sf_read_super_aux(sb, data, flags);
+ if (err)
+ printk(KERN_DEBUG "sf_read_super_aux err=%d\n", err);
+
+ return err;
+}
+
+# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+static struct super_block *sf_get_sb(struct file_system_type *fs_type,
+ int flags, const char *dev_name,
+ void *data)
+{
+ TRACE();
+ return get_sb_nodev(fs_type, flags, data, sf_read_super_26);
+}
+# elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39)
+static int sf_get_sb(struct file_system_type *fs_type, int flags,
+ const char *dev_name, void *data, struct vfsmount *mnt)
+{
+ TRACE();
+ return get_sb_nodev(fs_type, flags, data, sf_read_super_26, mnt);
+}
+# else /* LINUX_VERSION_CODE >= 2.6.39 */
+static struct dentry *sf_mount(struct file_system_type *fs_type, int flags,
+ const char *dev_name, void *data)
+{
+ TRACE();
+ return mount_nodev(fs_type, flags, data, sf_read_super_26);
+}
+# endif /* LINUX_VERSION_CODE >= 2.6.39 */
+
+static struct file_system_type vboxsf_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "vboxsf",
+# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39)
+ .get_sb = sf_get_sb,
+# else
+ .mount = sf_mount,
+# endif
+ .kill_sb = kill_anon_super
+};
+
+#endif /* LINUX_VERSION_CODE >= 2.6.0 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+static int follow_symlinks = 0;
+module_param(follow_symlinks, int, 0);
+MODULE_PARM_DESC(follow_symlinks,
+ "Let host resolve symlinks rather than showing them");
+#endif
+
+/* Module initialization/finalization handlers */
+static int __init init(void)
+{
+ int rcVBox;
+ int rcRet = 0;
+ int err;
+
+ TRACE();
+
+ if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) {
+ printk(KERN_ERR
+ "Mount information structure is too large %lu\n"
+ "Must be less than or equal to %lu\n",
+ (unsigned long)sizeof(struct vbsf_mount_info_new),
+ (unsigned long)PAGE_SIZE);
+ return -EINVAL;
+ }
+
+ err = register_filesystem(&vboxsf_fs_type);
+ if (err) {
+ LogFunc(("register_filesystem err=%d\n", err));
+ return err;
+ }
+
+ rcVBox = VbglR0HGCMInit();
+ if (RT_FAILURE(rcVBox)) {
+ LogRelFunc(("VbglR0HGCMInit failed, rc=%d\n", rcVBox));
+ rcRet = -EPROTO;
+ goto fail0;
+ }
+
+ rcVBox = VbglR0SfConnect(&client_handle);
+ if (RT_FAILURE(rcVBox)) {
+ LogRelFunc(("VbglR0SfConnect failed, rc=%d\n", rcVBox));
+ rcRet = -EPROTO;
+ goto fail1;
+ }
+
+ rcVBox = VbglR0SfSetUtf8(&client_handle);
+ if (RT_FAILURE(rcVBox)) {
+ LogRelFunc(("VbglR0SfSetUtf8 failed, rc=%d\n", rcVBox));
+ rcRet = -EPROTO;
+ goto fail2;
+ }
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+ if (!follow_symlinks) {
+ rcVBox = VbglR0SfSetSymlinks(&client_handle);
+ if (RT_FAILURE(rcVBox)) {
+ printk(KERN_WARNING
+ "vboxsf: Host unable to show symlinks, rc=%d\n",
+ rcVBox);
+ }
+ }
+#endif
+
+ printk(KERN_DEBUG
+ "vboxsf: Successfully loaded version " VBOX_VERSION_STRING
+ " (interface " RT_XSTR(VMMDEV_VERSION) ")\n");
+
+ return 0;
+
+ fail2:
+ VbglR0SfDisconnect(&client_handle);
+
+ fail1:
+ VbglR0HGCMTerminate();
+
+ fail0:
+ unregister_filesystem(&vboxsf_fs_type);
+ return rcRet;
+}
+
+static void __exit fini(void)
+{
+ TRACE();
+
+ VbglR0SfDisconnect(&client_handle);
+ VbglR0HGCMTerminate();
+ unregister_filesystem(&vboxsf_fs_type);
+}
+
+module_init(init);
+module_exit(fini);
+
+/* C++ hack */
+int __gxx_personality_v0 = 0xdeadbeef;