| // SPDX-License-Identifier: MIT |
| /* |
| * VirtualBox Guest Shared Folders support: Virtual File System. |
| * |
| * Module initialization/finalization |
| * File system registration/deregistration |
| * Superblock reading |
| * Few utility functions |
| * |
| * Copyright (C) 2006-2018 Oracle Corporation |
| */ |
| |
| #include <linux/idr.h> |
| #include <linux/fs_parser.h> |
| #include <linux/magic.h> |
| #include <linux/module.h> |
| #include <linux/nls.h> |
| #include <linux/statfs.h> |
| #include <linux/vbox_utils.h> |
| #include "vfsmod.h" |
| |
| #define VBOXSF_SUPER_MAGIC 0x786f4256 /* 'VBox' little endian */ |
| |
| static const unsigned char VBSF_MOUNT_SIGNATURE[4] = "\000\377\376\375"; |
| |
| static int follow_symlinks; |
| module_param(follow_symlinks, int, 0444); |
| MODULE_PARM_DESC(follow_symlinks, |
| "Let host resolve symlinks rather than showing them"); |
| |
| static DEFINE_IDA(vboxsf_bdi_ida); |
| static DEFINE_MUTEX(vboxsf_setup_mutex); |
| static bool vboxsf_setup_done; |
| static struct super_operations vboxsf_super_ops; /* forward declaration */ |
| static struct kmem_cache *vboxsf_inode_cachep; |
| |
| static char * const vboxsf_default_nls = CONFIG_NLS_DEFAULT; |
| |
| enum { opt_nls, opt_uid, opt_gid, opt_ttl, opt_dmode, opt_fmode, |
| opt_dmask, opt_fmask }; |
| |
| static const struct fs_parameter_spec vboxsf_fs_parameters[] = { |
| 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), |
| {} |
| }; |
| |
| static int vboxsf_parse_param(struct fs_context *fc, struct fs_parameter *param) |
| { |
| struct vboxsf_fs_context *ctx = fc->fs_private; |
| struct fs_parse_result result; |
| kuid_t uid; |
| kgid_t gid; |
| int opt; |
| |
| opt = fs_parse(fc, vboxsf_fs_parameters, param, &result); |
| if (opt < 0) |
| return opt; |
| |
| switch (opt) { |
| case opt_nls: |
| if (ctx->nls_name || fc->purpose != FS_CONTEXT_FOR_MOUNT) { |
| vbg_err("vboxsf: Cannot reconfigure nls option\n"); |
| return -EINVAL; |
| } |
| ctx->nls_name = param->string; |
| param->string = NULL; |
| break; |
| case opt_uid: |
| uid = make_kuid(current_user_ns(), result.uint_32); |
| if (!uid_valid(uid)) |
| return -EINVAL; |
| ctx->o.uid = uid; |
| break; |
| case opt_gid: |
| gid = make_kgid(current_user_ns(), result.uint_32); |
| if (!gid_valid(gid)) |
| return -EINVAL; |
| ctx->o.gid = gid; |
| break; |
| case opt_ttl: |
| ctx->o.ttl = msecs_to_jiffies(result.uint_32); |
| break; |
| case opt_dmode: |
| if (result.uint_32 & ~0777) |
| return -EINVAL; |
| ctx->o.dmode = result.uint_32; |
| ctx->o.dmode_set = true; |
| break; |
| case opt_fmode: |
| if (result.uint_32 & ~0777) |
| return -EINVAL; |
| ctx->o.fmode = result.uint_32; |
| ctx->o.fmode_set = true; |
| break; |
| case opt_dmask: |
| if (result.uint_32 & ~07777) |
| return -EINVAL; |
| ctx->o.dmask = result.uint_32; |
| break; |
| case opt_fmask: |
| if (result.uint_32 & ~07777) |
| return -EINVAL; |
| ctx->o.fmask = result.uint_32; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int vboxsf_fill_super(struct super_block *sb, struct fs_context *fc) |
| { |
| struct vboxsf_fs_context *ctx = fc->fs_private; |
| struct shfl_string *folder_name, root_path; |
| struct vboxsf_sbi *sbi; |
| struct dentry *droot; |
| struct inode *iroot; |
| char *nls_name; |
| size_t size; |
| int err; |
| |
| if (!fc->source) |
| return -EINVAL; |
| |
| sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); |
| if (!sbi) |
| return -ENOMEM; |
| |
| sbi->o = ctx->o; |
| idr_init(&sbi->ino_idr); |
| spin_lock_init(&sbi->ino_idr_lock); |
| sbi->next_generation = 1; |
| sbi->bdi_id = -1; |
| |
| /* Load nls if not utf8 */ |
| nls_name = ctx->nls_name ? ctx->nls_name : vboxsf_default_nls; |
| if (strcmp(nls_name, "utf8") != 0) { |
| if (nls_name == vboxsf_default_nls) |
| sbi->nls = load_nls_default(); |
| else |
| sbi->nls = load_nls(nls_name); |
| |
| if (!sbi->nls) { |
| vbg_err("vboxsf: Count not load '%s' nls\n", nls_name); |
| err = -EINVAL; |
| goto fail_free; |
| } |
| } |
| |
| sbi->bdi_id = ida_simple_get(&vboxsf_bdi_ida, 0, 0, GFP_KERNEL); |
| if (sbi->bdi_id < 0) { |
| err = sbi->bdi_id; |
| goto fail_free; |
| } |
| |
| err = super_setup_bdi_name(sb, "vboxsf-%d", sbi->bdi_id); |
| if (err) |
| goto fail_free; |
| sb->s_bdi->ra_pages = 0; |
| sb->s_bdi->io_pages = 0; |
| |
| /* Turn source into a shfl_string and map the folder */ |
| size = strlen(fc->source) + 1; |
| folder_name = kmalloc(SHFLSTRING_HEADER_SIZE + size, GFP_KERNEL); |
| if (!folder_name) { |
| err = -ENOMEM; |
| goto fail_free; |
| } |
| folder_name->size = size; |
| folder_name->length = size - 1; |
| strscpy(folder_name->string.utf8, fc->source, size); |
| err = vboxsf_map_folder(folder_name, &sbi->root); |
| kfree(folder_name); |
| if (err) { |
| vbg_err("vboxsf: Host rejected mount of '%s' with error %d\n", |
| fc->source, err); |
| goto fail_free; |
| } |
| |
| root_path.length = 1; |
| root_path.size = 2; |
| root_path.string.utf8[0] = '/'; |
| root_path.string.utf8[1] = 0; |
| err = vboxsf_stat(sbi, &root_path, &sbi->root_info); |
| if (err) |
| goto fail_unmap; |
| |
| sb->s_magic = VBOXSF_SUPER_MAGIC; |
| sb->s_blocksize = 1024; |
| sb->s_maxbytes = MAX_LFS_FILESIZE; |
| sb->s_op = &vboxsf_super_ops; |
| sb->s_d_op = &vboxsf_dentry_ops; |
| |
| iroot = iget_locked(sb, 0); |
| if (!iroot) { |
| err = -ENOMEM; |
| goto fail_unmap; |
| } |
| vboxsf_init_inode(sbi, iroot, &sbi->root_info, false); |
| unlock_new_inode(iroot); |
| |
| droot = d_make_root(iroot); |
| if (!droot) { |
| err = -ENOMEM; |
| goto fail_unmap; |
| } |
| |
| sb->s_root = droot; |
| sb->s_fs_info = sbi; |
| return 0; |
| |
| fail_unmap: |
| vboxsf_unmap_folder(sbi->root); |
| fail_free: |
| if (sbi->bdi_id >= 0) |
| ida_simple_remove(&vboxsf_bdi_ida, sbi->bdi_id); |
| if (sbi->nls) |
| unload_nls(sbi->nls); |
| idr_destroy(&sbi->ino_idr); |
| kfree(sbi); |
| return err; |
| } |
| |
| static void vboxsf_inode_init_once(void *data) |
| { |
| struct vboxsf_inode *sf_i = data; |
| |
| mutex_init(&sf_i->handle_list_mutex); |
| inode_init_once(&sf_i->vfs_inode); |
| } |
| |
| static struct inode *vboxsf_alloc_inode(struct super_block *sb) |
| { |
| struct vboxsf_inode *sf_i; |
| |
| sf_i = alloc_inode_sb(sb, vboxsf_inode_cachep, GFP_NOFS); |
| if (!sf_i) |
| return NULL; |
| |
| sf_i->force_restat = 0; |
| INIT_LIST_HEAD(&sf_i->handle_list); |
| |
| return &sf_i->vfs_inode; |
| } |
| |
| static void vboxsf_free_inode(struct inode *inode) |
| { |
| struct vboxsf_sbi *sbi = VBOXSF_SBI(inode->i_sb); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&sbi->ino_idr_lock, flags); |
| idr_remove(&sbi->ino_idr, inode->i_ino); |
| spin_unlock_irqrestore(&sbi->ino_idr_lock, flags); |
| kmem_cache_free(vboxsf_inode_cachep, VBOXSF_I(inode)); |
| } |
| |
| static void vboxsf_put_super(struct super_block *sb) |
| { |
| struct vboxsf_sbi *sbi = VBOXSF_SBI(sb); |
| |
| vboxsf_unmap_folder(sbi->root); |
| if (sbi->bdi_id >= 0) |
| ida_simple_remove(&vboxsf_bdi_ida, sbi->bdi_id); |
| if (sbi->nls) |
| unload_nls(sbi->nls); |
| |
| /* |
| * vboxsf_free_inode uses the idr, make sure all delayed rcu free |
| * inodes are flushed. |
| */ |
| rcu_barrier(); |
| idr_destroy(&sbi->ino_idr); |
| kfree(sbi); |
| } |
| |
| static int vboxsf_statfs(struct dentry *dentry, struct kstatfs *stat) |
| { |
| struct super_block *sb = dentry->d_sb; |
| struct shfl_volinfo shfl_volinfo; |
| struct vboxsf_sbi *sbi; |
| u32 buf_len; |
| int err; |
| |
| sbi = VBOXSF_SBI(sb); |
| buf_len = sizeof(shfl_volinfo); |
| err = vboxsf_fsinfo(sbi->root, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME, |
| &buf_len, &shfl_volinfo); |
| if (err) |
| return err; |
| |
| stat->f_type = VBOXSF_SUPER_MAGIC; |
| stat->f_bsize = shfl_volinfo.bytes_per_allocation_unit; |
| |
| do_div(shfl_volinfo.total_allocation_bytes, |
| shfl_volinfo.bytes_per_allocation_unit); |
| stat->f_blocks = shfl_volinfo.total_allocation_bytes; |
| |
| do_div(shfl_volinfo.available_allocation_bytes, |
| shfl_volinfo.bytes_per_allocation_unit); |
| stat->f_bfree = shfl_volinfo.available_allocation_bytes; |
| stat->f_bavail = shfl_volinfo.available_allocation_bytes; |
| |
| stat->f_files = 1000; |
| /* |
| * Don't return 0 here since the guest may then think that it is not |
| * possible to create any more files. |
| */ |
| stat->f_ffree = 1000000; |
| stat->f_fsid.val[0] = 0; |
| stat->f_fsid.val[1] = 0; |
| stat->f_namelen = 255; |
| return 0; |
| } |
| |
| static struct super_operations vboxsf_super_ops = { |
| .alloc_inode = vboxsf_alloc_inode, |
| .free_inode = vboxsf_free_inode, |
| .put_super = vboxsf_put_super, |
| .statfs = vboxsf_statfs, |
| }; |
| |
| static int vboxsf_setup(void) |
| { |
| int err; |
| |
| mutex_lock(&vboxsf_setup_mutex); |
| |
| if (vboxsf_setup_done) |
| goto success; |
| |
| vboxsf_inode_cachep = |
| kmem_cache_create("vboxsf_inode_cache", |
| sizeof(struct vboxsf_inode), 0, |
| (SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD | |
| SLAB_ACCOUNT), |
| vboxsf_inode_init_once); |
| if (!vboxsf_inode_cachep) { |
| err = -ENOMEM; |
| goto fail_nomem; |
| } |
| |
| err = vboxsf_connect(); |
| if (err) { |
| vbg_err("vboxsf: err %d connecting to guest PCI-device\n", err); |
| vbg_err("vboxsf: make sure you are inside a VirtualBox VM\n"); |
| vbg_err("vboxsf: and check dmesg for vboxguest errors\n"); |
| goto fail_free_cache; |
| } |
| |
| err = vboxsf_set_utf8(); |
| if (err) { |
| vbg_err("vboxsf_setutf8 error %d\n", err); |
| goto fail_disconnect; |
| } |
| |
| if (!follow_symlinks) { |
| err = vboxsf_set_symlinks(); |
| if (err) |
| vbg_warn("vboxsf: Unable to show symlinks: %d\n", err); |
| } |
| |
| vboxsf_setup_done = true; |
| success: |
| mutex_unlock(&vboxsf_setup_mutex); |
| return 0; |
| |
| fail_disconnect: |
| vboxsf_disconnect(); |
| fail_free_cache: |
| kmem_cache_destroy(vboxsf_inode_cachep); |
| fail_nomem: |
| mutex_unlock(&vboxsf_setup_mutex); |
| return err; |
| } |
| |
| static int vboxsf_parse_monolithic(struct fs_context *fc, void *data) |
| { |
| if (data && !memcmp(data, VBSF_MOUNT_SIGNATURE, 4)) { |
| vbg_err("vboxsf: Old binary mount data not supported, remove obsolete mount.vboxsf and/or update your VBoxService.\n"); |
| return -EINVAL; |
| } |
| |
| return generic_parse_monolithic(fc, data); |
| } |
| |
| static int vboxsf_get_tree(struct fs_context *fc) |
| { |
| int err; |
| |
| err = vboxsf_setup(); |
| if (err) |
| return err; |
| |
| return get_tree_nodev(fc, vboxsf_fill_super); |
| } |
| |
| static int vboxsf_reconfigure(struct fs_context *fc) |
| { |
| struct vboxsf_sbi *sbi = VBOXSF_SBI(fc->root->d_sb); |
| struct vboxsf_fs_context *ctx = fc->fs_private; |
| struct inode *iroot = fc->root->d_sb->s_root->d_inode; |
| |
| /* Apply changed options to the root inode */ |
| sbi->o = ctx->o; |
| vboxsf_init_inode(sbi, iroot, &sbi->root_info, true); |
| |
| return 0; |
| } |
| |
| static void vboxsf_free_fc(struct fs_context *fc) |
| { |
| struct vboxsf_fs_context *ctx = fc->fs_private; |
| |
| kfree(ctx->nls_name); |
| kfree(ctx); |
| } |
| |
| static const struct fs_context_operations vboxsf_context_ops = { |
| .free = vboxsf_free_fc, |
| .parse_param = vboxsf_parse_param, |
| .parse_monolithic = vboxsf_parse_monolithic, |
| .get_tree = vboxsf_get_tree, |
| .reconfigure = vboxsf_reconfigure, |
| }; |
| |
| static int vboxsf_init_fs_context(struct fs_context *fc) |
| { |
| struct vboxsf_fs_context *ctx; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| current_uid_gid(&ctx->o.uid, &ctx->o.gid); |
| |
| fc->fs_private = ctx; |
| fc->ops = &vboxsf_context_ops; |
| return 0; |
| } |
| |
| static struct file_system_type vboxsf_fs_type = { |
| .owner = THIS_MODULE, |
| .name = "vboxsf", |
| .init_fs_context = vboxsf_init_fs_context, |
| .kill_sb = kill_anon_super |
| }; |
| |
| /* Module initialization/finalization handlers */ |
| static int __init vboxsf_init(void) |
| { |
| return register_filesystem(&vboxsf_fs_type); |
| } |
| |
| static void __exit vboxsf_fini(void) |
| { |
| unregister_filesystem(&vboxsf_fs_type); |
| |
| mutex_lock(&vboxsf_setup_mutex); |
| if (vboxsf_setup_done) { |
| vboxsf_disconnect(); |
| /* |
| * Make sure all delayed rcu free inodes are flushed |
| * before we destroy the cache. |
| */ |
| rcu_barrier(); |
| kmem_cache_destroy(vboxsf_inode_cachep); |
| } |
| mutex_unlock(&vboxsf_setup_mutex); |
| } |
| |
| module_init(vboxsf_init); |
| module_exit(vboxsf_fini); |
| |
| MODULE_DESCRIPTION("Oracle VM VirtualBox Module for Host File System Access"); |
| MODULE_AUTHOR("Oracle Corporation"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS_FS("vboxsf"); |