diff options
author | Oliver Neukum <oliver@neukum.org> | 2006-12-20 04:52:44 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-02-07 13:37:13 -0500 |
commit | 94bebf4d1b8e7719f0f3944c037a21cfd99a4af7 (patch) | |
tree | 25c6ba1836e74f608b4e434b4f0f4d5c28b11de1 /fs | |
parent | cb986b749c7178422bfbc982cd30e04d5db54bbc (diff) |
Driver core: fix race in sysfs between sysfs_remove_file() and read()/write()
This patch prevents a race between IO and removing a file from sysfs.
It introduces a list of sysfs_buffers associated with a file at the inode.
Upon removal of a file the list is walked and the buffers marked orphaned.
IO to orphaned buffers fails with -ENODEV. The driver can safely free
associated data structures or be unloaded.
Signed-off-by: Oliver Neukum <oliver@neukum.name>
Acked-by: Maneesh Soni <maneesh@in.ibm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/sysfs/bin.c | 1 | ||||
-rw-r--r-- | fs/sysfs/dir.c | 1 | ||||
-rw-r--r-- | fs/sysfs/file.c | 67 | ||||
-rw-r--r-- | fs/sysfs/group.c | 1 | ||||
-rw-r--r-- | fs/sysfs/inode.c | 24 | ||||
-rw-r--r-- | fs/sysfs/mount.c | 9 | ||||
-rw-r--r-- | fs/sysfs/symlink.c | 1 | ||||
-rw-r--r-- | fs/sysfs/sysfs.h | 16 |
8 files changed, 103 insertions, 17 deletions
diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c index e8f540d38d4..9ec1c8539a5 100644 --- a/fs/sysfs/bin.c +++ b/fs/sysfs/bin.c | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <linux/slab.h> | 16 | #include <linux/slab.h> |
17 | 17 | ||
18 | #include <asm/uaccess.h> | 18 | #include <asm/uaccess.h> |
19 | #include <asm/semaphore.h> | ||
19 | 20 | ||
20 | #include "sysfs.h" | 21 | #include "sysfs.h" |
21 | 22 | ||
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 2bab1b4ddf5..9ff04491e3e 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include <linux/module.h> | 9 | #include <linux/module.h> |
10 | #include <linux/kobject.h> | 10 | #include <linux/kobject.h> |
11 | #include <linux/namei.h> | 11 | #include <linux/namei.h> |
12 | #include <asm/semaphore.h> | ||
12 | #include "sysfs.h" | 13 | #include "sysfs.h" |
13 | 14 | ||
14 | DECLARE_RWSEM(sysfs_rename_sem); | 15 | DECLARE_RWSEM(sysfs_rename_sem); |
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 9cfe53e1e00..cba4c1c7383 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c | |||
@@ -7,6 +7,7 @@ | |||
7 | #include <linux/kobject.h> | 7 | #include <linux/kobject.h> |
8 | #include <linux/namei.h> | 8 | #include <linux/namei.h> |
9 | #include <linux/poll.h> | 9 | #include <linux/poll.h> |
10 | #include <linux/list.h> | ||
10 | #include <asm/uaccess.h> | 11 | #include <asm/uaccess.h> |
11 | #include <asm/semaphore.h> | 12 | #include <asm/semaphore.h> |
12 | 13 | ||
@@ -50,17 +51,29 @@ static struct sysfs_ops subsys_sysfs_ops = { | |||
50 | .store = subsys_attr_store, | 51 | .store = subsys_attr_store, |
51 | }; | 52 | }; |
52 | 53 | ||
54 | /** | ||
55 | * add_to_collection - add buffer to a collection | ||
56 | * @buffer: buffer to be added | ||
57 | * @node inode of set to add to | ||
58 | */ | ||
53 | 59 | ||
54 | struct sysfs_buffer { | 60 | static inline void |
55 | size_t count; | 61 | add_to_collection(struct sysfs_buffer *buffer, struct inode *node) |
56 | loff_t pos; | 62 | { |
57 | char * page; | 63 | struct sysfs_buffer_collection *set = node->i_private; |
58 | struct sysfs_ops * ops; | ||
59 | struct semaphore sem; | ||
60 | int needs_read_fill; | ||
61 | int event; | ||
62 | }; | ||
63 | 64 | ||
65 | mutex_lock(&node->i_mutex); | ||
66 | list_add(&buffer->associates, &set->associates); | ||
67 | mutex_unlock(&node->i_mutex); | ||
68 | } | ||
69 | |||
70 | static inline void | ||
71 | remove_from_collection(struct sysfs_buffer *buffer, struct inode *node) | ||
72 | { | ||
73 | mutex_lock(&node->i_mutex); | ||
74 | list_del(&buffer->associates); | ||
75 | mutex_unlock(&node->i_mutex); | ||
76 | } | ||
64 | 77 | ||
65 | /** | 78 | /** |
66 | * fill_read_buffer - allocate and fill buffer from object. | 79 | * fill_read_buffer - allocate and fill buffer from object. |
@@ -153,6 +166,10 @@ sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) | |||
153 | ssize_t retval = 0; | 166 | ssize_t retval = 0; |
154 | 167 | ||
155 | down(&buffer->sem); | 168 | down(&buffer->sem); |
169 | if (buffer->orphaned) { | ||
170 | retval = -ENODEV; | ||
171 | goto out; | ||
172 | } | ||
156 | if (buffer->needs_read_fill) { | 173 | if (buffer->needs_read_fill) { |
157 | if ((retval = fill_read_buffer(file->f_path.dentry,buffer))) | 174 | if ((retval = fill_read_buffer(file->f_path.dentry,buffer))) |
158 | goto out; | 175 | goto out; |
@@ -165,7 +182,6 @@ out: | |||
165 | return retval; | 182 | return retval; |
166 | } | 183 | } |
167 | 184 | ||
168 | |||
169 | /** | 185 | /** |
170 | * fill_write_buffer - copy buffer from userspace. | 186 | * fill_write_buffer - copy buffer from userspace. |
171 | * @buffer: data buffer for file. | 187 | * @buffer: data buffer for file. |
@@ -243,19 +259,25 @@ sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t | |||
243 | ssize_t len; | 259 | ssize_t len; |
244 | 260 | ||
245 | down(&buffer->sem); | 261 | down(&buffer->sem); |
262 | if (buffer->orphaned) { | ||
263 | len = -ENODEV; | ||
264 | goto out; | ||
265 | } | ||
246 | len = fill_write_buffer(buffer, buf, count); | 266 | len = fill_write_buffer(buffer, buf, count); |
247 | if (len > 0) | 267 | if (len > 0) |
248 | len = flush_write_buffer(file->f_path.dentry, buffer, len); | 268 | len = flush_write_buffer(file->f_path.dentry, buffer, len); |
249 | if (len > 0) | 269 | if (len > 0) |
250 | *ppos += len; | 270 | *ppos += len; |
271 | out: | ||
251 | up(&buffer->sem); | 272 | up(&buffer->sem); |
252 | return len; | 273 | return len; |
253 | } | 274 | } |
254 | 275 | ||
255 | static int check_perm(struct inode * inode, struct file * file) | 276 | static int sysfs_open_file(struct inode *inode, struct file *file) |
256 | { | 277 | { |
257 | struct kobject *kobj = sysfs_get_kobject(file->f_path.dentry->d_parent); | 278 | struct kobject *kobj = sysfs_get_kobject(file->f_path.dentry->d_parent); |
258 | struct attribute * attr = to_attr(file->f_path.dentry); | 279 | struct attribute * attr = to_attr(file->f_path.dentry); |
280 | struct sysfs_buffer_collection *set; | ||
259 | struct sysfs_buffer * buffer; | 281 | struct sysfs_buffer * buffer; |
260 | struct sysfs_ops * ops = NULL; | 282 | struct sysfs_ops * ops = NULL; |
261 | int error = 0; | 283 | int error = 0; |
@@ -285,6 +307,18 @@ static int check_perm(struct inode * inode, struct file * file) | |||
285 | if (!ops) | 307 | if (!ops) |
286 | goto Eaccess; | 308 | goto Eaccess; |
287 | 309 | ||
310 | /* make sure we have a collection to add our buffers to */ | ||
311 | mutex_lock(&inode->i_mutex); | ||
312 | if (!(set = inode->i_private)) { | ||
313 | if (!(set = inode->i_private = kmalloc(sizeof(struct sysfs_buffer_collection), GFP_KERNEL))) { | ||
314 | error = -ENOMEM; | ||
315 | goto Done; | ||
316 | } else { | ||
317 | INIT_LIST_HEAD(&set->associates); | ||
318 | } | ||
319 | } | ||
320 | mutex_unlock(&inode->i_mutex); | ||
321 | |||
288 | /* File needs write support. | 322 | /* File needs write support. |
289 | * The inode's perms must say it's ok, | 323 | * The inode's perms must say it's ok, |
290 | * and we must have a store method. | 324 | * and we must have a store method. |
@@ -310,9 +344,11 @@ static int check_perm(struct inode * inode, struct file * file) | |||
310 | */ | 344 | */ |
311 | buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL); | 345 | buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL); |
312 | if (buffer) { | 346 | if (buffer) { |
347 | INIT_LIST_HEAD(&buffer->associates); | ||
313 | init_MUTEX(&buffer->sem); | 348 | init_MUTEX(&buffer->sem); |
314 | buffer->needs_read_fill = 1; | 349 | buffer->needs_read_fill = 1; |
315 | buffer->ops = ops; | 350 | buffer->ops = ops; |
351 | add_to_collection(buffer, inode); | ||
316 | file->private_data = buffer; | 352 | file->private_data = buffer; |
317 | } else | 353 | } else |
318 | error = -ENOMEM; | 354 | error = -ENOMEM; |
@@ -330,11 +366,6 @@ static int check_perm(struct inode * inode, struct file * file) | |||
330 | return error; | 366 | return error; |
331 | } | 367 | } |
332 | 368 | ||
333 | static int sysfs_open_file(struct inode * inode, struct file * filp) | ||
334 | { | ||
335 | return check_perm(inode,filp); | ||
336 | } | ||
337 | |||
338 | static int sysfs_release(struct inode * inode, struct file * filp) | 369 | static int sysfs_release(struct inode * inode, struct file * filp) |
339 | { | 370 | { |
340 | struct kobject * kobj = to_kobj(filp->f_path.dentry->d_parent); | 371 | struct kobject * kobj = to_kobj(filp->f_path.dentry->d_parent); |
@@ -342,6 +373,8 @@ static int sysfs_release(struct inode * inode, struct file * filp) | |||
342 | struct module * owner = attr->owner; | 373 | struct module * owner = attr->owner; |
343 | struct sysfs_buffer * buffer = filp->private_data; | 374 | struct sysfs_buffer * buffer = filp->private_data; |
344 | 375 | ||
376 | if (buffer) | ||
377 | remove_from_collection(buffer, inode); | ||
345 | if (kobj) | 378 | if (kobj) |
346 | kobject_put(kobj); | 379 | kobject_put(kobj); |
347 | /* After this point, attr should not be accessed. */ | 380 | /* After this point, attr should not be accessed. */ |
@@ -548,7 +581,7 @@ EXPORT_SYMBOL_GPL(sysfs_chmod_file); | |||
548 | 581 | ||
549 | void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr) | 582 | void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr) |
550 | { | 583 | { |
551 | sysfs_hash_and_remove(kobj->dentry,attr->name); | 584 | sysfs_hash_and_remove(kobj->dentry, attr->name); |
552 | } | 585 | } |
553 | 586 | ||
554 | 587 | ||
diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 122145b0895..46a277b0838 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c | |||
@@ -13,6 +13,7 @@ | |||
13 | #include <linux/dcache.h> | 13 | #include <linux/dcache.h> |
14 | #include <linux/namei.h> | 14 | #include <linux/namei.h> |
15 | #include <linux/err.h> | 15 | #include <linux/err.h> |
16 | #include <asm/semaphore.h> | ||
16 | #include "sysfs.h" | 17 | #include "sysfs.h" |
17 | 18 | ||
18 | 19 | ||
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index e79e38d52c0..1a81ebce20e 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c | |||
@@ -13,6 +13,7 @@ | |||
13 | #include <linux/backing-dev.h> | 13 | #include <linux/backing-dev.h> |
14 | #include <linux/capability.h> | 14 | #include <linux/capability.h> |
15 | #include <linux/errno.h> | 15 | #include <linux/errno.h> |
16 | #include <asm/semaphore.h> | ||
16 | #include "sysfs.h" | 17 | #include "sysfs.h" |
17 | 18 | ||
18 | extern struct super_block * sysfs_sb; | 19 | extern struct super_block * sysfs_sb; |
@@ -209,6 +210,22 @@ const unsigned char * sysfs_get_name(struct sysfs_dirent *sd) | |||
209 | return NULL; | 210 | return NULL; |
210 | } | 211 | } |
211 | 212 | ||
213 | static inline void orphan_all_buffers(struct inode *node) | ||
214 | { | ||
215 | struct sysfs_buffer_collection *set = node->i_private; | ||
216 | struct sysfs_buffer *buf; | ||
217 | |||
218 | mutex_lock(&node->i_mutex); | ||
219 | if (node->i_private) { | ||
220 | list_for_each_entry(buf, &set->associates, associates) { | ||
221 | down(&buf->sem); | ||
222 | buf->orphaned = 1; | ||
223 | up(&buf->sem); | ||
224 | } | ||
225 | } | ||
226 | mutex_unlock(&node->i_mutex); | ||
227 | } | ||
228 | |||
212 | 229 | ||
213 | /* | 230 | /* |
214 | * Unhashes the dentry corresponding to given sysfs_dirent | 231 | * Unhashes the dentry corresponding to given sysfs_dirent |
@@ -217,16 +234,23 @@ const unsigned char * sysfs_get_name(struct sysfs_dirent *sd) | |||
217 | void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) | 234 | void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) |
218 | { | 235 | { |
219 | struct dentry * dentry = sd->s_dentry; | 236 | struct dentry * dentry = sd->s_dentry; |
237 | struct inode *inode; | ||
220 | 238 | ||
221 | if (dentry) { | 239 | if (dentry) { |
222 | spin_lock(&dcache_lock); | 240 | spin_lock(&dcache_lock); |
223 | spin_lock(&dentry->d_lock); | 241 | spin_lock(&dentry->d_lock); |
224 | if (!(d_unhashed(dentry) && dentry->d_inode)) { | 242 | if (!(d_unhashed(dentry) && dentry->d_inode)) { |
243 | inode = dentry->d_inode; | ||
244 | spin_lock(&inode->i_lock); | ||
245 | __iget(inode); | ||
246 | spin_unlock(&inode->i_lock); | ||
225 | dget_locked(dentry); | 247 | dget_locked(dentry); |
226 | __d_drop(dentry); | 248 | __d_drop(dentry); |
227 | spin_unlock(&dentry->d_lock); | 249 | spin_unlock(&dentry->d_lock); |
228 | spin_unlock(&dcache_lock); | 250 | spin_unlock(&dcache_lock); |
229 | simple_unlink(parent->d_inode, dentry); | 251 | simple_unlink(parent->d_inode, dentry); |
252 | orphan_all_buffers(inode); | ||
253 | iput(inode); | ||
230 | } else { | 254 | } else { |
231 | spin_unlock(&dentry->d_lock); | 255 | spin_unlock(&dentry->d_lock); |
232 | spin_unlock(&dcache_lock); | 256 | spin_unlock(&dcache_lock); |
diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c index e503f858fba..a1a58b97f32 100644 --- a/fs/sysfs/mount.c +++ b/fs/sysfs/mount.c | |||
@@ -8,6 +8,7 @@ | |||
8 | #include <linux/mount.h> | 8 | #include <linux/mount.h> |
9 | #include <linux/pagemap.h> | 9 | #include <linux/pagemap.h> |
10 | #include <linux/init.h> | 10 | #include <linux/init.h> |
11 | #include <asm/semaphore.h> | ||
11 | 12 | ||
12 | #include "sysfs.h" | 13 | #include "sysfs.h" |
13 | 14 | ||
@@ -18,9 +19,12 @@ struct vfsmount *sysfs_mount; | |||
18 | struct super_block * sysfs_sb = NULL; | 19 | struct super_block * sysfs_sb = NULL; |
19 | struct kmem_cache *sysfs_dir_cachep; | 20 | struct kmem_cache *sysfs_dir_cachep; |
20 | 21 | ||
22 | static void sysfs_clear_inode(struct inode *inode); | ||
23 | |||
21 | static struct super_operations sysfs_ops = { | 24 | static struct super_operations sysfs_ops = { |
22 | .statfs = simple_statfs, | 25 | .statfs = simple_statfs, |
23 | .drop_inode = generic_delete_inode, | 26 | .drop_inode = generic_delete_inode, |
27 | .clear_inode = sysfs_clear_inode, | ||
24 | }; | 28 | }; |
25 | 29 | ||
26 | static struct sysfs_dirent sysfs_root = { | 30 | static struct sysfs_dirent sysfs_root = { |
@@ -31,6 +35,11 @@ static struct sysfs_dirent sysfs_root = { | |||
31 | .s_iattr = NULL, | 35 | .s_iattr = NULL, |
32 | }; | 36 | }; |
33 | 37 | ||
38 | static void sysfs_clear_inode(struct inode *inode) | ||
39 | { | ||
40 | kfree(inode->i_private); | ||
41 | } | ||
42 | |||
34 | static int sysfs_fill_super(struct super_block *sb, void *data, int silent) | 43 | static int sysfs_fill_super(struct super_block *sb, void *data, int silent) |
35 | { | 44 | { |
36 | struct inode *inode; | 45 | struct inode *inode; |
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index f50e3cc2ded..4869f611192 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c | |||
@@ -7,6 +7,7 @@ | |||
7 | #include <linux/module.h> | 7 | #include <linux/module.h> |
8 | #include <linux/kobject.h> | 8 | #include <linux/kobject.h> |
9 | #include <linux/namei.h> | 9 | #include <linux/namei.h> |
10 | #include <asm/semaphore.h> | ||
10 | 11 | ||
11 | #include "sysfs.h" | 12 | #include "sysfs.h" |
12 | 13 | ||
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index bd7cec295da..39c623fdc27 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h | |||
@@ -33,6 +33,22 @@ struct sysfs_symlink { | |||
33 | struct kobject * target_kobj; | 33 | struct kobject * target_kobj; |
34 | }; | 34 | }; |
35 | 35 | ||
36 | struct sysfs_buffer { | ||
37 | struct list_head associates; | ||
38 | size_t count; | ||
39 | loff_t pos; | ||
40 | char * page; | ||
41 | struct sysfs_ops * ops; | ||
42 | struct semaphore sem; | ||
43 | int orphaned; | ||
44 | int needs_read_fill; | ||
45 | int event; | ||
46 | }; | ||
47 | |||
48 | struct sysfs_buffer_collection { | ||
49 | struct list_head associates; | ||
50 | }; | ||
51 | |||
36 | static inline struct kobject * to_kobj(struct dentry * dentry) | 52 | static inline struct kobject * to_kobj(struct dentry * dentry) |
37 | { | 53 | { |
38 | struct sysfs_dirent * sd = dentry->d_fsdata; | 54 | struct sysfs_dirent * sd = dentry->d_fsdata; |