diff options
Diffstat (limited to 'fs/sysfs/symlink.c')
| -rw-r--r-- | fs/sysfs/symlink.c | 269 |
1 files changed, 121 insertions, 148 deletions
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index c5eff49fa41..aecb15f8455 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -11,71 +11,73 @@ */ #include <linux/fs.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; - 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); - - error = -ENOENT; - if (!target_sd) - goto out_put; - - error = -ENOMEM; - sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK); - if (!sd) - goto out_put; - - sd->s_symlink.target_sd = target_sd; - target_sd = NULL; /* reference is now owned by the symlink */ - - sysfs_addrm_start(&acxt, parent_sd); - if (warn) - error = sysfs_add_one(&acxt, sd); - else - error = __sysfs_add_one(&acxt, sd); - sysfs_addrm_finish(&acxt); + kn = kernfs_create_link(parent, name, target); + kernfs_put(target); + + if (!IS_ERR(kn)) + return 0; + + if (warn && PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(parent, name); + return PTR_ERR(kn); +} + +/** + * 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); +} + +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); } /** @@ -89,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. @@ -96,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, @@ -106,119 +109,89 @@ int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, } /** - * 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) { - struct sysfs_dirent *parent_sd = NULL; + const void *ns = NULL; - if (!kobj) - parent_sd = &sysfs_root; - else - parent_sd = kobj->sd; - - sysfs_hash_and_remove(parent_sd, 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); } -static int sysfs_get_target_path(struct sysfs_dirent *parent_sd, - struct sysfs_dirent *target_sd, 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) { - 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; - } + struct kernfs_node *parent = NULL; - /* 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; - } + 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 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; + struct kernfs_node *parent, *kn = NULL; + const void *old_ns = NULL; + int result; - mutex_lock(&sysfs_mutex); - error = sysfs_get_target_path(parent_sd, target_sd, path); - mutex_unlock(&sysfs_mutex); + if (!kobj) + parent = sysfs_root_kn; + else + parent = kobj->sd; - return error; -} + if (targ->sd) + old_ns = targ->sd->ns; -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); -} + result = -ENOENT; + kn = kernfs_find_and_get_ns(parent, old, old_ns); + if (!kn) + goto out; -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, -}; + result = -EINVAL; + if (kernfs_type(kn) != KERNFS_LINK) + goto out; + if (kn->symlink.target_kn->priv != targ) + goto out; + result = kernfs_rename_ns(kn, parent, new, new_ns); -EXPORT_SYMBOL_GPL(sysfs_create_link); -EXPORT_SYMBOL_GPL(sysfs_remove_link); +out: + kernfs_put(kn); + return result; +} +EXPORT_SYMBOL_GPL(sysfs_rename_link_ns); |
