diff options
author | Tejun Heo <htejun@gmail.com> | 2007-06-11 01:04:01 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-06-12 19:08:47 -0400 |
commit | dd14cbc994709a1c5a64ed3621f583c49a27e521 (patch) | |
tree | e48d38b7450661907c7b75490504c7f70b04d6cc /fs | |
parent | 6aa054aadfea613a437ad0b15d38eca2b963fc0a (diff) |
sysfs: fix race condition around sd->s_dentry, take#2
Allowing attribute and symlink dentries to be reclaimed means
sd->s_dentry can change dynamically. However, updates to the field
are unsynchronized leading to race conditions. This patch adds
sysfs_lock and use it to synchronize updates to sd->s_dentry.
Due to the locking around ->d_iput, the check in sysfs_drop_dentry()
is complex. sysfs_lock only protect sd->s_dentry pointer itself. The
validity of the dentry is protected by dcache_lock, so whether dentry
is alive or not can only be tested while holding both locks.
This is minimal backport of sysfs_drop_dentry() rewrite in devel
branch.
Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/sysfs/dir.c | 22 | ||||
-rw-r--r-- | fs/sysfs/inode.c | 18 | ||||
-rw-r--r-- | fs/sysfs/sysfs.h | 1 |
3 files changed, 38 insertions, 3 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 17a819151b91..c4342a019972 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c | |||
@@ -13,14 +13,26 @@ | |||
13 | #include "sysfs.h" | 13 | #include "sysfs.h" |
14 | 14 | ||
15 | DECLARE_RWSEM(sysfs_rename_sem); | 15 | DECLARE_RWSEM(sysfs_rename_sem); |
16 | spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED; | ||
16 | 17 | ||
17 | static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) | 18 | static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) |
18 | { | 19 | { |
19 | struct sysfs_dirent * sd = dentry->d_fsdata; | 20 | struct sysfs_dirent * sd = dentry->d_fsdata; |
20 | 21 | ||
21 | if (sd) { | 22 | if (sd) { |
22 | BUG_ON(sd->s_dentry != dentry); | 23 | /* sd->s_dentry is protected with sysfs_lock. This |
23 | sd->s_dentry = NULL; | 24 | * allows sysfs_drop_dentry() to dereference it. |
25 | */ | ||
26 | spin_lock(&sysfs_lock); | ||
27 | |||
28 | /* The dentry might have been deleted or another | ||
29 | * lookup could have happened updating sd->s_dentry to | ||
30 | * point the new dentry. Ignore if it isn't pointing | ||
31 | * to this dentry. | ||
32 | */ | ||
33 | if (sd->s_dentry == dentry) | ||
34 | sd->s_dentry = NULL; | ||
35 | spin_unlock(&sysfs_lock); | ||
24 | sysfs_put(sd); | 36 | sysfs_put(sd); |
25 | } | 37 | } |
26 | iput(inode); | 38 | iput(inode); |
@@ -247,7 +259,10 @@ static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) | |||
247 | } | 259 | } |
248 | 260 | ||
249 | dentry->d_fsdata = sysfs_get(sd); | 261 | dentry->d_fsdata = sysfs_get(sd); |
262 | /* protect sd->s_dentry against sysfs_d_iput */ | ||
263 | spin_lock(&sysfs_lock); | ||
250 | sd->s_dentry = dentry; | 264 | sd->s_dentry = dentry; |
265 | spin_unlock(&sysfs_lock); | ||
251 | error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); | 266 | error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); |
252 | if (error) { | 267 | if (error) { |
253 | sysfs_put(sd); | 268 | sysfs_put(sd); |
@@ -269,7 +284,10 @@ static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) | |||
269 | int err = 0; | 284 | int err = 0; |
270 | 285 | ||
271 | dentry->d_fsdata = sysfs_get(sd); | 286 | dentry->d_fsdata = sysfs_get(sd); |
287 | /* protect sd->s_dentry against sysfs_d_iput */ | ||
288 | spin_lock(&sysfs_lock); | ||
272 | sd->s_dentry = dentry; | 289 | sd->s_dentry = dentry; |
290 | spin_unlock(&sysfs_lock); | ||
273 | err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); | 291 | err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); |
274 | if (!err) { | 292 | if (!err) { |
275 | dentry->d_op = &sysfs_dentry_ops; | 293 | dentry->d_op = &sysfs_dentry_ops; |
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 38bbe071cc15..5266eec15f6e 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c | |||
@@ -246,9 +246,23 @@ static inline void orphan_all_buffers(struct inode *node) | |||
246 | */ | 246 | */ |
247 | void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) | 247 | void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) |
248 | { | 248 | { |
249 | struct dentry * dentry = sd->s_dentry; | 249 | struct dentry *dentry = NULL; |
250 | struct inode *inode; | 250 | struct inode *inode; |
251 | 251 | ||
252 | /* We're not holding a reference to ->s_dentry dentry but the | ||
253 | * field will stay valid as long as sysfs_lock is held. | ||
254 | */ | ||
255 | spin_lock(&sysfs_lock); | ||
256 | spin_lock(&dcache_lock); | ||
257 | |||
258 | /* dget dentry if it's still alive */ | ||
259 | if (sd->s_dentry && sd->s_dentry->d_inode) | ||
260 | dentry = dget_locked(sd->s_dentry); | ||
261 | |||
262 | spin_unlock(&dcache_lock); | ||
263 | spin_unlock(&sysfs_lock); | ||
264 | |||
265 | /* drop dentry */ | ||
252 | if (dentry) { | 266 | if (dentry) { |
253 | spin_lock(&dcache_lock); | 267 | spin_lock(&dcache_lock); |
254 | spin_lock(&dentry->d_lock); | 268 | spin_lock(&dentry->d_lock); |
@@ -268,6 +282,8 @@ void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) | |||
268 | spin_unlock(&dentry->d_lock); | 282 | spin_unlock(&dentry->d_lock); |
269 | spin_unlock(&dcache_lock); | 283 | spin_unlock(&dcache_lock); |
270 | } | 284 | } |
285 | |||
286 | dput(dentry); | ||
271 | } | 287 | } |
272 | } | 288 | } |
273 | 289 | ||
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 1966e1a0a015..502c949c402d 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h | |||
@@ -33,6 +33,7 @@ extern const unsigned char * sysfs_get_name(struct sysfs_dirent *sd); | |||
33 | extern void sysfs_drop_dentry(struct sysfs_dirent *sd, struct dentry *parent); | 33 | extern void sysfs_drop_dentry(struct sysfs_dirent *sd, struct dentry *parent); |
34 | extern int sysfs_setattr(struct dentry *dentry, struct iattr *iattr); | 34 | extern int sysfs_setattr(struct dentry *dentry, struct iattr *iattr); |
35 | 35 | ||
36 | extern spinlock_t sysfs_lock; | ||
36 | extern struct rw_semaphore sysfs_rename_sem; | 37 | extern struct rw_semaphore sysfs_rename_sem; |
37 | extern struct super_block * sysfs_sb; | 38 | extern struct super_block * sysfs_sb; |
38 | extern const struct file_operations sysfs_dir_operations; | 39 | extern const struct file_operations sysfs_dir_operations; |