diff options
author | Ian Kent <raven@themaw.net> | 2008-07-24 00:30:26 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-07-24 13:47:32 -0400 |
commit | 97e7449a7ad883bf9f516fc970778d75999c7843 (patch) | |
tree | 903f6de243847eb12d1a372b271b15046ecdc774 /fs/autofs4 | |
parent | 26e81b3142f1ba497d4cd0365c13661684b784ce (diff) |
autofs4: fix indirect mount pending expire race
The selection of a dentry for expiration and the setting of the
AUTOFS_INF_EXPIRING flag isn't done atomically which can lead to lookups
walking into an expiring mount.
What happens is that an expire is initiated by the daemon and a dentry is
selected for expire but, since there is no lock held between the selection
and setting of the expiring flag, a process may find the flag clear and
continue walking into the mount tree at the same time the daemon attempts
the expire it.
Signed-off-by: Ian Kent <raven@themaw.net>
Reviewed-by: Jeff Moyer <jmoyer@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/autofs4')
-rw-r--r-- | fs/autofs4/autofs_i.h | 10 | ||||
-rw-r--r-- | fs/autofs4/expire.c | 46 | ||||
-rw-r--r-- | fs/autofs4/root.c | 32 |
3 files changed, 65 insertions, 23 deletions
diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h index 058e1800caec..5d90ed3b4b43 100644 --- a/fs/autofs4/autofs_i.h +++ b/fs/autofs4/autofs_i.h | |||
@@ -138,18 +138,14 @@ static inline int autofs4_oz_mode(struct autofs_sb_info *sbi) { | |||
138 | static inline int autofs4_ispending(struct dentry *dentry) | 138 | static inline int autofs4_ispending(struct dentry *dentry) |
139 | { | 139 | { |
140 | struct autofs_info *inf = autofs4_dentry_ino(dentry); | 140 | struct autofs_info *inf = autofs4_dentry_ino(dentry); |
141 | int pending = 0; | ||
142 | 141 | ||
143 | if (dentry->d_flags & DCACHE_AUTOFS_PENDING) | 142 | if (dentry->d_flags & DCACHE_AUTOFS_PENDING) |
144 | return 1; | 143 | return 1; |
145 | 144 | ||
146 | if (inf) { | 145 | if (inf->flags & AUTOFS_INF_EXPIRING) |
147 | spin_lock(&inf->sbi->fs_lock); | 146 | return 1; |
148 | pending = inf->flags & AUTOFS_INF_EXPIRING; | ||
149 | spin_unlock(&inf->sbi->fs_lock); | ||
150 | } | ||
151 | 147 | ||
152 | return pending; | 148 | return 0; |
153 | } | 149 | } |
154 | 150 | ||
155 | static inline void autofs4_copy_atime(struct file *src, struct file *dst) | 151 | static inline void autofs4_copy_atime(struct file *src, struct file *dst) |
diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c index 894fee54d4d8..19f5bea2704f 100644 --- a/fs/autofs4/expire.c +++ b/fs/autofs4/expire.c | |||
@@ -292,6 +292,8 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb, | |||
292 | struct list_head *next; | 292 | struct list_head *next; |
293 | int do_now = how & AUTOFS_EXP_IMMEDIATE; | 293 | int do_now = how & AUTOFS_EXP_IMMEDIATE; |
294 | int exp_leaves = how & AUTOFS_EXP_LEAVES; | 294 | int exp_leaves = how & AUTOFS_EXP_LEAVES; |
295 | struct autofs_info *ino; | ||
296 | unsigned int ino_count; | ||
295 | 297 | ||
296 | if (!root) | 298 | if (!root) |
297 | return NULL; | 299 | return NULL; |
@@ -316,6 +318,9 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb, | |||
316 | dentry = dget(dentry); | 318 | dentry = dget(dentry); |
317 | spin_unlock(&dcache_lock); | 319 | spin_unlock(&dcache_lock); |
318 | 320 | ||
321 | spin_lock(&sbi->fs_lock); | ||
322 | ino = autofs4_dentry_ino(dentry); | ||
323 | |||
319 | /* | 324 | /* |
320 | * Case 1: (i) indirect mount or top level pseudo direct mount | 325 | * Case 1: (i) indirect mount or top level pseudo direct mount |
321 | * (autofs-4.1). | 326 | * (autofs-4.1). |
@@ -326,6 +331,11 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb, | |||
326 | DPRINTK("checking mountpoint %p %.*s", | 331 | DPRINTK("checking mountpoint %p %.*s", |
327 | dentry, (int)dentry->d_name.len, dentry->d_name.name); | 332 | dentry, (int)dentry->d_name.len, dentry->d_name.name); |
328 | 333 | ||
334 | /* Path walk currently on this dentry? */ | ||
335 | ino_count = atomic_read(&ino->count) + 2; | ||
336 | if (atomic_read(&dentry->d_count) > ino_count) | ||
337 | goto next; | ||
338 | |||
329 | /* Can we umount this guy */ | 339 | /* Can we umount this guy */ |
330 | if (autofs4_mount_busy(mnt, dentry)) | 340 | if (autofs4_mount_busy(mnt, dentry)) |
331 | goto next; | 341 | goto next; |
@@ -343,23 +353,25 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb, | |||
343 | 353 | ||
344 | /* Case 2: tree mount, expire iff entire tree is not busy */ | 354 | /* Case 2: tree mount, expire iff entire tree is not busy */ |
345 | if (!exp_leaves) { | 355 | if (!exp_leaves) { |
346 | /* Lock the tree as we must expire as a whole */ | 356 | /* Path walk currently on this dentry? */ |
347 | spin_lock(&sbi->fs_lock); | 357 | ino_count = atomic_read(&ino->count) + 1; |
348 | if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { | 358 | if (atomic_read(&dentry->d_count) > ino_count) |
349 | struct autofs_info *inf = autofs4_dentry_ino(dentry); | 359 | goto next; |
350 | 360 | ||
351 | /* Set this flag early to catch sys_chdir and the like */ | 361 | if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { |
352 | inf->flags |= AUTOFS_INF_EXPIRING; | ||
353 | spin_unlock(&sbi->fs_lock); | ||
354 | expired = dentry; | 362 | expired = dentry; |
355 | goto found; | 363 | goto found; |
356 | } | 364 | } |
357 | spin_unlock(&sbi->fs_lock); | ||
358 | /* | 365 | /* |
359 | * Case 3: pseudo direct mount, expire individual leaves | 366 | * Case 3: pseudo direct mount, expire individual leaves |
360 | * (autofs-4.1). | 367 | * (autofs-4.1). |
361 | */ | 368 | */ |
362 | } else { | 369 | } else { |
370 | /* Path walk currently on this dentry? */ | ||
371 | ino_count = atomic_read(&ino->count) + 1; | ||
372 | if (atomic_read(&dentry->d_count) > ino_count) | ||
373 | goto next; | ||
374 | |||
363 | expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); | 375 | expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); |
364 | if (expired) { | 376 | if (expired) { |
365 | dput(dentry); | 377 | dput(dentry); |
@@ -367,6 +379,7 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb, | |||
367 | } | 379 | } |
368 | } | 380 | } |
369 | next: | 381 | next: |
382 | spin_unlock(&sbi->fs_lock); | ||
370 | dput(dentry); | 383 | dput(dentry); |
371 | spin_lock(&dcache_lock); | 384 | spin_lock(&dcache_lock); |
372 | next = next->next; | 385 | next = next->next; |
@@ -377,6 +390,9 @@ next: | |||
377 | found: | 390 | found: |
378 | DPRINTK("returning %p %.*s", | 391 | DPRINTK("returning %p %.*s", |
379 | expired, (int)expired->d_name.len, expired->d_name.name); | 392 | expired, (int)expired->d_name.len, expired->d_name.name); |
393 | ino = autofs4_dentry_ino(expired); | ||
394 | ino->flags |= AUTOFS_INF_EXPIRING; | ||
395 | spin_unlock(&sbi->fs_lock); | ||
380 | spin_lock(&dcache_lock); | 396 | spin_lock(&dcache_lock); |
381 | list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); | 397 | list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); |
382 | spin_unlock(&dcache_lock); | 398 | spin_unlock(&dcache_lock); |
@@ -390,7 +406,9 @@ int autofs4_expire_run(struct super_block *sb, | |||
390 | struct autofs_packet_expire __user *pkt_p) | 406 | struct autofs_packet_expire __user *pkt_p) |
391 | { | 407 | { |
392 | struct autofs_packet_expire pkt; | 408 | struct autofs_packet_expire pkt; |
409 | struct autofs_info *ino; | ||
393 | struct dentry *dentry; | 410 | struct dentry *dentry; |
411 | int ret = 0; | ||
394 | 412 | ||
395 | memset(&pkt,0,sizeof pkt); | 413 | memset(&pkt,0,sizeof pkt); |
396 | 414 | ||
@@ -406,9 +424,14 @@ int autofs4_expire_run(struct super_block *sb, | |||
406 | dput(dentry); | 424 | dput(dentry); |
407 | 425 | ||
408 | if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) | 426 | if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) |
409 | return -EFAULT; | 427 | ret = -EFAULT; |
410 | 428 | ||
411 | return 0; | 429 | spin_lock(&sbi->fs_lock); |
430 | ino = autofs4_dentry_ino(dentry); | ||
431 | ino->flags &= ~AUTOFS_INF_EXPIRING; | ||
432 | spin_unlock(&sbi->fs_lock); | ||
433 | |||
434 | return ret; | ||
412 | } | 435 | } |
413 | 436 | ||
414 | /* Call repeatedly until it returns -EAGAIN, meaning there's nothing | 437 | /* Call repeatedly until it returns -EAGAIN, meaning there's nothing |
@@ -433,9 +456,10 @@ int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, | |||
433 | 456 | ||
434 | /* This is synchronous because it makes the daemon a | 457 | /* This is synchronous because it makes the daemon a |
435 | little easier */ | 458 | little easier */ |
436 | ino->flags |= AUTOFS_INF_EXPIRING; | ||
437 | ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); | 459 | ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); |
460 | spin_lock(&sbi->fs_lock); | ||
438 | ino->flags &= ~AUTOFS_INF_EXPIRING; | 461 | ino->flags &= ~AUTOFS_INF_EXPIRING; |
462 | spin_unlock(&sbi->fs_lock); | ||
439 | dput(dentry); | 463 | dput(dentry); |
440 | } | 464 | } |
441 | 465 | ||
diff --git a/fs/autofs4/root.c b/fs/autofs4/root.c index 61d1dca16884..1c2579de1f2e 100644 --- a/fs/autofs4/root.c +++ b/fs/autofs4/root.c | |||
@@ -133,7 +133,10 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags) | |||
133 | /* Block on any pending expiry here; invalidate the dentry | 133 | /* Block on any pending expiry here; invalidate the dentry |
134 | when expiration is done to trigger mount request with a new | 134 | when expiration is done to trigger mount request with a new |
135 | dentry */ | 135 | dentry */ |
136 | if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { | 136 | spin_lock(&sbi->fs_lock); |
137 | if (ino->flags & AUTOFS_INF_EXPIRING) { | ||
138 | spin_unlock(&sbi->fs_lock); | ||
139 | |||
137 | DPRINTK("waiting for expire %p name=%.*s", | 140 | DPRINTK("waiting for expire %p name=%.*s", |
138 | dentry, dentry->d_name.len, dentry->d_name.name); | 141 | dentry, dentry->d_name.len, dentry->d_name.name); |
139 | 142 | ||
@@ -149,8 +152,11 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags) | |||
149 | status = d_invalidate(dentry); | 152 | status = d_invalidate(dentry); |
150 | if (status != -EBUSY) | 153 | if (status != -EBUSY) |
151 | return -EAGAIN; | 154 | return -EAGAIN; |
152 | } | ||
153 | 155 | ||
156 | goto cont; | ||
157 | } | ||
158 | spin_unlock(&sbi->fs_lock); | ||
159 | cont: | ||
154 | DPRINTK("dentry=%p %.*s ino=%p", | 160 | DPRINTK("dentry=%p %.*s ino=%p", |
155 | dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); | 161 | dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); |
156 | 162 | ||
@@ -229,15 +235,21 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd) | |||
229 | goto done; | 235 | goto done; |
230 | 236 | ||
231 | /* If an expire request is pending wait for it. */ | 237 | /* If an expire request is pending wait for it. */ |
232 | if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { | 238 | spin_lock(&sbi->fs_lock); |
239 | if (ino->flags & AUTOFS_INF_EXPIRING) { | ||
240 | spin_unlock(&sbi->fs_lock); | ||
241 | |||
233 | DPRINTK("waiting for active request %p name=%.*s", | 242 | DPRINTK("waiting for active request %p name=%.*s", |
234 | dentry, dentry->d_name.len, dentry->d_name.name); | 243 | dentry, dentry->d_name.len, dentry->d_name.name); |
235 | 244 | ||
236 | status = autofs4_wait(sbi, dentry, NFY_NONE); | 245 | status = autofs4_wait(sbi, dentry, NFY_NONE); |
237 | 246 | ||
238 | DPRINTK("request done status=%d", status); | 247 | DPRINTK("request done status=%d", status); |
239 | } | ||
240 | 248 | ||
249 | goto cont; | ||
250 | } | ||
251 | spin_unlock(&sbi->fs_lock); | ||
252 | cont: | ||
241 | /* | 253 | /* |
242 | * If the dentry contains directories then it is an | 254 | * If the dentry contains directories then it is an |
243 | * autofs multi-mount with no root mount offset. So | 255 | * autofs multi-mount with no root mount offset. So |
@@ -292,8 +304,11 @@ static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd) | |||
292 | int status = 1; | 304 | int status = 1; |
293 | 305 | ||
294 | /* Pending dentry */ | 306 | /* Pending dentry */ |
307 | spin_lock(&sbi->fs_lock); | ||
295 | if (autofs4_ispending(dentry)) { | 308 | if (autofs4_ispending(dentry)) { |
296 | /* The daemon never causes a mount to trigger */ | 309 | /* The daemon never causes a mount to trigger */ |
310 | spin_unlock(&sbi->fs_lock); | ||
311 | |||
297 | if (oz_mode) | 312 | if (oz_mode) |
298 | return 1; | 313 | return 1; |
299 | 314 | ||
@@ -316,6 +331,7 @@ static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd) | |||
316 | 331 | ||
317 | return status; | 332 | return status; |
318 | } | 333 | } |
334 | spin_unlock(&sbi->fs_lock); | ||
319 | 335 | ||
320 | /* Negative dentry.. invalidate if "old" */ | 336 | /* Negative dentry.. invalidate if "old" */ |
321 | if (dentry->d_inode == NULL) | 337 | if (dentry->d_inode == NULL) |
@@ -329,6 +345,7 @@ static int autofs4_revalidate(struct dentry *dentry, struct nameidata *nd) | |||
329 | DPRINTK("dentry=%p %.*s, emptydir", | 345 | DPRINTK("dentry=%p %.*s, emptydir", |
330 | dentry, dentry->d_name.len, dentry->d_name.name); | 346 | dentry, dentry->d_name.len, dentry->d_name.name); |
331 | spin_unlock(&dcache_lock); | 347 | spin_unlock(&dcache_lock); |
348 | |||
332 | /* The daemon never causes a mount to trigger */ | 349 | /* The daemon never causes a mount to trigger */ |
333 | if (oz_mode) | 350 | if (oz_mode) |
334 | return 1; | 351 | return 1; |
@@ -521,13 +538,18 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s | |||
521 | * so it must have been successful, so just wait for it. | 538 | * so it must have been successful, so just wait for it. |
522 | */ | 539 | */ |
523 | ino = autofs4_dentry_ino(expiring); | 540 | ino = autofs4_dentry_ino(expiring); |
524 | while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { | 541 | spin_lock(&sbi->fs_lock); |
542 | if (ino->flags & AUTOFS_INF_EXPIRING) { | ||
543 | spin_unlock(&sbi->fs_lock); | ||
525 | DPRINTK("wait for incomplete expire %p name=%.*s", | 544 | DPRINTK("wait for incomplete expire %p name=%.*s", |
526 | expiring, expiring->d_name.len, | 545 | expiring, expiring->d_name.len, |
527 | expiring->d_name.name); | 546 | expiring->d_name.name); |
528 | autofs4_wait(sbi, expiring, NFY_NONE); | 547 | autofs4_wait(sbi, expiring, NFY_NONE); |
529 | DPRINTK("request completed"); | 548 | DPRINTK("request completed"); |
549 | goto cont; | ||
530 | } | 550 | } |
551 | spin_unlock(&sbi->fs_lock); | ||
552 | cont: | ||
531 | spin_lock(&sbi->lookup_lock); | 553 | spin_lock(&sbi->lookup_lock); |
532 | if (!list_empty(&ino->expiring)) | 554 | if (!list_empty(&ino->expiring)) |
533 | list_del_init(&ino->expiring); | 555 | list_del_init(&ino->expiring); |