diff options
Diffstat (limited to 'fs/kernfs/symlink.c')
-rw-r--r-- | fs/kernfs/symlink.c | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c new file mode 100644 index 000000000..5432883d8 --- /dev/null +++ b/fs/kernfs/symlink.c | |||
@@ -0,0 +1,153 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0-only | ||
2 | /* | ||
3 | * fs/kernfs/symlink.c - kernfs symlink implementation | ||
4 | * | ||
5 | * Copyright (c) 2001-3 Patrick Mochel | ||
6 | * Copyright (c) 2007 SUSE Linux Products GmbH | ||
7 | * Copyright (c) 2007, 2013 Tejun Heo <tj@kernel.org> | ||
8 | */ | ||
9 | |||
10 | #include <linux/fs.h> | ||
11 | #include <linux/gfp.h> | ||
12 | #include <linux/namei.h> | ||
13 | |||
14 | #include "kernfs-internal.h" | ||
15 | |||
16 | /** | ||
17 | * kernfs_create_link - create a symlink | ||
18 | * @parent: directory to create the symlink in | ||
19 | * @name: name of the symlink | ||
20 | * @target: target node for the symlink to point to | ||
21 | * | ||
22 | * Returns the created node on success, ERR_PTR() value on error. | ||
23 | * Ownership of the link matches ownership of the target. | ||
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 | int error; | ||
31 | kuid_t uid = GLOBAL_ROOT_UID; | ||
32 | kgid_t gid = GLOBAL_ROOT_GID; | ||
33 | |||
34 | if (target->iattr) { | ||
35 | uid = target->iattr->ia_uid; | ||
36 | gid = target->iattr->ia_gid; | ||
37 | } | ||
38 | |||
39 | kn = kernfs_new_node(parent, name, S_IFLNK|S_IRWXUGO, uid, gid, | ||
40 | KERNFS_LINK); | ||
41 | if (!kn) | ||
42 | return ERR_PTR(-ENOMEM); | ||
43 | |||
44 | if (kernfs_ns_enabled(parent)) | ||
45 | kn->ns = target->ns; | ||
46 | kn->symlink.target_kn = target; | ||
47 | kernfs_get(target); /* ref owned by symlink */ | ||
48 | |||
49 | error = kernfs_add_one(kn); | ||
50 | if (!error) | ||
51 | return kn; | ||
52 | |||
53 | kernfs_put(kn); | ||
54 | return ERR_PTR(error); | ||
55 | } | ||
56 | |||
57 | static int kernfs_get_target_path(struct kernfs_node *parent, | ||
58 | struct kernfs_node *target, char *path) | ||
59 | { | ||
60 | struct kernfs_node *base, *kn; | ||
61 | char *s = path; | ||
62 | int len = 0; | ||
63 | |||
64 | /* go up to the root, stop at the base */ | ||
65 | base = parent; | ||
66 | while (base->parent) { | ||
67 | kn = target->parent; | ||
68 | while (kn->parent && base != kn) | ||
69 | kn = kn->parent; | ||
70 | |||
71 | if (base == kn) | ||
72 | break; | ||
73 | |||
74 | if ((s - path) + 3 >= PATH_MAX) | ||
75 | return -ENAMETOOLONG; | ||
76 | |||
77 | strcpy(s, "../"); | ||
78 | s += 3; | ||
79 | base = base->parent; | ||
80 | } | ||
81 | |||
82 | /* determine end of target string for reverse fillup */ | ||
83 | kn = target; | ||
84 | while (kn->parent && kn != base) { | ||
85 | len += strlen(kn->name) + 1; | ||
86 | kn = kn->parent; | ||
87 | } | ||
88 | |||
89 | /* check limits */ | ||
90 | if (len < 2) | ||
91 | return -EINVAL; | ||
92 | len--; | ||
93 | if ((s - path) + len >= PATH_MAX) | ||
94 | return -ENAMETOOLONG; | ||
95 | |||
96 | /* reverse fillup of target string from target to base */ | ||
97 | kn = target; | ||
98 | while (kn->parent && kn != base) { | ||
99 | int slen = strlen(kn->name); | ||
100 | |||
101 | len -= slen; | ||
102 | memcpy(s + len, kn->name, slen); | ||
103 | if (len) | ||
104 | s[--len] = '/'; | ||
105 | |||
106 | kn = kn->parent; | ||
107 | } | ||
108 | |||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static int kernfs_getlink(struct inode *inode, char *path) | ||
113 | { | ||
114 | struct kernfs_node *kn = inode->i_private; | ||
115 | struct kernfs_node *parent = kn->parent; | ||
116 | struct kernfs_node *target = kn->symlink.target_kn; | ||
117 | int error; | ||
118 | |||
119 | mutex_lock(&kernfs_mutex); | ||
120 | error = kernfs_get_target_path(parent, target, path); | ||
121 | mutex_unlock(&kernfs_mutex); | ||
122 | |||
123 | return error; | ||
124 | } | ||
125 | |||
126 | static const char *kernfs_iop_get_link(struct dentry *dentry, | ||
127 | struct inode *inode, | ||
128 | struct delayed_call *done) | ||
129 | { | ||
130 | char *body; | ||
131 | int error; | ||
132 | |||
133 | if (!dentry) | ||
134 | return ERR_PTR(-ECHILD); | ||
135 | body = kzalloc(PAGE_SIZE, GFP_KERNEL); | ||
136 | if (!body) | ||
137 | return ERR_PTR(-ENOMEM); | ||
138 | error = kernfs_getlink(inode, body); | ||
139 | if (unlikely(error < 0)) { | ||
140 | kfree(body); | ||
141 | return ERR_PTR(error); | ||
142 | } | ||
143 | set_delayed_call(done, kfree_link, body); | ||
144 | return body; | ||
145 | } | ||
146 | |||
147 | const struct inode_operations kernfs_symlink_iops = { | ||
148 | .listxattr = kernfs_iop_listxattr, | ||
149 | .get_link = kernfs_iop_get_link, | ||
150 | .setattr = kernfs_iop_setattr, | ||
151 | .getattr = kernfs_iop_getattr, | ||
152 | .permission = kernfs_iop_permission, | ||
153 | }; | ||