vfs: protect remounting superblock read-only
[linux-2.6-block.git] / fs / namespace.c
index 145217b088d1e82f27eeb07139533b9644295b93..98ebc78b21ab7cc1678f8f40da3b9343e22d5111 100644 (file)
@@ -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);