2005-12-16 01:29:43 +03:00
/* -*- mode: c; c-basic-offset: 8; -*-
* vim : noexpandtab sw = 8 ts = 8 sts = 0 :
*
* symlink . c - operations for configfs symlinks .
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public
* License along with this program ; if not , write to the
* Free Software Foundation , Inc . , 59 Temple Place - Suite 330 ,
* Boston , MA 021110 - 1307 , USA .
*
* Based on sysfs :
* sysfs is Copyright ( C ) 2001 , 2002 , 2003 Patrick Mochel
*
* configfs Copyright ( C ) 2005 Oracle . All rights reserved .
*/
# include <linux/fs.h>
# include <linux/module.h>
# include <linux/namei.h>
# include <linux/configfs.h>
# include "configfs_internal.h"
static int item_depth ( struct config_item * item )
{
struct config_item * p = item ;
int depth = 0 ;
do { depth + + ; } while ( ( p = p - > ci_parent ) & & ! configfs_is_root ( p ) ) ;
return depth ;
}
static int item_path_length ( struct config_item * item )
{
struct config_item * p = item ;
int length = 1 ;
do {
length + = strlen ( config_item_name ( p ) ) + 1 ;
p = p - > ci_parent ;
} while ( p & & ! configfs_is_root ( p ) ) ;
return length ;
}
static void fill_item_path ( struct config_item * item , char * buffer , int length )
{
struct config_item * p ;
- - length ;
for ( p = item ; p & & ! configfs_is_root ( p ) ; p = p - > ci_parent ) {
int cur = strlen ( config_item_name ( p ) ) ;
/* back up enough to print this bus id with '/' */
length - = cur ;
strncpy ( buffer + length , config_item_name ( p ) , cur ) ;
* ( buffer + - - length ) = ' / ' ;
}
}
static int create_link ( struct config_item * parent_item ,
2006-03-10 22:42:30 +03:00
struct config_item * item ,
2005-12-16 01:29:43 +03:00
struct dentry * dentry )
{
struct configfs_dirent * target_sd = item - > ci_dentry - > d_fsdata ;
struct configfs_symlink * sl ;
int ret ;
ret = - ENOMEM ;
sl = kmalloc ( sizeof ( struct configfs_symlink ) , GFP_KERNEL ) ;
if ( sl ) {
sl - > sl_target = config_item_get ( item ) ;
/* FIXME: needs a lock, I'd bet */
list_add ( & sl - > sl_list , & target_sd - > s_links ) ;
ret = configfs_create_link ( sl , parent_item - > ci_dentry ,
dentry ) ;
if ( ret ) {
list_del_init ( & sl - > sl_list ) ;
config_item_put ( item ) ;
kfree ( sl ) ;
}
}
return ret ;
}
static int get_target ( const char * symname , struct nameidata * nd ,
struct config_item * * target )
{
int ret ;
ret = path_lookup ( symname , LOOKUP_FOLLOW | LOOKUP_DIRECTORY , nd ) ;
if ( ! ret ) {
2008-02-15 06:34:32 +03:00
if ( nd - > path . dentry - > d_sb = = configfs_sb ) {
* target = configfs_get_config_item ( nd - > path . dentry ) ;
2005-12-16 01:29:43 +03:00
if ( ! * target ) {
ret = - ENOENT ;
2008-02-15 06:34:35 +03:00
path_put ( & nd - > path ) ;
2005-12-16 01:29:43 +03:00
}
} else
ret = - EPERM ;
}
return ret ;
}
int configfs_symlink ( struct inode * dir , struct dentry * dentry , const char * symname )
{
int ret ;
struct nameidata nd ;
struct config_item * parent_item ;
struct config_item * target_item ;
struct config_item_type * type ;
ret = - EPERM ; /* What lack-of-symlink returns */
if ( dentry - > d_parent = = configfs_sb - > s_root )
goto out ;
parent_item = configfs_get_config_item ( dentry - > d_parent ) ;
type = parent_item - > ci_type ;
if ( ! type | | ! type - > ct_item_ops | |
! type - > ct_item_ops - > allow_link )
goto out_put ;
ret = get_target ( symname , & nd , & target_item ) ;
if ( ret )
goto out_put ;
ret = type - > ct_item_ops - > allow_link ( parent_item , target_item ) ;
if ( ! ret )
ret = create_link ( parent_item , target_item , dentry ) ;
config_item_put ( target_item ) ;
2008-02-15 06:34:35 +03:00
path_put ( & nd . path ) ;
2005-12-16 01:29:43 +03:00
out_put :
config_item_put ( parent_item ) ;
out :
return ret ;
}
int configfs_unlink ( struct inode * dir , struct dentry * dentry )
{
struct configfs_dirent * sd = dentry - > d_fsdata ;
struct configfs_symlink * sl ;
struct config_item * parent_item ;
struct config_item_type * type ;
int ret ;
ret = - EPERM ; /* What lack-of-symlink returns */
if ( ! ( sd - > s_type & CONFIGFS_ITEM_LINK ) )
goto out ;
2006-01-27 12:32:24 +03:00
BUG_ON ( dentry - > d_parent = = configfs_sb - > s_root ) ;
2005-12-16 01:29:43 +03:00
sl = sd - > s_element ;
parent_item = configfs_get_config_item ( dentry - > d_parent ) ;
type = parent_item - > ci_type ;
list_del_init ( & sd - > s_sibling ) ;
configfs_drop_dentry ( sd , dentry - > d_parent ) ;
dput ( dentry ) ;
configfs_put ( sd ) ;
/*
* drop_link ( ) must be called before
* list_del_init ( & sl - > sl_list ) , so that the order of
* drop_link ( this , target ) and drop_item ( target ) is preserved .
*/
if ( type & & type - > ct_item_ops & &
type - > ct_item_ops - > drop_link )
type - > ct_item_ops - > drop_link ( parent_item ,
sl - > sl_target ) ;
/* FIXME: Needs lock */
list_del_init ( & sl - > sl_list ) ;
/* Put reference from create_link() */
config_item_put ( sl - > sl_target ) ;
kfree ( sl ) ;
config_item_put ( parent_item ) ;
ret = 0 ;
out :
return ret ;
}
static int configfs_get_target_path ( struct config_item * item , struct config_item * target ,
char * path )
{
char * s ;
int depth , size ;
depth = item_depth ( item ) ;
size = item_path_length ( target ) + depth * 3 - 1 ;
if ( size > PATH_MAX )
return - ENAMETOOLONG ;
pr_debug ( " %s: depth = %d, size = %d \n " , __FUNCTION__ , depth , size ) ;
for ( s = path ; depth - - ; s + = 3 )
strcpy ( s , " ../ " ) ;
fill_item_path ( target , path , size ) ;
pr_debug ( " %s: path = '%s' \n " , __FUNCTION__ , path ) ;
return 0 ;
}
static int configfs_getlink ( struct dentry * dentry , char * path )
{
struct config_item * item , * target_item ;
int error = 0 ;
item = configfs_get_config_item ( dentry - > d_parent ) ;
if ( ! item )
return - EINVAL ;
target_item = configfs_get_config_item ( dentry ) ;
if ( ! target_item ) {
config_item_put ( item ) ;
return - EINVAL ;
}
down_read ( & configfs_rename_sem ) ;
error = configfs_get_target_path ( item , target_item , path ) ;
up_read ( & configfs_rename_sem ) ;
config_item_put ( item ) ;
config_item_put ( target_item ) ;
return error ;
}
static void * configfs_follow_link ( struct dentry * dentry , struct nameidata * nd )
{
int error = - ENOMEM ;
unsigned long page = get_zeroed_page ( GFP_KERNEL ) ;
if ( page ) {
error = configfs_getlink ( dentry , ( char * ) page ) ;
if ( ! error ) {
nd_set_link ( nd , ( char * ) page ) ;
return ( void * ) page ;
}
}
nd_set_link ( nd , ERR_PTR ( error ) ) ;
return NULL ;
}
static void configfs_put_link ( struct dentry * dentry , struct nameidata * nd ,
void * cookie )
{
if ( cookie ) {
unsigned long page = ( unsigned long ) cookie ;
free_page ( page ) ;
}
}
2007-02-12 11:55:38 +03:00
const struct inode_operations configfs_symlink_inode_operations = {
2005-12-16 01:29:43 +03:00
. follow_link = configfs_follow_link ,
. readlink = generic_readlink ,
. put_link = configfs_put_link ,
2006-01-26 00:31:07 +03:00
. setattr = configfs_setattr ,
2005-12-16 01:29:43 +03:00
} ;