diff options
author | Tejun Heo <tj@kernel.org> | 2013-11-28 14:54:35 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-11-29 18:08:39 -0800 |
commit | 2072f1afddfe9fa00c1c0c79f8986707324ec65b (patch) | |
tree | 636f5f2e0c44082afcded77328bcbcdb62b58f51 /fs/kernfs | |
parent | 414985ae23c031efbd6d16d484dea8b5de28b8f7 (diff) | |
download | lwn-2072f1afddfe9fa00c1c0c79f8986707324ec65b.tar.gz lwn-2072f1afddfe9fa00c1c0c79f8986707324ec65b.zip |
sysfs, kernfs: move symlink core code to fs/kernfs/symlink.c
Move core symlink code to fs/kernfs/symlink.c. fs/sysfs/symlink.c now
only contains sysfs wrappers around kernfs interfaces. The respective
declarations in fs/sysfs/sysfs.h are moved to
fs/kernfs/kernfs-internal.h.
This is pure relocation.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/kernfs')
-rw-r--r-- | fs/kernfs/kernfs-internal.h | 5 | ||||
-rw-r--r-- | fs/kernfs/symlink.c | 139 |
2 files changed, 144 insertions, 0 deletions
diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 38e3a163e5ad..62ae35f997f7 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -149,4 +149,9 @@ extern const struct file_operations kernfs_file_operations; void sysfs_unmap_bin_file(struct sysfs_dirent *sd); +/* + * symlink.c + */ +extern const struct inode_operations sysfs_symlink_inode_operations; + #endif /* __KERNFS_INTERNAL_H */ diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 2578715baf0e..af3570bb4303 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -7,3 +7,142 @@ * * This file is released under the GPLv2. */ + +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/namei.h> + +#include "kernfs-internal.h" + +/** + * kernfs_create_link - create a symlink + * @parent: directory to create the symlink in + * @name: name of the symlink + * @target: target node for the symlink to point to + * + * Returns the created node on success, ERR_PTR() value on error. + */ +struct sysfs_dirent *kernfs_create_link(struct sysfs_dirent *parent, + const char *name, + struct sysfs_dirent *target) +{ + struct sysfs_dirent *sd; + struct sysfs_addrm_cxt acxt; + int error; + + sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK); + if (!sd) + return ERR_PTR(-ENOMEM); + + if (parent->s_flags & SYSFS_FLAG_NS) + sd->s_ns = target->s_ns; + sd->s_symlink.target_sd = target; + kernfs_get(target); /* ref owned by symlink */ + + sysfs_addrm_start(&acxt); + error = sysfs_add_one(&acxt, sd, parent); + sysfs_addrm_finish(&acxt); + + if (!error) + return sd; + + kernfs_put(sd); + return ERR_PTR(error); +} + +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, +}; |