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/kernfs | |
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/kernfs')
-rw-r--r-- | fs/kernfs/dir.c | 175 |
1 files changed, 165 insertions, 10 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); |