Skip to content

Commit 3821f34

Browse files
fdmananamasoncl
authored andcommitted
Btrfs: update commit root on snapshot creation after orphan cleanup
On snapshot creation (either writable or read-only), we do orphan cleanup against the root of the snapshot. If the cleanup did remove any orphans, then the current root node will be different from the commit root node until the next transaction commit happens. A send operation always uses the commit root of a snapshot - this means it will see the orphans if it starts computing the send stream before the next transaction commit happens (triggered by a timer or sync() for .e.g), which is when the commit root gets assigned a reference to current root, where the orphans are not visible anymore. The consequence of send seeing the orphans is explained below. For example: mkfs.btrfs -f /dev/sdd mount -o commit=999 /dev/sdd /mnt # open a file with O_TMPFILE and leave it open # write some data to the file btrfs subvolume snapshot -r /mnt /mnt/snap1 btrfs send /mnt/snap1 -f /tmp/send.data The send operation will fail with the following error: ERROR: send ioctl failed with -116: Stale file handle What happens here is that our snapshot has an orphan inode still visible through the commit root, that corresponds to the tmpfile. However send will attempt to call inode.c:btrfs_iget(), with the goal of reading the file's data, which will return -ESTALE because it will use the current root (and not the commit root) of the snapshot. Of course, there are other cases where we can get orphans, but this example using a tmpfile makes it much easier to reproduce the issue. Therefore on snapshot creation, after calling btrfs_orphan_cleanup, if the commit root is different from the current root, just commit the transaction associated with the snapshot's root (if it exists), so that a send will not see any orphans that don't exist anymore. This also guarantees a send will always see the same content regardless of whether a transaction commit happened already before the send was requested and after the orphan cleanup (meaning the commit root and current roots are the same) or it hasn't happened yet (commit and current roots are different). Signed-off-by: Filipe David Borba Manana <[email protected]> Signed-off-by: Chris Mason <[email protected]>
1 parent ff5df9b commit 3821f34

File tree

1 file changed

+29
-0
lines changed

1 file changed

+29
-0
lines changed

fs/btrfs/ioctl.c

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

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

0 commit comments

Comments
 (0)