diff options
-rw-r--r-- | fs/mount.h | 2 | ||||
-rw-r--r-- | fs/namespace.c | 2 | ||||
-rw-r--r-- | fs/pnode.c | 144 |
3 files changed, 88 insertions, 60 deletions
diff --git a/fs/mount.h b/fs/mount.h index ede5a1d5cf99..de45d9e76748 100644 --- a/fs/mount.h +++ b/fs/mount.h | |||
@@ -58,7 +58,7 @@ struct mount { | |||
58 | struct mnt_namespace *mnt_ns; /* containing namespace */ | 58 | struct mnt_namespace *mnt_ns; /* containing namespace */ |
59 | struct mountpoint *mnt_mp; /* where is it mounted */ | 59 | struct mountpoint *mnt_mp; /* where is it mounted */ |
60 | struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ | 60 | struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ |
61 | struct list_head mnt_reparent; /* reparent list entry */ | 61 | struct list_head mnt_umounting; /* list entry for umount propagation */ |
62 | #ifdef CONFIG_FSNOTIFY | 62 | #ifdef CONFIG_FSNOTIFY |
63 | struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks; | 63 | struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks; |
64 | __u32 mnt_fsnotify_mask; | 64 | __u32 mnt_fsnotify_mask; |
diff --git a/fs/namespace.c b/fs/namespace.c index 51e49866e1fe..5e3dcbeb1de5 100644 --- a/fs/namespace.c +++ b/fs/namespace.c | |||
@@ -236,7 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name) | |||
236 | INIT_LIST_HEAD(&mnt->mnt_slave_list); | 236 | INIT_LIST_HEAD(&mnt->mnt_slave_list); |
237 | INIT_LIST_HEAD(&mnt->mnt_slave); | 237 | INIT_LIST_HEAD(&mnt->mnt_slave); |
238 | INIT_HLIST_NODE(&mnt->mnt_mp_list); | 238 | INIT_HLIST_NODE(&mnt->mnt_mp_list); |
239 | INIT_LIST_HEAD(&mnt->mnt_reparent); | 239 | INIT_LIST_HEAD(&mnt->mnt_umounting); |
240 | init_fs_pin(&mnt->mnt_umount, drop_mountpoint); | 240 | init_fs_pin(&mnt->mnt_umount, drop_mountpoint); |
241 | } | 241 | } |
242 | return mnt; | 242 | return mnt; |
diff --git a/fs/pnode.c b/fs/pnode.c index 52aca0a118ff..fbaca7df2eb0 100644 --- a/fs/pnode.c +++ b/fs/pnode.c | |||
@@ -413,86 +413,95 @@ void propagate_mount_unlock(struct mount *mnt) | |||
413 | } | 413 | } |
414 | } | 414 | } |
415 | 415 | ||
416 | /* | 416 | static void umount_one(struct mount *mnt, struct list_head *to_umount) |
417 | * Mark all mounts that the MNT_LOCKED logic will allow to be unmounted. | ||
418 | */ | ||
419 | static void mark_umount_candidates(struct mount *mnt) | ||
420 | { | 417 | { |
421 | struct mount *parent = mnt->mnt_parent; | 418 | CLEAR_MNT_MARK(mnt); |
422 | struct mount *m; | 419 | mnt->mnt.mnt_flags |= MNT_UMOUNT; |
423 | 420 | list_del_init(&mnt->mnt_child); | |
424 | BUG_ON(parent == mnt); | 421 | list_del_init(&mnt->mnt_umounting); |
425 | 422 | list_move_tail(&mnt->mnt_list, to_umount); | |
426 | for (m = propagation_next(parent, parent); m; | ||
427 | m = propagation_next(m, parent)) { | ||
428 | struct mount *child = __lookup_mnt(&m->mnt, | ||
429 | mnt->mnt_mountpoint); | ||
430 | if (!child || (child->mnt.mnt_flags & MNT_UMOUNT)) | ||
431 | continue; | ||
432 | if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) { | ||
433 | SET_MNT_MARK(child); | ||
434 | } | ||
435 | } | ||
436 | } | 423 | } |
437 | 424 | ||
438 | /* | 425 | /* |
439 | * NOTE: unmounting 'mnt' naturally propagates to all other mounts its | 426 | * NOTE: unmounting 'mnt' naturally propagates to all other mounts its |
440 | * parent propagates to. | 427 | * parent propagates to. |
441 | */ | 428 | */ |
442 | static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent) | 429 | static bool __propagate_umount(struct mount *mnt, |
430 | struct list_head *to_umount, | ||
431 | struct list_head *to_restore) | ||
443 | { | 432 | { |
444 | struct mount *parent = mnt->mnt_parent; | 433 | bool progress = false; |
445 | struct mount *m; | 434 | struct mount *child; |
446 | 435 | ||
447 | BUG_ON(parent == mnt); | 436 | /* |
437 | * The state of the parent won't change if this mount is | ||
438 | * already unmounted or marked as without children. | ||
439 | */ | ||
440 | if (mnt->mnt.mnt_flags & (MNT_UMOUNT | MNT_MARKED)) | ||
441 | goto out; | ||
448 | 442 | ||
449 | for (m = propagation_next(parent, parent); m; | 443 | /* Verify topper is the only grandchild that has not been |
450 | m = propagation_next(m, parent)) { | 444 | * speculatively unmounted. |
451 | struct mount *topper; | 445 | */ |
452 | struct mount *child = __lookup_mnt(&m->mnt, | 446 | list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { |
453 | mnt->mnt_mountpoint); | 447 | if (child->mnt_mountpoint == mnt->mnt.mnt_root) |
454 | /* | ||
455 | * umount the child only if the child has no children | ||
456 | * and the child is marked safe to unmount. | ||
457 | */ | ||
458 | if (!child || !IS_MNT_MARKED(child)) | ||
459 | continue; | 448 | continue; |
460 | CLEAR_MNT_MARK(child); | 449 | if (!list_empty(&child->mnt_umounting) && IS_MNT_MARKED(child)) |
450 | continue; | ||
451 | /* Found a mounted child */ | ||
452 | goto children; | ||
453 | } | ||
461 | 454 | ||
462 | /* If there is exactly one mount covering all of child | 455 | /* Mark mounts that can be unmounted if not locked */ |
463 | * replace child with that mount. | 456 | SET_MNT_MARK(mnt); |
464 | */ | 457 | progress = true; |
465 | topper = find_topper(child); | ||
466 | if (topper) | ||
467 | list_add_tail(&topper->mnt_reparent, to_reparent); | ||
468 | 458 | ||
469 | if (topper || list_empty(&child->mnt_mounts)) { | 459 | /* If a mount is without children and not locked umount it. */ |
470 | list_del_init(&child->mnt_child); | 460 | if (!IS_MNT_LOCKED(mnt)) { |
471 | list_del_init(&child->mnt_reparent); | 461 | umount_one(mnt, to_umount); |
472 | child->mnt.mnt_flags |= MNT_UMOUNT; | 462 | } else { |
473 | list_move_tail(&child->mnt_list, &mnt->mnt_list); | 463 | children: |
464 | list_move_tail(&mnt->mnt_umounting, to_restore); | ||
465 | } | ||
466 | out: | ||
467 | return progress; | ||
468 | } | ||
469 | |||
470 | static void umount_list(struct list_head *to_umount, | ||
471 | struct list_head *to_restore) | ||
472 | { | ||
473 | struct mount *mnt, *child, *tmp; | ||
474 | list_for_each_entry(mnt, to_umount, mnt_list) { | ||
475 | list_for_each_entry_safe(child, tmp, &mnt->mnt_mounts, mnt_child) { | ||
476 | /* topper? */ | ||
477 | if (child->mnt_mountpoint == mnt->mnt.mnt_root) | ||
478 | list_move_tail(&child->mnt_umounting, to_restore); | ||
479 | else | ||
480 | umount_one(child, to_umount); | ||
474 | } | 481 | } |
475 | } | 482 | } |
476 | } | 483 | } |
477 | 484 | ||
478 | static void reparent_mounts(struct list_head *to_reparent) | 485 | static void restore_mounts(struct list_head *to_restore) |
479 | { | 486 | { |
480 | while (!list_empty(to_reparent)) { | 487 | /* Restore mounts to a clean working state */ |
488 | while (!list_empty(to_restore)) { | ||
481 | struct mount *mnt, *parent; | 489 | struct mount *mnt, *parent; |
482 | struct mountpoint *mp; | 490 | struct mountpoint *mp; |
483 | 491 | ||
484 | mnt = list_first_entry(to_reparent, struct mount, mnt_reparent); | 492 | mnt = list_first_entry(to_restore, struct mount, mnt_umounting); |
485 | list_del_init(&mnt->mnt_reparent); | 493 | CLEAR_MNT_MARK(mnt); |
494 | list_del_init(&mnt->mnt_umounting); | ||
486 | 495 | ||
487 | /* Where should this mount be reparented to? */ | 496 | /* Should this mount be reparented? */ |
488 | mp = mnt->mnt_mp; | 497 | mp = mnt->mnt_mp; |
489 | parent = mnt->mnt_parent; | 498 | parent = mnt->mnt_parent; |
490 | while (parent->mnt.mnt_flags & MNT_UMOUNT) { | 499 | while (parent->mnt.mnt_flags & MNT_UMOUNT) { |
491 | mp = parent->mnt_mp; | 500 | mp = parent->mnt_mp; |
492 | parent = parent->mnt_parent; | 501 | parent = parent->mnt_parent; |
493 | } | 502 | } |
494 | 503 | if (parent != mnt->mnt_parent) | |
495 | mnt_change_mountpoint(parent, mp, mnt); | 504 | mnt_change_mountpoint(parent, mp, mnt); |
496 | } | 505 | } |
497 | } | 506 | } |
498 | 507 | ||
@@ -506,15 +515,34 @@ static void reparent_mounts(struct list_head *to_reparent) | |||
506 | int propagate_umount(struct list_head *list) | 515 | int propagate_umount(struct list_head *list) |
507 | { | 516 | { |
508 | struct mount *mnt; | 517 | struct mount *mnt; |
509 | LIST_HEAD(to_reparent); | 518 | LIST_HEAD(to_restore); |
519 | LIST_HEAD(to_umount); | ||
510 | 520 | ||
511 | list_for_each_entry_reverse(mnt, list, mnt_list) | 521 | list_for_each_entry(mnt, list, mnt_list) { |
512 | mark_umount_candidates(mnt); | 522 | struct mount *parent = mnt->mnt_parent; |
523 | struct mount *m; | ||
513 | 524 | ||
514 | list_for_each_entry(mnt, list, mnt_list) | 525 | for (m = propagation_next(parent, parent); m; |
515 | __propagate_umount(mnt, &to_reparent); | 526 | m = propagation_next(m, parent)) { |
527 | struct mount *child = __lookup_mnt(&m->mnt, | ||
528 | mnt->mnt_mountpoint); | ||
529 | if (!child) | ||
530 | continue; | ||
531 | |||
532 | /* Check the child and parents while progress is made */ | ||
533 | while (__propagate_umount(child, | ||
534 | &to_umount, &to_restore)) { | ||
535 | /* Is the parent a umount candidate? */ | ||
536 | child = child->mnt_parent; | ||
537 | if (list_empty(&child->mnt_umounting)) | ||
538 | break; | ||
539 | } | ||
540 | } | ||
541 | } | ||
516 | 542 | ||
517 | reparent_mounts(&to_reparent); | 543 | umount_list(&to_umount, &to_restore); |
544 | restore_mounts(&to_restore); | ||
545 | list_splice_tail(&to_umount, list); | ||
518 | 546 | ||
519 | return 0; | 547 | return 0; |
520 | } | 548 | } |