From 771f2f4192e479781e367cbba7ccd3b8bda5dd55 Mon Sep 17 00:00:00 2001
From: David Sterba <dsterba@suse.com>
Date: Thu, 2 Jun 2016 13:50:44 +0200
References: bsc#951844 bsc#1024015
Patch-mainline: never, ugly
Subject: [PATCH] btrfs: serialize subvolume mounts with potentially
mismatching rw flags
Racing subvolume mounts with mixed ro/rw flags can fail if the mount and
remount are not done atomically.
Signed-off-by: David Sterba <dsterba@suse.com>
---
fs/btrfs/super.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1284,6 +1284,7 @@ static struct dentry *mount_subvol(const
struct vfsmount *mnt = NULL;
char *newargs;
int ret;
+ static DEFINE_MUTEX(subvol_lock);
newargs = setup_root_args(data);
if (!newargs) {
@@ -1291,6 +1292,24 @@ static struct dentry *mount_subvol(const
goto out;
}
+ /*
+ * Protect against racing mounts of subvolumes with different RO/RW
+ * flags. The first vfs_kern_mount could fail with -EBUSY if the rw
+ * flags do not match with the first and the currently mounted
+ * subvolume.
+ *
+ * To resolve that, we adjust the rw flags and do remount. If another
+ * mounts goes through the same path and hits the window between the
+ * adjusted vfs_kern_mount and btrfs_remount, it will fail because of
+ * the ro/rw mismatch in btrfs_mount.
+ *
+ * If the mounts do not race and are serialized externally, everything
+ * works fine. The function-local mutex enforces the serialization but
+ * is otherwise only an ugly workaround due to lack of better
+ * solutions.
+ */
+ mutex_lock(&subvol_lock);
+
mnt = vfs_kern_mount(&btrfs_fs_type, flags, device_name, newargs);
if (PTR_ERR_OR_ZERO(mnt) == -EBUSY) {
if (flags & MS_RDONLY) {
@@ -1302,6 +1321,7 @@ static struct dentry *mount_subvol(const
if (IS_ERR(mnt)) {
root = ERR_CAST(mnt);
mnt = NULL;
+ mutex_unlock(&subvol_lock);
goto out;
}
@@ -1310,10 +1330,13 @@ static struct dentry *mount_subvol(const
up_write(&mnt->mnt_sb->s_umount);
if (ret < 0) {
root = ERR_PTR(ret);
+ mutex_unlock(&subvol_lock);
goto out;
}
}
}
+ mutex_unlock(&subvol_lock);
+
if (IS_ERR(mnt)) {
root = ERR_CAST(mnt);
mnt = NULL;