diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2011-11-21 06:11:31 -0500 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2012-01-06 23:20:12 -0500 |
commit | 4ed5e82fe77f4147cf386327c9a63a2dd7eff518 (patch) | |
tree | f4eaeefaf5d293014457892ac31f878eece07331 | |
parent | 39f7c4db1d2d9e2e2a90abdf34811783089d217d (diff) |
vfs: protect remounting superblock read-only
Currently remouting superblock read-only is racy in a major way.
With the per mount read-only infrastructure it is now possible to
prevent most races, which this patch attempts.
Before starting the remount read-only, iterate through all mounts
belonging to the superblock and if none of them have any pending
writes, set sb->s_readonly_remount. This indicates that remount is in
progress and no further write requests are allowed. If the remount
succeeds set MS_RDONLY and reset s_readonly_remount.
If the remounting is unsuccessful just reset s_readonly_remount.
This can result in transient EROFS errors, despite the fact the
remount failed. Unfortunately hodling off writes is difficult as
remount itself may touch the filesystem (e.g. through load_nls())
which would deadlock.
A later patch deals with delayed writes due to nlink going to zero.
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Tested-by: Toshiyuki Okajima <toshi.okajima@jp.fujitsu.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | fs/internal.h | 1 | ||||
-rw-r--r-- | fs/namespace.c | 40 | ||||
-rw-r--r-- | fs/super.c | 22 | ||||
-rw-r--r-- | include/linux/fs.h | 3 |
4 files changed, 61 insertions, 5 deletions
diff --git a/fs/internal.h b/fs/internal.h index 2523a4029452..9962c59ba280 100644 --- a/fs/internal.h +++ b/fs/internal.h | |||
@@ -52,6 +52,7 @@ extern int finish_automount(struct vfsmount *, struct path *); | |||
52 | 52 | ||
53 | extern void mnt_make_longterm(struct vfsmount *); | 53 | extern void mnt_make_longterm(struct vfsmount *); |
54 | extern void mnt_make_shortterm(struct vfsmount *); | 54 | extern void mnt_make_shortterm(struct vfsmount *); |
55 | extern int sb_prepare_remount_readonly(struct super_block *); | ||
55 | 56 | ||
56 | extern void __init mnt_init(void); | 57 | extern void __init mnt_init(void); |
57 | 58 | ||
diff --git a/fs/namespace.c b/fs/namespace.c index 145217b088d1..98ebc78b21ab 100644 --- a/fs/namespace.c +++ b/fs/namespace.c | |||
@@ -273,6 +273,15 @@ static unsigned int mnt_get_writers(struct mount *mnt) | |||
273 | #endif | 273 | #endif |
274 | } | 274 | } |
275 | 275 | ||
276 | static int mnt_is_readonly(struct vfsmount *mnt) | ||
277 | { | ||
278 | if (mnt->mnt_sb->s_readonly_remount) | ||
279 | return 1; | ||
280 | /* Order wrt setting s_flags/s_readonly_remount in do_remount() */ | ||
281 | smp_rmb(); | ||
282 | return __mnt_is_readonly(mnt); | ||
283 | } | ||
284 | |||
276 | /* | 285 | /* |
277 | * Most r/o checks on a fs are for operations that take | 286 | * Most r/o checks on a fs are for operations that take |
278 | * discrete amounts of time, like a write() or unlink(). | 287 | * discrete amounts of time, like a write() or unlink(). |
@@ -312,7 +321,7 @@ int mnt_want_write(struct vfsmount *m) | |||
312 | * MNT_WRITE_HOLD is cleared. | 321 | * MNT_WRITE_HOLD is cleared. |
313 | */ | 322 | */ |
314 | smp_rmb(); | 323 | smp_rmb(); |
315 | if (__mnt_is_readonly(m)) { | 324 | if (mnt_is_readonly(m)) { |
316 | mnt_dec_writers(mnt); | 325 | mnt_dec_writers(mnt); |
317 | ret = -EROFS; | 326 | ret = -EROFS; |
318 | goto out; | 327 | goto out; |
@@ -435,6 +444,35 @@ static void __mnt_unmake_readonly(struct mount *mnt) | |||
435 | br_write_unlock(vfsmount_lock); | 444 | br_write_unlock(vfsmount_lock); |
436 | } | 445 | } |
437 | 446 | ||
447 | int sb_prepare_remount_readonly(struct super_block *sb) | ||
448 | { | ||
449 | struct mount *mnt; | ||
450 | int err = 0; | ||
451 | |||
452 | br_write_lock(vfsmount_lock); | ||
453 | list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) { | ||
454 | if (!(mnt->mnt.mnt_flags & MNT_READONLY)) { | ||
455 | mnt->mnt.mnt_flags |= MNT_WRITE_HOLD; | ||
456 | smp_mb(); | ||
457 | if (mnt_get_writers(mnt) > 0) { | ||
458 | err = -EBUSY; | ||
459 | break; | ||
460 | } | ||
461 | } | ||
462 | } | ||
463 | if (!err) { | ||
464 | sb->s_readonly_remount = 1; | ||
465 | smp_wmb(); | ||
466 | } | ||
467 | list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) { | ||
468 | if (mnt->mnt.mnt_flags & MNT_WRITE_HOLD) | ||
469 | mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD; | ||
470 | } | ||
471 | br_write_unlock(vfsmount_lock); | ||
472 | |||
473 | return err; | ||
474 | } | ||
475 | |||
438 | static void free_vfsmnt(struct mount *mnt) | 476 | static void free_vfsmnt(struct mount *mnt) |
439 | { | 477 | { |
440 | kfree(mnt->mnt_devname); | 478 | kfree(mnt->mnt_devname); |
diff --git a/fs/super.c b/fs/super.c index 993ca8f128d6..6acc02237e3e 100644 --- a/fs/super.c +++ b/fs/super.c | |||
@@ -723,23 +723,33 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force) | |||
723 | /* If we are remounting RDONLY and current sb is read/write, | 723 | /* If we are remounting RDONLY and current sb is read/write, |
724 | make sure there are no rw files opened */ | 724 | make sure there are no rw files opened */ |
725 | if (remount_ro) { | 725 | if (remount_ro) { |
726 | if (force) | 726 | if (force) { |
727 | mark_files_ro(sb); | 727 | mark_files_ro(sb); |
728 | else if (!fs_may_remount_ro(sb)) | 728 | } else { |
729 | return -EBUSY; | 729 | retval = sb_prepare_remount_readonly(sb); |
730 | if (retval) | ||
731 | return retval; | ||
732 | |||
733 | retval = -EBUSY; | ||
734 | if (!fs_may_remount_ro(sb)) | ||
735 | goto cancel_readonly; | ||
736 | } | ||
730 | } | 737 | } |
731 | 738 | ||
732 | if (sb->s_op->remount_fs) { | 739 | if (sb->s_op->remount_fs) { |
733 | retval = sb->s_op->remount_fs(sb, &flags, data); | 740 | retval = sb->s_op->remount_fs(sb, &flags, data); |
734 | if (retval) { | 741 | if (retval) { |
735 | if (!force) | 742 | if (!force) |
736 | return retval; | 743 | goto cancel_readonly; |
737 | /* If forced remount, go ahead despite any errors */ | 744 | /* If forced remount, go ahead despite any errors */ |
738 | WARN(1, "forced remount of a %s fs returned %i\n", | 745 | WARN(1, "forced remount of a %s fs returned %i\n", |
739 | sb->s_type->name, retval); | 746 | sb->s_type->name, retval); |
740 | } | 747 | } |
741 | } | 748 | } |
742 | sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK); | 749 | sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK); |
750 | /* Needs to be ordered wrt mnt_is_readonly() */ | ||
751 | smp_wmb(); | ||
752 | sb->s_readonly_remount = 0; | ||
743 | 753 | ||
744 | /* | 754 | /* |
745 | * Some filesystems modify their metadata via some other path than the | 755 | * Some filesystems modify their metadata via some other path than the |
@@ -752,6 +762,10 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force) | |||
752 | if (remount_ro && sb->s_bdev) | 762 | if (remount_ro && sb->s_bdev) |
753 | invalidate_bdev(sb->s_bdev); | 763 | invalidate_bdev(sb->s_bdev); |
754 | return 0; | 764 | return 0; |
765 | |||
766 | cancel_readonly: | ||
767 | sb->s_readonly_remount = 0; | ||
768 | return retval; | ||
755 | } | 769 | } |
756 | 770 | ||
757 | static void do_emergency_remount(struct work_struct *work) | 771 | static void do_emergency_remount(struct work_struct *work) |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 03385acd71e8..7b8a681b1ef4 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -1482,6 +1482,9 @@ struct super_block { | |||
1482 | int cleancache_poolid; | 1482 | int cleancache_poolid; |
1483 | 1483 | ||
1484 | struct shrinker s_shrink; /* per-sb shrinker handle */ | 1484 | struct shrinker s_shrink; /* per-sb shrinker handle */ |
1485 | |||
1486 | /* Being remounted read-only */ | ||
1487 | int s_readonly_remount; | ||
1485 | }; | 1488 | }; |
1486 | 1489 | ||
1487 | /* superblock cache pruning functions */ | 1490 | /* superblock cache pruning functions */ |