diff options
Diffstat (limited to 'fs/sysfs/symlink.c')
| -rw-r--r-- | fs/sysfs/symlink.c | 276 | 
1 files changed, 83 insertions, 193 deletions
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index a7ac78f8e67..aecb15f8455 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -11,88 +11,73 @@   */  #include <linux/fs.h> -#include <linux/gfp.h> -#include <linux/mount.h>  #include <linux/module.h>  #include <linux/kobject.h> -#include <linux/namei.h>  #include <linux/mutex.h>  #include <linux/security.h>  #include "sysfs.h" -static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target, -				const char *name, int warn) +static int sysfs_do_create_link_sd(struct kernfs_node *parent, +				   struct kobject *target_kobj, +				   const char *name, int warn)  { -	struct sysfs_dirent *parent_sd = NULL; -	struct sysfs_dirent *target_sd = NULL; -	struct sysfs_dirent *sd = NULL; -	struct sysfs_addrm_cxt acxt; -	enum kobj_ns_type ns_type; -	int error; +	struct kernfs_node *kn, *target = NULL; -	BUG_ON(!name); +	BUG_ON(!name || !parent); -	if (!kobj) -		parent_sd = &sysfs_root; -	else -		parent_sd = kobj->sd; +	/* +	 * We don't own @target_kobj and it may be removed at any time. +	 * Synchronize using sysfs_symlink_target_lock.  See +	 * sysfs_remove_dir() for details. +	 */ +	spin_lock(&sysfs_symlink_target_lock); +	if (target_kobj->sd) { +		target = target_kobj->sd; +		kernfs_get(target); +	} +	spin_unlock(&sysfs_symlink_target_lock); -	error = -EFAULT; -	if (!parent_sd) -		goto out_put; +	if (!target) +		return -ENOENT; -	/* target->sd can go away beneath us but is protected with -	 * sysfs_assoc_lock.  Fetch target_sd from it. -	 */ -	spin_lock(&sysfs_assoc_lock); -	if (target->sd) -		target_sd = sysfs_get(target->sd); -	spin_unlock(&sysfs_assoc_lock); +	kn = kernfs_create_link(parent, name, target); +	kernfs_put(target); -	error = -ENOENT; -	if (!target_sd) -		goto out_put; +	if (!IS_ERR(kn)) +		return 0; -	error = -ENOMEM; -	sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK); -	if (!sd) -		goto out_put; +	if (warn && PTR_ERR(kn) == -EEXIST) +		sysfs_warn_dup(parent, name); +	return PTR_ERR(kn); +} -	ns_type = sysfs_ns_type(parent_sd); -	if (ns_type) -		sd->s_ns = target->ktype->namespace(target); -	sd->s_symlink.target_sd = target_sd; -	target_sd = NULL;	/* reference is now owned by the symlink */ +/** + *	sysfs_create_link_sd - create symlink to a given object. + *	@kn:		directory we're creating the link in. + *	@target:	object we're pointing to. + *	@name:		name of the symlink. + */ +int sysfs_create_link_sd(struct kernfs_node *kn, struct kobject *target, +			 const char *name) +{ +	return sysfs_do_create_link_sd(kn, target, name, 1); +} -	sysfs_addrm_start(&acxt, parent_sd); -	/* Symlinks must be between directories with the same ns_type */ -	if (!ns_type || -	    (ns_type == sysfs_ns_type(sd->s_symlink.target_sd->s_parent))) { -		if (warn) -			error = sysfs_add_one(&acxt, sd); -		else -			error = __sysfs_add_one(&acxt, sd); -	} else { -		error = -EINVAL; -		WARN(1, KERN_WARNING -			"sysfs: symlink across ns_types %s/%s -> %s/%s\n", -			parent_sd->s_name, -			sd->s_name, -			sd->s_symlink.target_sd->s_parent->s_name, -			sd->s_symlink.target_sd->s_name); -	} -	sysfs_addrm_finish(&acxt); +static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target, +				const char *name, int warn) +{ +	struct kernfs_node *parent = NULL; -	if (error) -		goto out_put; +	if (!kobj) +		parent = sysfs_root_kn; +	else +		parent = kobj->sd; -	return 0; +	if (!parent) +		return -EFAULT; - out_put: -	sysfs_put(target_sd); -	sysfs_put(sd); -	return error; +	return sysfs_do_create_link_sd(parent, target, name, warn);  }  /** @@ -106,6 +91,7 @@ int sysfs_create_link(struct kobject *kobj, struct kobject *target,  {  	return sysfs_do_create_link(kobj, target, name, 1);  } +EXPORT_SYMBOL_GPL(sysfs_create_link);  /**   *	sysfs_create_link_nowarn - create symlink between two objects. @@ -113,7 +99,7 @@ int sysfs_create_link(struct kobject *kobj, struct kobject *target,   *	@target:	object we're pointing to.   *	@name:		name of the symlink.   * - *	This function does the same as sysf_create_link(), but it + *	This function does the same as sysfs_create_link(), but it   *	doesn't warn if the link already exists.   */  int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, @@ -135,11 +121,17 @@ void sysfs_delete_link(struct kobject *kobj, struct kobject *targ,  			const char *name)  {  	const void *ns = NULL; -	spin_lock(&sysfs_assoc_lock); -	if (targ->sd && sysfs_ns_type(kobj->sd)) -		ns = targ->sd->s_ns; -	spin_unlock(&sysfs_assoc_lock); -	sysfs_hash_and_remove(kobj->sd, ns, name); + +	/* +	 * We don't own @target and it may be removed at any time. +	 * Synchronize using sysfs_symlink_target_lock.  See +	 * sysfs_remove_dir() for details. +	 */ +	spin_lock(&sysfs_symlink_target_lock); +	if (targ->sd && kernfs_ns_enabled(kobj->sd)) +		ns = targ->sd->ns; +	spin_unlock(&sysfs_symlink_target_lock); +	kernfs_remove_by_name_ns(kobj->sd, name, ns);  }  /** @@ -147,161 +139,59 @@ void sysfs_delete_link(struct kobject *kobj, struct kobject *targ,   *	@kobj:	object we're acting for.   *	@name:	name of the symlink to remove.   */ - -void sysfs_remove_link(struct kobject * kobj, const char * name) +void sysfs_remove_link(struct kobject *kobj, const char *name)  { -	struct sysfs_dirent *parent_sd = NULL; +	struct kernfs_node *parent = NULL;  	if (!kobj) -		parent_sd = &sysfs_root; +		parent = sysfs_root_kn;  	else -		parent_sd = kobj->sd; +		parent = kobj->sd; -	sysfs_hash_and_remove(parent_sd, NULL, name); +	kernfs_remove_by_name(parent, name);  } +EXPORT_SYMBOL_GPL(sysfs_remove_link);  /** - *	sysfs_rename_link - rename symlink in object's directory. + *	sysfs_rename_link_ns - rename symlink in object's directory.   *	@kobj:	object we're acting for.   *	@targ:	object we're pointing to.   *	@old:	previous name of the symlink.   *	@new:	new name of the symlink. + *	@new_ns: new namespace of the symlink.   *   *	A helper function for the common rename symlink idiom.   */ -int sysfs_rename_link(struct kobject *kobj, struct kobject *targ, -			const char *old, const char *new) +int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ, +			 const char *old, const char *new, const void *new_ns)  { -	struct sysfs_dirent *parent_sd, *sd = NULL; -	const void *old_ns = NULL, *new_ns = NULL; +	struct kernfs_node *parent, *kn = NULL; +	const void *old_ns = NULL;  	int result;  	if (!kobj) -		parent_sd = &sysfs_root; +		parent = sysfs_root_kn;  	else -		parent_sd = kobj->sd; +		parent = kobj->sd;  	if (targ->sd) -		old_ns = targ->sd->s_ns; +		old_ns = targ->sd->ns;  	result = -ENOENT; -	sd = sysfs_get_dirent(parent_sd, old_ns, old); -	if (!sd) +	kn = kernfs_find_and_get_ns(parent, old, old_ns); +	if (!kn)  		goto out;  	result = -EINVAL; -	if (sysfs_type(sd) != SYSFS_KOBJ_LINK) +	if (kernfs_type(kn) != KERNFS_LINK)  		goto out; -	if (sd->s_symlink.target_sd->s_dir.kobj != targ) +	if (kn->symlink.target_kn->priv != targ)  		goto out; -	if (sysfs_ns_type(parent_sd)) -		new_ns = targ->ktype->namespace(targ); - -	result = sysfs_rename(sd, parent_sd, new_ns, new); +	result = kernfs_rename_ns(kn, parent, new, new_ns);  out: -	sysfs_put(sd); +	kernfs_put(kn);  	return result;  } - -static int sysfs_get_target_path(struct sysfs_dirent *parent_sd, -				 struct sysfs_dirent *target_sd, char *path) -{ -	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; -	} - -	/* check limits */ -	if (len < 2) -		return -EINVAL; -	len--; -	if ((s - path) + len > PATH_MAX) -		return -ENAMETOOLONG; - -	/* reverse fillup of target string from target to base */ -	sd = target_sd; -	while (sd->s_parent && sd != base) { -		int slen = strlen(sd->s_name); - -		len -= slen; -		strncpy(s + len, sd->s_name, slen); -		if (len) -			s[--len] = '/'; - -		sd = sd->s_parent; -	} - -	return 0; -} - -static int sysfs_getlink(struct dentry *dentry, char * path) -{ -	struct sysfs_dirent *sd = dentry->d_fsdata; -	struct sysfs_dirent *parent_sd = sd->s_parent; -	struct sysfs_dirent *target_sd = sd->s_symlink.target_sd; -	int error; - -	mutex_lock(&sysfs_mutex); -	error = sysfs_get_target_path(parent_sd, target_sd, path); -	mutex_unlock(&sysfs_mutex); - -	return error; -} - -static void *sysfs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ -	int error = -ENOMEM; -	unsigned long page = get_zeroed_page(GFP_KERNEL); -	if (page) { -		error = sysfs_getlink(dentry, (char *) page);  -		if (error < 0) -			free_page((unsigned long)page); -	} -	nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); -	return NULL; -} - -static void sysfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) -{ -	char *page = nd_get_link(nd); -	if (!IS_ERR(page)) -		free_page((unsigned long)page); -} - -const struct inode_operations sysfs_symlink_inode_operations = { -	.setxattr	= sysfs_setxattr, -	.readlink	= generic_readlink, -	.follow_link	= sysfs_follow_link, -	.put_link	= sysfs_put_link, -	.setattr	= sysfs_setattr, -	.getattr	= sysfs_getattr, -	.permission	= sysfs_permission, -}; - - -EXPORT_SYMBOL_GPL(sysfs_create_link); -EXPORT_SYMBOL_GPL(sysfs_remove_link); -EXPORT_SYMBOL_GPL(sysfs_rename_link); +EXPORT_SYMBOL_GPL(sysfs_rename_link_ns);  | 
