diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2011-11-21 12:11:31 +0100 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2012-01-06 23:20:12 -0500 |
commit | 4ed5e82fe77f4147cf386327c9a63a2dd7eff518 (patch) | |
tree | f4eaeefaf5d293014457892ac31f878eece07331 /fs/namespace.c | |
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>
Diffstat (limited to 'fs/namespace.c')
-rw-r--r-- | fs/namespace.c | 40 |
1 files changed, 39 insertions, 1 deletions
diff --git a/fs/namespace.c b/fs/namespace.c index 145217b088d..98ebc78b21a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -273,6 +273,15 @@ static unsigned int mnt_get_writers(struct mount *mnt) #endif } +static int mnt_is_readonly(struct vfsmount *mnt) +{ + if (mnt->mnt_sb->s_readonly_remount) + return 1; + /* Order wrt setting s_flags/s_readonly_remount in do_remount() */ + smp_rmb(); + return __mnt_is_readonly(mnt); +} + /* * Most r/o checks on a fs are for operations that take * discrete amounts of time, like a write() or unlink(). @@ -312,7 +321,7 @@ int mnt_want_write(struct vfsmount *m) * MNT_WRITE_HOLD is cleared. */ smp_rmb(); - if (__mnt_is_readonly(m)) { + if (mnt_is_readonly(m)) { mnt_dec_writers(mnt); ret = -EROFS; goto out; @@ -435,6 +444,35 @@ static void __mnt_unmake_readonly(struct mount *mnt) br_write_unlock(vfsmount_lock); } +int sb_prepare_remount_readonly(struct super_block *sb) +{ + struct mount *mnt; + int err = 0; + + br_write_lock(vfsmount_lock); + list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) { + if (!(mnt->mnt.mnt_flags & MNT_READONLY)) { + mnt->mnt.mnt_flags |= MNT_WRITE_HOLD; + smp_mb(); + if (mnt_get_writers(mnt) > 0) { + err = -EBUSY; + break; + } + } + } + if (!err) { + sb->s_readonly_remount = 1; + smp_wmb(); + } + list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) { + if (mnt->mnt.mnt_flags & MNT_WRITE_HOLD) + mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD; + } + br_write_unlock(vfsmount_lock); + + return err; +} + static void free_vfsmnt(struct mount *mnt) { kfree(mnt->mnt_devname); |