Blob Blame History Raw
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;