Skip to content

Commit e5fa8f8

Browse files
fdmananamasoncl
authored andcommitted
Btrfs: ensure send always works on roots without orphans
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 <[email protected]> Signed-off-by: Chris Mason <[email protected]>
1 parent 758eb51 commit e5fa8f8

File tree

2 files changed

+49
-29
lines changed

2 files changed

+49
-29
lines changed

fs/btrfs/ioctl.c

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -717,35 +717,6 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
717717
if (ret)
718718
goto fail;
719719

720-
/*
721-
* If orphan cleanup did remove any orphans, it means the tree was
722-
* modified and therefore the commit root is not the same as the
723-
* current root anymore. This is a problem, because send uses the
724-
* commit root and therefore can see inode items that don't exist
725-
* in the current root anymore, and for example make calls to
726-
* btrfs_iget, which will do tree lookups based on the current root
727-
* and not on the commit root. Those lookups will fail, returning a
728-
* -ESTALE error, and making send fail with that error. So make sure
729-
* a send does not see any orphans we have just removed, and that it
730-
* will see the same inodes regardless of whether a transaction
731-
* commit happened before it started (meaning that the commit root
732-
* will be the same as the current root) or not.
733-
*/
734-
if (readonly && pending_snapshot->snap->node !=
735-
pending_snapshot->snap->commit_root) {
736-
trans = btrfs_join_transaction(pending_snapshot->snap);
737-
if (IS_ERR(trans) && PTR_ERR(trans) != -ENOENT) {
738-
ret = PTR_ERR(trans);
739-
goto fail;
740-
}
741-
if (!IS_ERR(trans)) {
742-
ret = btrfs_commit_transaction(trans,
743-
pending_snapshot->snap);
744-
if (ret)
745-
goto fail;
746-
}
747-
}
748-
749720
inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry);
750721
if (IS_ERR(inode)) {
751722
ret = PTR_ERR(inode);

fs/btrfs/send.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5507,6 +5507,51 @@ static int send_subvol(struct send_ctx *sctx)
55075507
return ret;
55085508
}
55095509

5510+
/*
5511+
* If orphan cleanup did remove any orphans from a root, it means the tree
5512+
* was modified and therefore the commit root is not the same as the current
5513+
* root anymore. This is a problem, because send uses the commit root and
5514+
* therefore can see inode items that don't exist in the current root anymore,
5515+
* and for example make calls to btrfs_iget, which will do tree lookups based
5516+
* on the current root and not on the commit root. Those lookups will fail,
5517+
* returning a -ESTALE error, and making send fail with that error. So make
5518+
* sure a send does not see any orphans we have just removed, and that it will
5519+
* see the same inodes regardless of whether a transaction commit happened
5520+
* before it started (meaning that the commit root will be the same as the
5521+
* current root) or not.
5522+
*/
5523+
static int ensure_commit_roots_uptodate(struct send_ctx *sctx)
5524+
{
5525+
int i;
5526+
struct btrfs_trans_handle *trans = NULL;
5527+
5528+
again:
5529+
if (sctx->parent_root &&
5530+
sctx->parent_root->node != sctx->parent_root->commit_root)
5531+
goto commit_trans;
5532+
5533+
for (i = 0; i < sctx->clone_roots_cnt; i++)
5534+
if (sctx->clone_roots[i].root->node !=
5535+
sctx->clone_roots[i].root->commit_root)
5536+
goto commit_trans;
5537+
5538+
if (trans)
5539+
return btrfs_end_transaction(trans, sctx->send_root);
5540+
5541+
return 0;
5542+
5543+
commit_trans:
5544+
/* Use any root, all fs roots will get their commit roots updated. */
5545+
if (!trans) {
5546+
trans = btrfs_join_transaction(sctx->send_root);
5547+
if (IS_ERR(trans))
5548+
return PTR_ERR(trans);
5549+
goto again;
5550+
}
5551+
5552+
return btrfs_commit_transaction(trans, sctx->send_root);
5553+
}
5554+
55105555
static void btrfs_root_dec_send_in_progress(struct btrfs_root* root)
55115556
{
55125557
spin_lock(&root->root_item_lock);
@@ -5728,6 +5773,10 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)
57285773
NULL);
57295774
sort_clone_roots = 1;
57305775

5776+
ret = ensure_commit_roots_uptodate(sctx);
5777+
if (ret)
5778+
goto out;
5779+
57315780
current->journal_info = BTRFS_SEND_TRANS_STUB;
57325781
ret = send_subvol(sctx);
57335782
current->journal_info = NULL;

0 commit comments

Comments
 (0)