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 | |
| 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>
| -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); |
