Commit 4ed5e82f authored by Miklos Szeredi's avatar Miklos Szeredi Committed by Al Viro

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: default avatarMiklos Szeredi <mszeredi@suse.cz>
Tested-by: default avatarToshiyuki Okajima <toshi.okajima@jp.fujitsu.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 39f7c4db
...@@ -52,6 +52,7 @@ extern int finish_automount(struct vfsmount *, struct path *); ...@@ -52,6 +52,7 @@ extern int finish_automount(struct vfsmount *, struct path *);
extern void mnt_make_longterm(struct vfsmount *); extern void mnt_make_longterm(struct vfsmount *);
extern void mnt_make_shortterm(struct vfsmount *); extern void mnt_make_shortterm(struct vfsmount *);
extern int sb_prepare_remount_readonly(struct super_block *);
extern void __init mnt_init(void); extern void __init mnt_init(void);
......
...@@ -273,6 +273,15 @@ static unsigned int mnt_get_writers(struct mount *mnt) ...@@ -273,6 +273,15 @@ static unsigned int mnt_get_writers(struct mount *mnt)
#endif #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 * Most r/o checks on a fs are for operations that take
* discrete amounts of time, like a write() or unlink(). * discrete amounts of time, like a write() or unlink().
...@@ -312,7 +321,7 @@ int mnt_want_write(struct vfsmount *m) ...@@ -312,7 +321,7 @@ int mnt_want_write(struct vfsmount *m)
* MNT_WRITE_HOLD is cleared. * MNT_WRITE_HOLD is cleared.
*/ */
smp_rmb(); smp_rmb();
if (__mnt_is_readonly(m)) { if (mnt_is_readonly(m)) {
mnt_dec_writers(mnt); mnt_dec_writers(mnt);
ret = -EROFS; ret = -EROFS;
goto out; goto out;
...@@ -435,6 +444,35 @@ static void __mnt_unmake_readonly(struct mount *mnt) ...@@ -435,6 +444,35 @@ static void __mnt_unmake_readonly(struct mount *mnt)
br_write_unlock(vfsmount_lock); 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) static void free_vfsmnt(struct mount *mnt)
{ {
kfree(mnt->mnt_devname); kfree(mnt->mnt_devname);
......
...@@ -723,23 +723,33 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force) ...@@ -723,23 +723,33 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
/* If we are remounting RDONLY and current sb is read/write, /* If we are remounting RDONLY and current sb is read/write,
make sure there are no rw files opened */ make sure there are no rw files opened */
if (remount_ro) { if (remount_ro) {
if (force) if (force) {
mark_files_ro(sb); mark_files_ro(sb);
else if (!fs_may_remount_ro(sb)) } else {
return -EBUSY; retval = sb_prepare_remount_readonly(sb);
if (retval)
return retval;
retval = -EBUSY;
if (!fs_may_remount_ro(sb))
goto cancel_readonly;
}
} }
if (sb->s_op->remount_fs) { if (sb->s_op->remount_fs) {
retval = sb->s_op->remount_fs(sb, &flags, data); retval = sb->s_op->remount_fs(sb, &flags, data);
if (retval) { if (retval) {
if (!force) if (!force)
return retval; goto cancel_readonly;
/* If forced remount, go ahead despite any errors */ /* If forced remount, go ahead despite any errors */
WARN(1, "forced remount of a %s fs returned %i\n", WARN(1, "forced remount of a %s fs returned %i\n",
sb->s_type->name, retval); sb->s_type->name, retval);
} }
} }
sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK); sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK);
/* Needs to be ordered wrt mnt_is_readonly() */
smp_wmb();
sb->s_readonly_remount = 0;
/* /*
* Some filesystems modify their metadata via some other path than the * 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,6 +762,10 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
if (remount_ro && sb->s_bdev) if (remount_ro && sb->s_bdev)
invalidate_bdev(sb->s_bdev); invalidate_bdev(sb->s_bdev);
return 0; return 0;
cancel_readonly:
sb->s_readonly_remount = 0;
return retval;
} }
static void do_emergency_remount(struct work_struct *work) static void do_emergency_remount(struct work_struct *work)
......
...@@ -1482,6 +1482,9 @@ struct super_block { ...@@ -1482,6 +1482,9 @@ struct super_block {
int cleancache_poolid; int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */ struct shrinker s_shrink; /* per-sb shrinker handle */
/* Being remounted read-only */
int s_readonly_remount;
}; };
/* superblock cache pruning functions */ /* superblock cache pruning functions */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment