2019-12-12 17:09:14 +03:00
// 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 */
# define VBSF_MOUNT_SIGNATURE_BYTE_0 ('\000')
# define VBSF_MOUNT_SIGNATURE_BYTE_1 ('\377')
# define VBSF_MOUNT_SIGNATURE_BYTE_2 ('\376')
# define VBSF_MOUNT_SIGNATURE_BYTE_3 ('\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 ;
}
2020-05-04 15:47:53 +03:00
err = super_setup_bdi_name ( sb , " vboxsf-%d " , sbi - > bdi_id ) ;
2019-12-12 17:09:14 +03:00
if ( err )
goto fail_free ;
2020-09-24 09:51:32 +03:00
sb - > s_bdi - > ra_pages = 0 ;
sb - > s_bdi - > io_pages = 0 ;
2019-12-12 17:09:14 +03:00
/* 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 ;
strlcpy ( 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 ) ;
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 = kmem_cache_alloc ( 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 )
{
2020-08-25 14:12:57 +03:00
unsigned char * options = data ;
2019-12-12 17:09:14 +03:00
if ( options & & options [ 0 ] = = VBSF_MOUNT_SIGNATURE_BYTE_0 & &
options [ 1 ] = = VBSF_MOUNT_SIGNATURE_BYTE_1 & &
options [ 2 ] = = VBSF_MOUNT_SIGNATURE_BYTE_2 & &
options [ 3 ] = = VBSF_MOUNT_SIGNATURE_BYTE_3 ) {
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 ) ;
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 " ) ;