2008-01-25 06:28:31 +03:00
/*
* Contains the CIFS DFS referral mounting routines used for handling
* traversal via DFS junction point
*
* Copyright ( c ) 2007 Igor Mammedov
2008-01-25 13:12:41 +03:00
* Copyright ( C ) International Business Machines Corp . , 2008
2008-01-25 06:28:31 +03:00
* Author ( s ) : Igor Mammedov ( niallain @ gmail . com )
2008-01-25 13:12:41 +03:00
* Steve French ( sfrench @ us . ibm . com )
2008-01-25 06:28:31 +03:00
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/dcache.h>
# include <linux/mount.h>
# include <linux/namei.h>
# include <linux/vfs.h>
# include <linux/fs.h>
# include "cifsglob.h"
# include "cifsproto.h"
# include "cifsfs.h"
# include "dns_resolve.h"
# include "cifs_debug.h"
LIST_HEAD ( cifs_dfs_automount_list ) ;
/*
* DFS functions
*/
void dfs_shrink_umount_helper ( struct vfsmount * vfsmnt )
{
mark_mounts_for_expiry ( & cifs_dfs_automount_list ) ;
mark_mounts_for_expiry ( & cifs_dfs_automount_list ) ;
shrink_submounts ( vfsmnt , & cifs_dfs_automount_list ) ;
}
/**
* cifs_get_share_name - extracts share name from UNC
* @ node_name : pointer to UNC string
*
* Extracts sharename form full UNC .
* i . e . strips from UNC trailing path that is not part of share
* name and fixup missing ' \ ' in the begining of DFS node refferal
* if neccessary .
* Returns pointer to share name on success or NULL on error .
* Caller is responsible for freeing returned string .
*/
static char * cifs_get_share_name ( const char * node_name )
{
int len ;
char * UNC ;
char * pSep ;
len = strlen ( node_name ) ;
UNC = kmalloc ( len + 2 /*for term null and additional \ if it's missed */ ,
GFP_KERNEL ) ;
if ( ! UNC )
return NULL ;
/* get share name and server name */
if ( node_name [ 1 ] ! = ' \\ ' ) {
UNC [ 0 ] = ' \\ ' ;
strncpy ( UNC + 1 , node_name , len ) ;
len + + ;
UNC [ len ] = 0 ;
} else {
strncpy ( UNC , node_name , len ) ;
UNC [ len ] = 0 ;
}
/* find server name end */
pSep = memchr ( UNC + 2 , ' \\ ' , len - 2 ) ;
if ( ! pSep ) {
cERROR ( 1 , ( " %s: no server name end in node name: %s " ,
__FUNCTION__ , node_name ) ) ;
kfree ( UNC ) ;
return NULL ;
}
/* find sharename end */
pSep + + ;
pSep = memchr ( UNC + ( pSep - UNC ) , ' \\ ' , len - ( pSep - UNC ) ) ;
if ( ! pSep ) {
cERROR ( 1 , ( " %s:2 cant find share name in node name: %s " ,
__FUNCTION__ , node_name ) ) ;
kfree ( UNC ) ;
return NULL ;
}
/* trim path up to sharename end
* * now we have share name in UNC */
* pSep = 0 ;
return UNC ;
}
/**
* compose_mount_options - creates mount options for refferral
* @ sb_mountdata : parent / root DFS mount options ( template )
* @ ref_unc : refferral server UNC
* @ devname : pointer for saving device name
*
* creates mount options for submount based on template options sb_mountdata
* and replacing unc , ip , prefixpath options with ones we ' ve got form ref_unc .
*
* Returns : pointer to new mount options or ERR_PTR .
* Caller is responcible for freeing retunrned value if it is not error .
*/
2008-01-25 13:12:41 +03:00
static char * compose_mount_options ( const char * sb_mountdata ,
const char * ref_unc ,
char * * devname )
2008-01-25 06:28:31 +03:00
{
int rc ;
char * mountdata ;
int md_len ;
char * tkn_e ;
char * srvIP = NULL ;
char sep = ' , ' ;
int off , noff ;
if ( sb_mountdata = = NULL )
return ERR_PTR ( - EINVAL ) ;
* devname = cifs_get_share_name ( ref_unc ) ;
rc = dns_resolve_server_name_to_ip ( * devname , & srvIP ) ;
if ( rc ! = 0 ) {
cERROR ( 1 , ( " %s: Failed to resolve server part of %s to IP " ,
__FUNCTION__ , * devname ) ) ;
mountdata = ERR_PTR ( rc ) ;
goto compose_mount_options_out ;
}
md_len = strlen ( sb_mountdata ) + strlen ( srvIP ) + strlen ( ref_unc ) + 3 ;
mountdata = kzalloc ( md_len + 1 , GFP_KERNEL ) ;
if ( mountdata = = NULL ) {
mountdata = ERR_PTR ( - ENOMEM ) ;
goto compose_mount_options_out ;
}
/* copy all options except of unc,ip,prefixpath */
off = 0 ;
if ( strncmp ( sb_mountdata , " sep= " , 4 ) = = 0 ) {
sep = sb_mountdata [ 4 ] ;
strncpy ( mountdata , sb_mountdata , 5 ) ;
off + = 5 ;
}
while ( ( tkn_e = strchr ( sb_mountdata + off , sep ) ) ) {
noff = ( tkn_e - ( sb_mountdata + off ) ) + 1 ;
if ( strnicmp ( sb_mountdata + off , " unc= " , 4 ) = = 0 ) {
off + = noff ;
continue ;
}
if ( strnicmp ( sb_mountdata + off , " ip= " , 3 ) = = 0 ) {
off + = noff ;
continue ;
}
if ( strnicmp ( sb_mountdata + off , " prefixpath= " , 3 ) = = 0 ) {
off + = noff ;
continue ;
}
strncat ( mountdata , sb_mountdata + off , noff ) ;
off + = noff ;
}
strcat ( mountdata , sb_mountdata + off ) ;
mountdata [ md_len ] = ' \0 ' ;
/* copy new IP and ref share name */
strcat ( mountdata , " ,ip= " ) ;
strcat ( mountdata , srvIP ) ;
strcat ( mountdata , " ,unc= " ) ;
strcat ( mountdata , * devname ) ;
/* find & copy prefixpath */
tkn_e = strchr ( ref_unc + 2 , ' \\ ' ) ;
if ( tkn_e ) {
tkn_e = strchr ( tkn_e + 1 , ' \\ ' ) ;
if ( tkn_e ) {
strcat ( mountdata , " ,prefixpath= " ) ;
strcat ( mountdata , tkn_e ) ;
}
}
/*cFYI(1,("%s: parent mountdata: %s", __FUNCTION__,sb_mountdata));*/
/*cFYI(1, ("%s: submount mountdata: %s", __FUNCTION__, mountdata ));*/
compose_mount_options_out :
kfree ( srvIP ) ;
return mountdata ;
}
2008-01-25 13:12:41 +03:00
static struct vfsmount * cifs_dfs_do_refmount ( const struct vfsmount * mnt_parent ,
2008-01-25 06:28:31 +03:00
struct dentry * dentry , char * ref_unc )
{
struct cifs_sb_info * cifs_sb ;
struct vfsmount * mnt ;
char * mountdata ;
2008-01-25 13:12:41 +03:00
char * devname = NULL ;
2008-01-25 06:28:31 +03:00
cifs_sb = CIFS_SB ( dentry - > d_inode - > i_sb ) ;
mountdata = compose_mount_options ( cifs_sb - > mountdata ,
ref_unc , & devname ) ;
if ( IS_ERR ( mountdata ) )
return ( struct vfsmount * ) mountdata ;
mnt = vfs_kern_mount ( & cifs_fs_type , 0 , devname , mountdata ) ;
kfree ( mountdata ) ;
kfree ( devname ) ;
return mnt ;
}
static char * build_full_dfs_path_from_dentry ( struct dentry * dentry )
{
char * full_path = NULL ;
char * search_path ;
char * tmp_path ;
size_t l_max_len ;
struct cifs_sb_info * cifs_sb ;
if ( dentry - > d_inode = = NULL )
return NULL ;
cifs_sb = CIFS_SB ( dentry - > d_inode - > i_sb ) ;
if ( cifs_sb - > tcon = = NULL )
return NULL ;
search_path = build_path_from_dentry ( dentry ) ;
if ( search_path = = NULL )
return NULL ;
if ( cifs_sb - > tcon - > Flags & SMB_SHARE_IS_IN_DFS ) {
/* we should use full path name to correct working with DFS */
l_max_len = strnlen ( cifs_sb - > tcon - > treeName , MAX_TREE_SIZE + 1 ) +
strnlen ( search_path , MAX_PATHCONF ) + 1 ;
tmp_path = kmalloc ( l_max_len , GFP_KERNEL ) ;
if ( tmp_path = = NULL ) {
kfree ( search_path ) ;
return NULL ;
}
strncpy ( tmp_path , cifs_sb - > tcon - > treeName , l_max_len ) ;
strcat ( tmp_path , search_path ) ;
tmp_path [ l_max_len - 1 ] = 0 ;
full_path = tmp_path ;
kfree ( search_path ) ;
} else {
full_path = search_path ;
}
return full_path ;
}
static int add_mount_helper ( struct vfsmount * newmnt , struct nameidata * nd ,
struct list_head * mntlist )
{
/* stolen from afs code */
int err ;
mntget ( newmnt ) ;
2008-02-15 06:34:32 +03:00
err = do_add_mount ( newmnt , nd , nd - > path . mnt - > mnt_flags , mntlist ) ;
2008-01-25 06:28:31 +03:00
switch ( err ) {
case 0 :
2008-02-15 06:34:32 +03:00
dput ( nd - > path . dentry ) ;
mntput ( nd - > path . mnt ) ;
nd - > path . mnt = newmnt ;
nd - > path . dentry = dget ( newmnt - > mnt_root ) ;
2008-01-25 06:28:31 +03:00
break ;
case - EBUSY :
/* someone else made a mount here whilst we were busy */
2008-02-15 06:34:32 +03:00
while ( d_mountpoint ( nd - > path . dentry ) & &
follow_down ( & nd - > path . mnt , & nd - > path . dentry ) )
2008-01-25 06:28:31 +03:00
;
err = 0 ;
default :
mntput ( newmnt ) ;
break ;
}
return err ;
}
2008-01-25 13:12:41 +03:00
static void dump_referral ( const struct dfs_info3_param * ref )
2008-01-25 06:28:31 +03:00
{
cFYI ( 1 , ( " DFS: ref path: %s " , ref - > path_name ) ) ;
cFYI ( 1 , ( " DFS: node path: %s " , ref - > node_name ) ) ;
cFYI ( 1 , ( " DFS: fl: %hd, srv_type: %hd " , ref - > flags , ref - > server_type ) ) ;
cFYI ( 1 , ( " DFS: ref_flags: %hd, path_consumed: %hd " , ref - > ref_flag ,
2008-02-15 21:21:49 +03:00
ref - > path_consumed ) ) ;
2008-01-25 06:28:31 +03:00
}
static void *
cifs_dfs_follow_mountpoint ( struct dentry * dentry , struct nameidata * nd )
{
struct dfs_info3_param * referrals = NULL ;
unsigned int num_referrals = 0 ;
struct cifs_sb_info * cifs_sb ;
struct cifsSesInfo * ses ;
char * full_path = NULL ;
int xid , i ;
int rc = 0 ;
struct vfsmount * mnt = ERR_PTR ( - ENOENT ) ;
cFYI ( 1 , ( " in %s " , __FUNCTION__ ) ) ;
BUG_ON ( IS_ROOT ( dentry ) ) ;
xid = GetXid ( ) ;
2008-02-15 06:34:32 +03:00
dput ( nd - > path . dentry ) ;
nd - > path . dentry = dget ( dentry ) ;
2008-01-25 06:28:31 +03:00
cifs_sb = CIFS_SB ( dentry - > d_inode - > i_sb ) ;
ses = cifs_sb - > tcon - > ses ;
if ( ! ses ) {
rc = - EINVAL ;
goto out_err ;
}
full_path = build_full_dfs_path_from_dentry ( dentry ) ;
if ( full_path = = NULL ) {
rc = - ENOMEM ;
goto out_err ;
}
rc = get_dfs_path ( xid , ses , full_path , cifs_sb - > local_nls ,
& num_referrals , & referrals ,
cifs_sb - > mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR ) ;
for ( i = 0 ; i < num_referrals ; i + + ) {
dump_referral ( referrals + i ) ;
/* connect to a storage node */
if ( referrals [ i ] . flags & DFSREF_STORAGE_SERVER ) {
int len ;
len = strlen ( referrals [ i ] . node_name ) ;
if ( len < 2 ) {
cERROR ( 1 , ( " %s: Net Address path too short: %s " ,
__FUNCTION__ , referrals [ i ] . node_name ) ) ;
rc = - EINVAL ;
goto out_err ;
}
2008-02-15 06:34:32 +03:00
mnt = cifs_dfs_do_refmount ( nd - > path . mnt ,
nd - > path . dentry ,
2008-01-25 06:28:31 +03:00
referrals [ i ] . node_name ) ;
cFYI ( 1 , ( " %s: cifs_dfs_do_refmount:%s , mnt:%p " ,
__FUNCTION__ ,
referrals [ i ] . node_name , mnt ) ) ;
/* complete mount procedure if we accured submount */
if ( ! IS_ERR ( mnt ) )
break ;
}
}
/* we need it cause for() above could exit without valid submount */
rc = PTR_ERR ( mnt ) ;
if ( IS_ERR ( mnt ) )
goto out_err ;
2008-02-15 06:34:32 +03:00
nd - > path . mnt - > mnt_flags | = MNT_SHRINKABLE ;
2008-01-25 06:28:31 +03:00
rc = add_mount_helper ( mnt , nd , & cifs_dfs_automount_list ) ;
out :
FreeXid ( xid ) ;
free_dfs_info_array ( referrals , num_referrals ) ;
kfree ( full_path ) ;
cFYI ( 1 , ( " leaving %s " , __FUNCTION__ ) ) ;
return ERR_PTR ( rc ) ;
out_err :
2008-02-15 06:34:35 +03:00
path_put ( & nd - > path ) ;
2008-01-25 06:28:31 +03:00
goto out ;
}
struct inode_operations cifs_dfs_referral_inode_operations = {
. follow_link = cifs_dfs_follow_mountpoint ,
} ;