diff options
Diffstat (limited to 'fs/kernfs/symlink.c')
-rw-r--r-- | fs/kernfs/symlink.c | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c new file mode 100644 index 000000000000..4d457055acb9 --- /dev/null +++ b/fs/kernfs/symlink.c | |||
@@ -0,0 +1,151 @@ | |||
1 | /* | ||
2 | * fs/kernfs/symlink.c - kernfs symlink implementation | ||
3 | * | ||
4 | * Copyright (c) 2001-3 Patrick Mochel | ||
5 | * Copyright (c) 2007 SUSE Linux Products GmbH | ||
6 | * Copyright (c) 2007, 2013 Tejun Heo <tj@kernel.org> | ||
7 | * | ||
8 | * This file is released under the GPLv2. | ||
9 | */ | ||
10 | |||
11 | #include <linux/fs.h> | ||
12 | #include <linux/gfp.h> | ||
13 | #include <linux/namei.h> | ||
14 | |||
15 | #include "kernfs-internal.h" | ||
16 | |||
17 | /** | ||
18 | * kernfs_create_link - create a symlink | ||
19 | * @parent: directory to create the symlink in | ||
20 | * @name: name of the symlink | ||
21 | * @target: target node for the symlink to point to | ||
22 | * | ||
23 | * Returns the created node on success, ERR_PTR() value on error. | ||
24 | */ | ||
25 | struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, | ||
26 | const char *name, | ||
27 | struct kernfs_node *target) | ||
28 | { | ||
29 | struct kernfs_node *kn; | ||
30 | struct kernfs_addrm_cxt acxt; | ||
31 | int error; | ||
32 | |||
33 | kn = kernfs_new_node(parent, name, S_IFLNK|S_IRWXUGO, KERNFS_LINK); | ||
34 | if (!kn) | ||
35 | return ERR_PTR(-ENOMEM); | ||
36 | |||
37 | if (kernfs_ns_enabled(parent)) | ||
38 | kn->ns = target->ns; | ||
39 | kn->symlink.target_kn = target; | ||
40 | kernfs_get(target); /* ref owned by symlink */ | ||
41 | |||
42 | kernfs_addrm_start(&acxt); | ||
43 | error = kernfs_add_one(&acxt, kn); | ||
44 | kernfs_addrm_finish(&acxt); | ||
45 | |||
46 | if (!error) | ||
47 | return kn; | ||
48 | |||
49 | kernfs_put(kn); | ||
50 | return ERR_PTR(error); | ||
51 | } | ||
52 | |||
53 | static int kernfs_get_target_path(struct kernfs_node *parent, | ||
54 | struct kernfs_node *target, char *path) | ||
55 | { | ||
56 | struct kernfs_node *base, *kn; | ||
57 | char *s = path; | ||
58 | int len = 0; | ||
59 | |||
60 | /* go up to the root, stop at the base */ | ||
61 | base = parent; | ||
62 | while (base->parent) { | ||
63 | kn = target->parent; | ||
64 | while (kn->parent && base != kn) | ||
65 | kn = kn->parent; | ||
66 | |||
67 | if (base == kn) | ||
68 | break; | ||
69 | |||
70 | strcpy(s, "../"); | ||
71 | s += 3; | ||
72 | base = base->parent; | ||
73 | } | ||
74 | |||
75 | /* determine end of target string for reverse fillup */ | ||
76 | kn = target; | ||
77 | while (kn->parent && kn != base) { | ||
78 | len += strlen(kn->name) + 1; | ||
79 | kn = kn->parent; | ||
80 | } | ||
81 | |||
82 | /* check limits */ | ||
83 | if (len < 2) | ||
84 | return -EINVAL; | ||
85 | len--; | ||
86 | if ((s - path) + len > PATH_MAX) | ||
87 | return -ENAMETOOLONG; | ||
88 | |||
89 | /* reverse fillup of target string from target to base */ | ||
90 | kn = target; | ||
91 | while (kn->parent && kn != base) { | ||
92 | int slen = strlen(kn->name); | ||
93 | |||
94 | len -= slen; | ||
95 | strncpy(s + len, kn->name, slen); | ||
96 | if (len) | ||
97 | s[--len] = '/'; | ||
98 | |||
99 | kn = kn->parent; | ||
100 | } | ||
101 | |||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static int kernfs_getlink(struct dentry *dentry, char *path) | ||
106 | { | ||
107 | struct kernfs_node *kn = dentry->d_fsdata; | ||
108 | struct kernfs_node *parent = kn->parent; | ||
109 | struct kernfs_node *target = kn->symlink.target_kn; | ||
110 | int error; | ||
111 | |||
112 | mutex_lock(&kernfs_mutex); | ||
113 | error = kernfs_get_target_path(parent, target, path); | ||
114 | mutex_unlock(&kernfs_mutex); | ||
115 | |||
116 | return error; | ||
117 | } | ||
118 | |||
119 | static void *kernfs_iop_follow_link(struct dentry *dentry, struct nameidata *nd) | ||
120 | { | ||
121 | int error = -ENOMEM; | ||
122 | unsigned long page = get_zeroed_page(GFP_KERNEL); | ||
123 | if (page) { | ||
124 | error = kernfs_getlink(dentry, (char *) page); | ||
125 | if (error < 0) | ||
126 | free_page((unsigned long)page); | ||
127 | } | ||
128 | nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); | ||
129 | return NULL; | ||
130 | } | ||
131 | |||
132 | static void kernfs_iop_put_link(struct dentry *dentry, struct nameidata *nd, | ||
133 | void *cookie) | ||
134 | { | ||
135 | char *page = nd_get_link(nd); | ||
136 | if (!IS_ERR(page)) | ||
137 | free_page((unsigned long)page); | ||
138 | } | ||
139 | |||
140 | const struct inode_operations kernfs_symlink_iops = { | ||
141 | .setxattr = kernfs_iop_setxattr, | ||
142 | .removexattr = kernfs_iop_removexattr, | ||
143 | .getxattr = kernfs_iop_getxattr, | ||
144 | .listxattr = kernfs_iop_listxattr, | ||
145 | .readlink = generic_readlink, | ||
146 | .follow_link = kernfs_iop_follow_link, | ||
147 | .put_link = kernfs_iop_put_link, | ||
148 | .setattr = kernfs_iop_setattr, | ||
149 | .getattr = kernfs_iop_getattr, | ||
150 | .permission = kernfs_iop_permission, | ||
151 | }; | ||