Btrfs: Add more synchronization before creating a snapshot

File data checksums are only done during writepage, so we have to make sure
all pages are written when the snapshot is taken.  This also adds some
locking so that new writes don't race in and add new dirty pages.

Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index cd75c90..03e1c4a 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -333,6 +333,7 @@
 	struct inode *inode;
 	struct kobject root_kobj;
 	struct completion kobj_unregister;
+	struct rw_semaphore snap_sem;
 	u64 objectid;
 	u64 last_trans;
 	u32 blocksize;
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index c25ef0a..6c953a0 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -300,6 +300,7 @@
 	memset(&root->defrag_progress, 0, sizeof(root->defrag_progress));
 	memset(&root->root_kobj, 0, sizeof(root->root_kobj));
 	init_completion(&root->kobj_unregister);
+	init_rwsem(&root->snap_sem);
 	root->defrag_running = 0;
 	root->defrag_level = 0;
 	root->root_key.objectid = objectid;
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index acef8cb..77f1950 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -160,6 +160,7 @@
 	num_blocks = (write_bytes + pos - start_pos + root->blocksize - 1) >>
 			inode->i_blkbits;
 
+	down_read(&BTRFS_I(inode)->root->snap_sem);
 	end_of_last_block = start_pos + (num_blocks << inode->i_blkbits) - 1;
 	lock_extent(em_tree, start_pos, end_of_last_block, GFP_NOFS);
 	mutex_lock(&root->fs_info->fs_mutex);
@@ -250,6 +251,7 @@
 	mutex_unlock(&root->fs_info->fs_mutex);
 	unlock_extent(em_tree, start_pos, end_of_last_block, GFP_NOFS);
 	free_extent_map(em);
+	up_read(&BTRFS_I(inode)->root->snap_sem);
 	return err;
 }
 
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 6b3e440..1ace6d1 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -686,6 +686,7 @@
 	if ((offset & (blocksize - 1)) == 0)
 		goto out;
 
+	down_read(&BTRFS_I(inode)->root->snap_sem);
 	ret = -ENOMEM;
 	page = grab_cache_page(mapping, index);
 	if (!page)
@@ -704,6 +705,7 @@
 
 	unlock_page(page);
 	page_cache_release(page);
+	up_read(&BTRFS_I(inode)->root->snap_sem);
 out:
 	return ret;
 }
@@ -1668,6 +1670,7 @@
 	int ret = -EINVAL;
 	u64 page_start;
 
+	down_read(&BTRFS_I(inode)->root->snap_sem);
 	lock_page(page);
 	wait_on_page_writeback(page);
 	size = i_size_read(inode);
@@ -1688,6 +1691,7 @@
 	ret = btrfs_cow_one_page(inode, page, end);
 
 out_unlock:
+	up_read(&BTRFS_I(inode)->root->snap_sem);
 	unlock_page(page);
 	return ret;
 }
@@ -1851,6 +1855,10 @@
 	if (!root->ref_cows)
 		return -EINVAL;
 
+	down_write(&root->snap_sem);
+	freeze_bdev(root->fs_info->sb->s_bdev);
+	thaw_bdev(root->fs_info->sb->s_bdev, root->fs_info->sb);
+
 	mutex_lock(&root->fs_info->fs_mutex);
 	trans = btrfs_start_transaction(root, 1);
 	BUG_ON(!trans);
@@ -1894,12 +1902,12 @@
 	ret = btrfs_inc_root_ref(trans, root);
 	if (ret)
 		goto fail;
-
 fail:
 	err = btrfs_commit_transaction(trans, root);
 	if (err && !ret)
 		ret = err;
 	mutex_unlock(&root->fs_info->fs_mutex);
+	up_write(&root->snap_sem);
 	btrfs_btree_balance_dirty(root);
 	return ret;
 }