diff options
author | Tejun Heo <tj@kernel.org> | 2014-02-07 13:32:07 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-02-07 19:05:35 -0500 |
commit | 3eef34ad7dc369b7183ec383908aff3da2f6e5ec (patch) | |
tree | fac6ef1dd497adeff55fae499d953d5d51061867 /fs | |
parent | 0c23b2259a4850494e2c53e864ea840597c6cdd3 (diff) |
kernfs: implement kernfs_get_parent(), kernfs_name/path() and friends
kernfs_node->parent and ->name are currently marked as "published"
indicating that kernfs users may access them directly; however, those
fields may get updated by kernfs_rename[_ns]() and unrestricted access
may lead to erroneous values or oops.
Protect ->parent and ->name updates with a irq-safe spinlock
kernfs_rename_lock and implement the following accessors for these
fields.
* kernfs_name() - format the node's name into the specified buffer
* kernfs_path() - format the node's path into the specified buffer
* pr_cont_kernfs_name() - pr_cont a node's name (doesn't need buffer)
* pr_cont_kernfs_path() - pr_cont a node's path (doesn't need buffer)
* kernfs_get_parent() - pin and return a node's parent
All can be called under any context. The recursive sysfs_pathname()
in fs/sysfs/dir.c is replaced with kernfs_path() and
sysfs_rename_dir_ns() is updated to use kernfs_get_parent() instead of
dereferencing parent directly.
v2: Dummy definition of kernfs_path() for !CONFIG_KERNFS was missing
static inline making it cause a lot of build warnings. Add it.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/kernfs/dir.c | 175 | ||||
-rw-r--r-- | fs/sysfs/dir.c | 44 |
2 files changed, 178 insertions, 41 deletions
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 42a250f83b98..a347792c2e5a 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c | |||
@@ -19,6 +19,8 @@ | |||
19 | #include "kernfs-internal.h" | 19 | #include "kernfs-internal.h" |
20 | 20 | ||
21 | DEFINE_MUTEX(kernfs_mutex); | 21 | DEFINE_MUTEX(kernfs_mutex); |
22 | static DEFINE_SPINLOCK(kernfs_rename_lock); /* kn->parent and ->name */ | ||
23 | static char kernfs_pr_cont_buf[PATH_MAX]; /* protected by rename_lock */ | ||
22 | 24 | ||
23 | #define rb_to_kn(X) rb_entry((X), struct kernfs_node, rb) | 25 | #define rb_to_kn(X) rb_entry((X), struct kernfs_node, rb) |
24 | 26 | ||
@@ -37,6 +39,141 @@ static bool kernfs_lockdep(struct kernfs_node *kn) | |||
37 | #endif | 39 | #endif |
38 | } | 40 | } |
39 | 41 | ||
42 | static int kernfs_name_locked(struct kernfs_node *kn, char *buf, size_t buflen) | ||
43 | { | ||
44 | return strlcpy(buf, kn->parent ? kn->name : "/", buflen); | ||
45 | } | ||
46 | |||
47 | static char * __must_check kernfs_path_locked(struct kernfs_node *kn, char *buf, | ||
48 | size_t buflen) | ||
49 | { | ||
50 | char *p = buf + buflen; | ||
51 | int len; | ||
52 | |||
53 | *--p = '\0'; | ||
54 | |||
55 | do { | ||
56 | len = strlen(kn->name); | ||
57 | if (p - buf < len + 1) { | ||
58 | buf[0] = '\0'; | ||
59 | p = NULL; | ||
60 | break; | ||
61 | } | ||
62 | p -= len; | ||
63 | memcpy(p, kn->name, len); | ||
64 | *--p = '/'; | ||
65 | kn = kn->parent; | ||
66 | } while (kn && kn->parent); | ||
67 | |||
68 | return p; | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * kernfs_name - obtain the name of a given node | ||
73 | * @kn: kernfs_node of interest | ||
74 | * @buf: buffer to copy @kn's name into | ||
75 | * @buflen: size of @buf | ||
76 | * | ||
77 | * Copies the name of @kn into @buf of @buflen bytes. The behavior is | ||
78 | * similar to strlcpy(). It returns the length of @kn's name and if @buf | ||
79 | * isn't long enough, it's filled upto @buflen-1 and nul terminated. | ||
80 | * | ||
81 | * This function can be called from any context. | ||
82 | */ | ||
83 | int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen) | ||
84 | { | ||
85 | unsigned long flags; | ||
86 | int ret; | ||
87 | |||
88 | spin_lock_irqsave(&kernfs_rename_lock, flags); | ||
89 | ret = kernfs_name_locked(kn, buf, buflen); | ||
90 | spin_unlock_irqrestore(&kernfs_rename_lock, flags); | ||
91 | return ret; | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * kernfs_path - build full path of a given node | ||
96 | * @kn: kernfs_node of interest | ||
97 | * @buf: buffer to copy @kn's name into | ||
98 | * @buflen: size of @buf | ||
99 | * | ||
100 | * Builds and returns the full path of @kn in @buf of @buflen bytes. The | ||
101 | * path is built from the end of @buf so the returned pointer usually | ||
102 | * doesn't match @buf. If @buf isn't long enough, @buf is nul terminated | ||
103 | * and %NULL is returned. | ||
104 | */ | ||
105 | char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen) | ||
106 | { | ||
107 | unsigned long flags; | ||
108 | char *p; | ||
109 | |||
110 | spin_lock_irqsave(&kernfs_rename_lock, flags); | ||
111 | p = kernfs_path_locked(kn, buf, buflen); | ||
112 | spin_unlock_irqrestore(&kernfs_rename_lock, flags); | ||
113 | return p; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * pr_cont_kernfs_name - pr_cont name of a kernfs_node | ||
118 | * @kn: kernfs_node of interest | ||
119 | * | ||
120 | * This function can be called from any context. | ||
121 | */ | ||
122 | void pr_cont_kernfs_name(struct kernfs_node *kn) | ||
123 | { | ||
124 | unsigned long flags; | ||
125 | |||
126 | spin_lock_irqsave(&kernfs_rename_lock, flags); | ||
127 | |||
128 | kernfs_name_locked(kn, kernfs_pr_cont_buf, sizeof(kernfs_pr_cont_buf)); | ||
129 | pr_cont("%s", kernfs_pr_cont_buf); | ||
130 | |||
131 | spin_unlock_irqrestore(&kernfs_rename_lock, flags); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * pr_cont_kernfs_path - pr_cont path of a kernfs_node | ||
136 | * @kn: kernfs_node of interest | ||
137 | * | ||
138 | * This function can be called from any context. | ||
139 | */ | ||
140 | void pr_cont_kernfs_path(struct kernfs_node *kn) | ||
141 | { | ||
142 | unsigned long flags; | ||
143 | char *p; | ||
144 | |||
145 | spin_lock_irqsave(&kernfs_rename_lock, flags); | ||
146 | |||
147 | p = kernfs_path_locked(kn, kernfs_pr_cont_buf, | ||
148 | sizeof(kernfs_pr_cont_buf)); | ||
149 | if (p) | ||
150 | pr_cont("%s", p); | ||
151 | else | ||
152 | pr_cont("<name too long>"); | ||
153 | |||
154 | spin_unlock_irqrestore(&kernfs_rename_lock, flags); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * kernfs_get_parent - determine the parent node and pin it | ||
159 | * @kn: kernfs_node of interest | ||
160 | * | ||
161 | * Determines @kn's parent, pins and returns it. This function can be | ||
162 | * called from any context. | ||
163 | */ | ||
164 | struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn) | ||
165 | { | ||
166 | struct kernfs_node *parent; | ||
167 | unsigned long flags; | ||
168 | |||
169 | spin_lock_irqsave(&kernfs_rename_lock, flags); | ||
170 | parent = kn->parent; | ||
171 | kernfs_get(parent); | ||
172 | spin_unlock_irqrestore(&kernfs_rename_lock, flags); | ||
173 | |||
174 | return parent; | ||
175 | } | ||
176 | |||
40 | /** | 177 | /** |
41 | * kernfs_name_hash | 178 | * kernfs_name_hash |
42 | * @name: Null terminated string to hash | 179 | * @name: Null terminated string to hash |
@@ -1103,8 +1240,14 @@ int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name, | |||
1103 | int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, | 1240 | int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, |
1104 | const char *new_name, const void *new_ns) | 1241 | const char *new_name, const void *new_ns) |
1105 | { | 1242 | { |
1243 | struct kernfs_node *old_parent; | ||
1244 | const char *old_name = NULL; | ||
1106 | int error; | 1245 | int error; |
1107 | 1246 | ||
1247 | /* can't move or rename root */ | ||
1248 | if (!kn->parent) | ||
1249 | return -EINVAL; | ||
1250 | |||
1108 | mutex_lock(&kernfs_mutex); | 1251 | mutex_lock(&kernfs_mutex); |
1109 | 1252 | ||
1110 | error = -ENOENT; | 1253 | error = -ENOENT; |
@@ -1126,13 +1269,8 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, | |||
1126 | new_name = kstrdup(new_name, GFP_KERNEL); | 1269 | new_name = kstrdup(new_name, GFP_KERNEL); |
1127 | if (!new_name) | 1270 | if (!new_name) |
1128 | goto out; | 1271 | goto out; |
1129 | 1272 | } else { | |
1130 | if (kn->flags & KERNFS_STATIC_NAME) | 1273 | new_name = NULL; |
1131 | kn->flags &= ~KERNFS_STATIC_NAME; | ||
1132 | else | ||
1133 | kfree(kn->name); | ||
1134 | |||
1135 | kn->name = new_name; | ||
1136 | } | 1274 | } |
1137 | 1275 | ||
1138 | /* | 1276 | /* |
@@ -1140,12 +1278,29 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, | |||
1140 | */ | 1278 | */ |
1141 | kernfs_unlink_sibling(kn); | 1279 | kernfs_unlink_sibling(kn); |
1142 | kernfs_get(new_parent); | 1280 | kernfs_get(new_parent); |
1143 | kernfs_put(kn->parent); | 1281 | |
1144 | kn->ns = new_ns; | 1282 | /* rename_lock protects ->parent and ->name accessors */ |
1145 | kn->hash = kernfs_name_hash(kn->name, kn->ns); | 1283 | spin_lock_irq(&kernfs_rename_lock); |
1284 | |||
1285 | old_parent = kn->parent; | ||
1146 | kn->parent = new_parent; | 1286 | kn->parent = new_parent; |
1287 | |||
1288 | kn->ns = new_ns; | ||
1289 | if (new_name) { | ||
1290 | if (!(kn->flags & KERNFS_STATIC_NAME)) | ||
1291 | old_name = kn->name; | ||
1292 | kn->flags &= ~KERNFS_STATIC_NAME; | ||
1293 | kn->name = new_name; | ||
1294 | } | ||
1295 | |||
1296 | spin_unlock_irq(&kernfs_rename_lock); | ||
1297 | |||
1298 | kn->hash = kernfs_name_hash(new_name, new_ns); | ||
1147 | kernfs_link_sibling(kn); | 1299 | kernfs_link_sibling(kn); |
1148 | 1300 | ||
1301 | kernfs_put(old_parent); | ||
1302 | kfree(old_name); | ||
1303 | |||
1149 | error = 0; | 1304 | error = 0; |
1150 | out: | 1305 | out: |
1151 | mutex_unlock(&kernfs_mutex); | 1306 | mutex_unlock(&kernfs_mutex); |
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index ee0d761c3179..0b45ff42f374 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c | |||
@@ -19,39 +19,18 @@ | |||
19 | 19 | ||
20 | DEFINE_SPINLOCK(sysfs_symlink_target_lock); | 20 | DEFINE_SPINLOCK(sysfs_symlink_target_lock); |
21 | 21 | ||
22 | /** | ||
23 | * sysfs_pathname - return full path to sysfs dirent | ||
24 | * @kn: kernfs_node whose path we want | ||
25 | * @path: caller allocated buffer of size PATH_MAX | ||
26 | * | ||
27 | * Gives the name "/" to the sysfs_root entry; any path returned | ||
28 | * is relative to wherever sysfs is mounted. | ||
29 | */ | ||
30 | static char *sysfs_pathname(struct kernfs_node *kn, char *path) | ||
31 | { | ||
32 | if (kn->parent) { | ||
33 | sysfs_pathname(kn->parent, path); | ||
34 | strlcat(path, "/", PATH_MAX); | ||
35 | } | ||
36 | strlcat(path, kn->name, PATH_MAX); | ||
37 | return path; | ||
38 | } | ||
39 | |||
40 | void sysfs_warn_dup(struct kernfs_node *parent, const char *name) | 22 | void sysfs_warn_dup(struct kernfs_node *parent, const char *name) |
41 | { | 23 | { |
42 | char *path; | 24 | char *buf, *path = NULL; |
43 | 25 | ||
44 | path = kzalloc(PATH_MAX, GFP_KERNEL); | 26 | buf = kzalloc(PATH_MAX, GFP_KERNEL); |
45 | if (path) { | 27 | if (buf) |
46 | sysfs_pathname(parent, path); | 28 | path = kernfs_path(parent, buf, PATH_MAX); |
47 | strlcat(path, "/", PATH_MAX); | ||
48 | strlcat(path, name, PATH_MAX); | ||
49 | } | ||
50 | 29 | ||
51 | WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s'\n", | 30 | WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s/%s'\n", |
52 | path ? path : name); | 31 | path, name); |
53 | 32 | ||
54 | kfree(path); | 33 | kfree(buf); |
55 | } | 34 | } |
56 | 35 | ||
57 | /** | 36 | /** |
@@ -122,9 +101,13 @@ void sysfs_remove_dir(struct kobject *kobj) | |||
122 | int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, | 101 | int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, |
123 | const void *new_ns) | 102 | const void *new_ns) |
124 | { | 103 | { |
125 | struct kernfs_node *parent = kobj->sd->parent; | 104 | struct kernfs_node *parent; |
105 | int ret; | ||
126 | 106 | ||
127 | return kernfs_rename_ns(kobj->sd, parent, new_name, new_ns); | 107 | parent = kernfs_get_parent(kobj->sd); |
108 | ret = kernfs_rename_ns(kobj->sd, parent, new_name, new_ns); | ||
109 | kernfs_put(parent); | ||
110 | return ret; | ||
128 | } | 111 | } |
129 | 112 | ||
130 | int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, | 113 | int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, |
@@ -133,7 +116,6 @@ int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, | |||
133 | struct kernfs_node *kn = kobj->sd; | 116 | struct kernfs_node *kn = kobj->sd; |
134 | struct kernfs_node *new_parent; | 117 | struct kernfs_node *new_parent; |
135 | 118 | ||
136 | BUG_ON(!kn->parent); | ||
137 | new_parent = new_parent_kobj && new_parent_kobj->sd ? | 119 | new_parent = new_parent_kobj && new_parent_kobj->sd ? |
138 | new_parent_kobj->sd : sysfs_root_kn; | 120 | new_parent_kobj->sd : sysfs_root_kn; |
139 | 121 | ||