diff options
Diffstat (limited to 'fs/sysfs/symlink.c')
| -rw-r--r-- | fs/sysfs/symlink.c | 269 |
1 files changed, 143 insertions, 126 deletions
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index de402fa915f..aecb15f8455 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -1,76 +1,83 @@ /* - * symlink.c - operations for sysfs symlinks. + * 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. */ #include <linux/fs.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 object_depth(struct kobject * kobj) +static int sysfs_do_create_link_sd(struct kernfs_node *parent, + struct kobject *target_kobj, + const char *name, int warn) { - struct kobject * p = kobj; - int depth = 0; - do { depth++; } while ((p = p->parent)); - return depth; -} + struct kernfs_node *kn, *target = NULL; + + BUG_ON(!name || !parent); + + /* + * 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); -static int object_path_length(struct kobject * kobj) -{ - struct kobject * p = kobj; - int length = 1; - do { - length += strlen(kobject_name(p)) + 1; - p = p->parent; - } while (p); - return length; -} + if (!target) + return -ENOENT; -static void fill_object_path(struct kobject * kobj, char * buffer, int length) -{ - struct kobject * p; + kn = kernfs_create_link(parent, name, target); + kernfs_put(target); - --length; - for (p = kobj; p; p = p->parent) { - int cur = strlen(kobject_name(p)); + if (!IS_ERR(kn)) + return 0; - /* back up enough to print this bus id with '/' */ - length -= cur; - strncpy(buffer + length,kobject_name(p),cur); - *(buffer + --length) = '/'; - } + if (warn && PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(parent, name); + return PTR_ERR(kn); } -static int sysfs_add_link(struct dentry * parent, const char * name, struct kobject * target) +/** + * 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) { - struct sysfs_dirent * parent_sd = parent->d_fsdata; - struct sysfs_symlink * sl; - int error = 0; - - error = -ENOMEM; - sl = kmalloc(sizeof(*sl), GFP_KERNEL); - if (!sl) - goto exit1; + return sysfs_do_create_link_sd(kn, target, name, 1); +} - sl->link_name = kmalloc(strlen(name) + 1, GFP_KERNEL); - if (!sl->link_name) - goto exit2; +static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target, + const char *name, int warn) +{ + struct kernfs_node *parent = NULL; - strcpy(sl->link_name, name); - sl->target_kobj = kobject_get(target); + if (!kobj) + parent = sysfs_root_kn; + else + parent = kobj->sd; - error = sysfs_make_dirent(parent_sd, NULL, sl, S_IFLNK|S_IRWXUGO, - SYSFS_KOBJ_LINK); - if (!error) - return 0; + if (!parent) + return -EFAULT; - kfree(sl->link_name); -exit2: - kfree(sl); -exit1: - return error; + return sysfs_do_create_link_sd(parent, target, name, warn); } /** @@ -79,102 +86,112 @@ exit1: * @target: object we're pointing to. * @name: name of the symlink. */ -int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name) +int sysfs_create_link(struct kobject *kobj, struct kobject *target, + const char *name) { - struct dentry * dentry = kobj->dentry; - int error = 0; - - BUG_ON(!kobj || !kobj->dentry || !name); - - down(&dentry->d_inode->i_sem); - error = sysfs_add_link(dentry, name, target); - up(&dentry->d_inode->i_sem); - return error; + return sysfs_do_create_link(kobj, target, name, 1); } +EXPORT_SYMBOL_GPL(sysfs_create_link); +/** + * sysfs_create_link_nowarn - 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. + * + * 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, + const char *name) +{ + return sysfs_do_create_link(kobj, target, name, 0); +} /** - * sysfs_remove_link - remove symlink in object's directory. + * sysfs_delete_link - remove symlink in object's directory. * @kobj: object we're acting for. + * @targ: object we're pointing to. * @name: name of the symlink to remove. + * + * Unlike sysfs_remove_link sysfs_delete_link has enough information + * to successfully delete symlinks in tagged directories. */ - -void sysfs_remove_link(struct kobject * kobj, const char * name) +void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, + const char *name) { - sysfs_hash_and_remove(kobj->dentry,name); + const void *ns = NULL; + + /* + * 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); } -static int sysfs_get_target_path(struct kobject * kobj, struct kobject * target, - char *path) +/** + * sysfs_remove_link - remove symlink in object's directory. + * @kobj: object we're acting for. + * @name: name of the symlink to remove. + */ +void sysfs_remove_link(struct kobject *kobj, const char *name) { - char * s; - int depth, size; + struct kernfs_node *parent = NULL; - depth = object_depth(kobj); - size = object_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_object_path(target, path, size); - pr_debug("%s: path = '%s'\n", __FUNCTION__, path); + if (!kobj) + parent = sysfs_root_kn; + else + parent = kobj->sd; - return 0; + kernfs_remove_by_name(parent, name); } +EXPORT_SYMBOL_GPL(sysfs_remove_link); -static int sysfs_getlink(struct dentry *dentry, char * path) +/** + * 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_ns(struct kobject *kobj, struct kobject *targ, + const char *old, const char *new, const void *new_ns) { - struct kobject *kobj, *target_kobj; - int error = 0; + struct kernfs_node *parent, *kn = NULL; + const void *old_ns = NULL; + int result; - kobj = sysfs_get_kobject(dentry->d_parent); if (!kobj) - return -EINVAL; + parent = sysfs_root_kn; + else + parent = kobj->sd; - target_kobj = sysfs_get_kobject(dentry); - if (!target_kobj) { - kobject_put(kobj); - return -EINVAL; - } + if (targ->sd) + old_ns = targ->sd->ns; - down_read(&sysfs_rename_sem); - error = sysfs_get_target_path(kobj, target_kobj, path); - up_read(&sysfs_rename_sem); - - kobject_put(kobj); - kobject_put(target_kobj); - return error; + result = -ENOENT; + kn = kernfs_find_and_get_ns(parent, old, old_ns); + if (!kn) + goto out; -} + result = -EINVAL; + if (kernfs_type(kn) != KERNFS_LINK) + goto out; + if (kn->symlink.target_kn->priv != targ) + goto out; -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); - nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); - return NULL; -} + result = kernfs_rename_ns(kn, parent, new, new_ns); -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); +out: + kernfs_put(kn); + return result; } - -struct inode_operations sysfs_symlink_inode_operations = { - .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); - +EXPORT_SYMBOL_GPL(sysfs_rename_link_ns); |
