2005-04-17 02:20:36 +04:00
/*
2007-09-20 12:31:38 +04:00
* fs / sysfs / symlink . c - sysfs symlink implementation
*
* Copyright ( c ) 2001 - 3 Patrick Mochel
* Copyright ( c ) 2007 SUSE Linux Products GmbH
* Copyright ( c ) 2007 Tejun Heo < teheo @ suse . de >
*
* This file is released under the GPLv2 .
*
* Please see Documentation / filesystems / sysfs . txt for more information .
2005-04-17 02:20:36 +04:00
*/
# include <linux/fs.h>
2002-04-09 23:14:34 +04:00
# include <linux/mount.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/kobject.h>
# include <linux/namei.h>
2007-07-26 18:53:53 +04:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
# include "sysfs.h"
/**
* sysfs_create_link - create symlink between two objects .
* @ kobj : object whose directory we ' re creating the link in .
* @ target : object we ' re pointing to .
* @ name : name of the symlink .
*/
2005-04-26 11:31:08 +04:00
int sysfs_create_link ( struct kobject * kobj , struct kobject * target , const char * name )
2005-04-17 02:20:36 +04:00
{
2007-06-13 22:45:15 +04:00
struct sysfs_dirent * parent_sd = NULL ;
struct sysfs_dirent * target_sd = NULL ;
2007-06-13 23:27:23 +04:00
struct sysfs_dirent * sd = NULL ;
2007-06-13 23:27:24 +04:00
struct sysfs_addrm_cxt acxt ;
2007-06-13 23:27:23 +04:00
int error ;
2005-04-17 02:20:36 +04:00
2002-04-09 23:14:34 +04:00
BUG_ON ( ! name ) ;
2007-08-20 16:36:30 +04:00
if ( ! kobj )
parent_sd = & sysfs_root ;
else
2007-06-13 23:27:22 +04:00
parent_sd = kobj - > sd ;
2002-04-09 23:14:34 +04:00
2007-06-13 23:27:23 +04:00
error = - EFAULT ;
2007-06-13 23:27:22 +04:00
if ( ! parent_sd )
2007-06-13 23:27:23 +04:00
goto out_put ;
2007-06-13 22:45:15 +04:00
2007-06-13 23:27:22 +04:00
/* target->sd can go away beneath us but is protected with
2007-06-13 23:27:23 +04:00
* sysfs_assoc_lock . Fetch target_sd from it .
2007-06-13 22:45:15 +04:00
*/
2007-06-13 23:27:23 +04:00
spin_lock ( & sysfs_assoc_lock ) ;
2007-06-13 23:27:22 +04:00
if ( target - > sd )
target_sd = sysfs_get ( target - > sd ) ;
2007-06-13 23:27:23 +04:00
spin_unlock ( & sysfs_assoc_lock ) ;
2007-06-13 22:45:15 +04:00
2007-06-13 23:27:23 +04:00
error = - ENOENT ;
2007-06-13 22:45:15 +04:00
if ( ! target_sd )
2007-06-13 23:27:23 +04:00
goto out_put ;
error = - ENOMEM ;
sd = sysfs_new_dirent ( name , S_IFLNK | S_IRWXUGO , SYSFS_KOBJ_LINK ) ;
if ( ! sd )
goto out_put ;
2007-07-18 11:14:45 +04:00
2007-09-20 11:05:11 +04:00
sd - > s_symlink . target_sd = target_sd ;
2007-07-18 11:14:45 +04:00
target_sd = NULL ; /* reference is now owned by the symlink */
2005-04-17 02:20:36 +04:00
2007-06-13 23:27:24 +04:00
sysfs_addrm_start ( & acxt , parent_sd ) ;
2007-08-02 16:38:03 +04:00
error = sysfs_add_one ( & acxt , sd ) ;
sysfs_addrm_finish ( & acxt ) ;
2007-06-13 22:45:15 +04:00
2007-08-02 16:38:03 +04:00
if ( error )
2007-07-18 11:38:11 +04:00
goto out_put ;
return 0 ;
2007-06-13 23:27:24 +04:00
2007-06-13 23:27:23 +04:00
out_put :
sysfs_put ( target_sd ) ;
sysfs_put ( sd ) ;
2005-04-17 02:20:36 +04:00
return error ;
}
/**
* sysfs_remove_link - remove symlink in object ' s directory .
* @ kobj : object we ' re acting for .
* @ name : name of the symlink to remove .
*/
2005-04-26 11:31:08 +04:00
void sysfs_remove_link ( struct kobject * kobj , const char * name )
2005-04-17 02:20:36 +04:00
{
2008-01-30 01:35:18 +03:00
struct sysfs_dirent * parent_sd = NULL ;
if ( ! kobj )
parent_sd = & sysfs_root ;
else
parent_sd = kobj - > sd ;
sysfs_hash_and_remove ( parent_sd , name ) ;
2005-04-17 02:20:36 +04:00
}
2007-11-01 22:20:52 +03:00
static int sysfs_get_target_path ( struct sysfs_dirent * parent_sd ,
struct sysfs_dirent * target_sd , char * path )
2005-04-17 02:20:36 +04:00
{
2007-11-01 22:20:52 +03:00
struct sysfs_dirent * base , * sd ;
char * s = path ;
int len = 0 ;
/* go up to the root, stop at the base */
base = parent_sd ;
while ( base - > s_parent ) {
sd = target_sd - > s_parent ;
while ( sd - > s_parent & & base ! = sd )
sd = sd - > s_parent ;
if ( base = = sd )
break ;
strcpy ( s , " ../ " ) ;
s + = 3 ;
base = base - > s_parent ;
}
/* determine end of target string for reverse fillup */
sd = target_sd ;
while ( sd - > s_parent & & sd ! = base ) {
len + = strlen ( sd - > s_name ) + 1 ;
sd = sd - > s_parent ;
}
2005-04-17 02:20:36 +04:00
2007-11-01 22:20:52 +03:00
/* check limits */
if ( len < 2 )
return - EINVAL ;
len - - ;
if ( ( s - path ) + len > PATH_MAX )
2005-04-17 02:20:36 +04:00
return - ENAMETOOLONG ;
2007-11-01 22:20:52 +03:00
/* reverse fillup of target string from target to base */
sd = target_sd ;
while ( sd - > s_parent & & sd ! = base ) {
int slen = strlen ( sd - > s_name ) ;
2005-04-17 02:20:36 +04:00
2007-11-01 22:20:52 +03:00
len - = slen ;
strncpy ( s + len , sd - > s_name , slen ) ;
if ( len )
s [ - - len ] = ' / ' ;
2005-04-17 02:20:36 +04:00
2007-11-01 22:20:52 +03:00
sd = sd - > s_parent ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int sysfs_getlink ( struct dentry * dentry , char * path )
{
2007-06-13 22:45:15 +04:00
struct sysfs_dirent * sd = dentry - > d_fsdata ;
struct sysfs_dirent * parent_sd = sd - > s_parent ;
2007-09-20 11:05:11 +04:00
struct sysfs_dirent * target_sd = sd - > s_symlink . target_sd ;
2007-06-13 22:45:15 +04:00
int error ;
2005-04-17 02:20:36 +04:00
2007-06-13 23:27:23 +04:00
mutex_lock ( & sysfs_mutex ) ;
2007-06-13 22:45:15 +04:00
error = sysfs_get_target_path ( parent_sd , target_sd , path ) ;
2007-06-13 23:27:23 +04:00
mutex_unlock ( & sysfs_mutex ) ;
2005-04-17 02:20:36 +04:00
2007-06-13 22:45:15 +04:00
return error ;
2005-04-17 02:20:36 +04:00
}
2005-08-20 05:02:56 +04:00
static void * sysfs_follow_link ( struct dentry * dentry , struct nameidata * nd )
2005-04-17 02:20:36 +04:00
{
int error = - ENOMEM ;
unsigned long page = get_zeroed_page ( GFP_KERNEL ) ;
if ( page )
error = sysfs_getlink ( dentry , ( char * ) page ) ;
nd_set_link ( nd , error ? ERR_PTR ( error ) : ( char * ) page ) ;
2005-08-20 05:02:56 +04:00
return NULL ;
2005-04-17 02:20:36 +04:00
}
2005-08-20 05:02:56 +04:00
static void sysfs_put_link ( struct dentry * dentry , struct nameidata * nd , void * cookie )
2005-04-17 02:20:36 +04:00
{
char * page = nd_get_link ( nd ) ;
if ( ! IS_ERR ( page ) )
free_page ( ( unsigned long ) page ) ;
}
2007-02-12 11:55:40 +03:00
const struct inode_operations sysfs_symlink_inode_operations = {
2005-04-17 02:20:36 +04:00
. readlink = generic_readlink ,
. follow_link = sysfs_follow_link ,
. put_link = sysfs_put_link ,
} ;
EXPORT_SYMBOL_GPL ( sysfs_create_link ) ;
EXPORT_SYMBOL_GPL ( sysfs_remove_link ) ;