Btrfs: ensure send always works on roots without orphans
authorFilipe Manana <fdmanana@suse.com>
Tue, 21 Oct 2014 10:11:41 +0000 (11:11 +0100)
committerChris Mason <clm@fb.com>
Tue, 25 Nov 2014 15:41:23 +0000 (07:41 -0800)
Move the logic from the snapshot creation ioctl into send. This avoids
doing the transaction commit if send isn't used, and ensures that if
a crash/reboot happens after the transaction commit that created the
snapshot and before the transaction commit that switched the commit
root, send will not get a commit root that differs from the main root
(that has orphan items).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Chris Mason <clm@fb.com>
fs/btrfs/ioctl.c
fs/btrfs/send.c

index 4399f0c3a4ce1c6a08cfa81f2720f27a7138eea7..3abc068c55437140fdc54d9a959b140aaa7ed04a 100644 (file)
@@ -717,35 +717,6 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
        if (ret)
                goto fail;
 
-       /*
-        * If orphan cleanup did remove any orphans, it means the tree was
-        * modified and therefore the commit root is not the same as the
-        * current root anymore. This is a problem, because send uses the
-        * commit root and therefore can see inode items that don't exist
-        * in the current root anymore, and for example make calls to
-        * btrfs_iget, which will do tree lookups based on the current root
-        * and not on the commit root. Those lookups will fail, returning a
-        * -ESTALE error, and making send fail with that error. So make sure
-        * a send does not see any orphans we have just removed, and that it
-        * will see the same inodes regardless of whether a transaction
-        * commit happened before it started (meaning that the commit root
-        * will be the same as the current root) or not.
-        */
-       if (readonly && pending_snapshot->snap->node !=
-           pending_snapshot->snap->commit_root) {
-               trans = btrfs_join_transaction(pending_snapshot->snap);
-               if (IS_ERR(trans) && PTR_ERR(trans) != -ENOENT) {
-                       ret = PTR_ERR(trans);
-                       goto fail;
-               }
-               if (!IS_ERR(trans)) {
-                       ret = btrfs_commit_transaction(trans,
-                                                      pending_snapshot->snap);
-                       if (ret)
-                               goto fail;
-               }
-       }
-
        inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry);
        if (IS_ERR(inode)) {
                ret = PTR_ERR(inode);
index 874828dd0a8627d8c57097d17303f6db68f76775..804432dbc351d8b73064724cbfbc08237d522298 100644 (file)
@@ -5507,6 +5507,51 @@ out:
        return ret;
 }
 
+/*
+ * If orphan cleanup did remove any orphans from a root, it means the tree
+ * was modified and therefore the commit root is not the same as the current
+ * root anymore. This is a problem, because send uses the commit root and
+ * therefore can see inode items that don't exist in the current root anymore,
+ * and for example make calls to btrfs_iget, which will do tree lookups based
+ * on the current root and not on the commit root. Those lookups will fail,
+ * returning a -ESTALE error, and making send fail with that error. So make
+ * sure a send does not see any orphans we have just removed, and that it will
+ * see the same inodes regardless of whether a transaction commit happened
+ * before it started (meaning that the commit root will be the same as the
+ * current root) or not.
+ */
+static int ensure_commit_roots_uptodate(struct send_ctx *sctx)
+{
+       int i;
+       struct btrfs_trans_handle *trans = NULL;
+
+again:
+       if (sctx->parent_root &&
+           sctx->parent_root->node != sctx->parent_root->commit_root)
+               goto commit_trans;
+
+       for (i = 0; i < sctx->clone_roots_cnt; i++)
+               if (sctx->clone_roots[i].root->node !=
+                   sctx->clone_roots[i].root->commit_root)
+                       goto commit_trans;
+
+       if (trans)
+               return btrfs_end_transaction(trans, sctx->send_root);
+
+       return 0;
+
+commit_trans:
+       /* Use any root, all fs roots will get their commit roots updated. */
+       if (!trans) {
+               trans = btrfs_join_transaction(sctx->send_root);
+               if (IS_ERR(trans))
+                       return PTR_ERR(trans);
+               goto again;
+       }
+
+       return btrfs_commit_transaction(trans, sctx->send_root);
+}
+
 static void btrfs_root_dec_send_in_progress(struct btrfs_root* root)
 {
        spin_lock(&root->root_item_lock);
@@ -5728,6 +5773,10 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)
                        NULL);
        sort_clone_roots = 1;
 
+       ret = ensure_commit_roots_uptodate(sctx);
+       if (ret)
+               goto out;
+
        current->journal_info = BTRFS_SEND_TRANS_STUB;
        ret = send_subvol(sctx);
        current->journal_info = NULL;