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/sysfs/inode.c | |
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/sysfs/inode.c')
-rw-r--r-- | fs/sysfs/inode.c | 24 |
1 files changed, 24 insertions, 0 deletions
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index e79e38d52c00..1a81ebce20e9 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); |