Skip to content

Commit 9515558

Browse files
committed
Btrfs: send, don't bug on inconsistent snapshots
When doing an incremental send, if we find a new/modified/deleted extent, reference or xattr without having previously processed the corresponding inode item we end up exexuting a BUG_ON(). This is because whenever an extent, xattr or reference is added, modified or deleted, we always expect to have the corresponding inode item updated. However there are situations where this will not happen due to transient -ENOMEM or -ENOSPC errors when doing delayed inode updates. For example, when punching holes we can succeed in deleting and modifying (shrinking) extents but later fail to do the delayed inode update. So after such failure we close our transaction handle and right after a snapshot of the fs/subvol tree can be made and used later for a send operation. The same thing can happen during truncate, link, unlink, and xattr related operations. So instead of executing a BUG_ON, make send return an -EIO error and print an informative error message do dmesg/syslog. Signed-off-by: Filipe Manana <[email protected]>
1 parent 15b253e commit 9515558

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed

fs/btrfs/send.c

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,39 @@ struct name_cache_entry {
273273
char name[];
274274
};
275275

276+
static void inconsistent_snapshot_error(struct send_ctx *sctx,
277+
enum btrfs_compare_tree_result result,
278+
const char *what)
279+
{
280+
const char *result_string;
281+
282+
switch (result) {
283+
case BTRFS_COMPARE_TREE_NEW:
284+
result_string = "new";
285+
break;
286+
case BTRFS_COMPARE_TREE_DELETED:
287+
result_string = "deleted";
288+
break;
289+
case BTRFS_COMPARE_TREE_CHANGED:
290+
result_string = "updated";
291+
break;
292+
case BTRFS_COMPARE_TREE_SAME:
293+
ASSERT(0);
294+
result_string = "unchanged";
295+
break;
296+
default:
297+
ASSERT(0);
298+
result_string = "unexpected";
299+
}
300+
301+
btrfs_err(sctx->send_root->fs_info,
302+
"Send: inconsistent snapshot, found %s %s for inode %llu without updated inode item, send root is %llu, parent root is %llu",
303+
result_string, what, sctx->cmp_key->objectid,
304+
sctx->send_root->root_key.objectid,
305+
(sctx->parent_root ?
306+
sctx->parent_root->root_key.objectid : 0));
307+
}
308+
276309
static int is_waiting_for_move(struct send_ctx *sctx, u64 ino);
277310

278311
static struct waiting_dir_move *
@@ -5711,7 +5744,10 @@ static int changed_ref(struct send_ctx *sctx,
57115744
{
57125745
int ret = 0;
57135746

5714-
BUG_ON(sctx->cur_ino != sctx->cmp_key->objectid);
5747+
if (sctx->cur_ino != sctx->cmp_key->objectid) {
5748+
inconsistent_snapshot_error(sctx, result, "reference");
5749+
return -EIO;
5750+
}
57155751

57165752
if (!sctx->cur_inode_new_gen &&
57175753
sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID) {
@@ -5736,7 +5772,10 @@ static int changed_xattr(struct send_ctx *sctx,
57365772
{
57375773
int ret = 0;
57385774

5739-
BUG_ON(sctx->cur_ino != sctx->cmp_key->objectid);
5775+
if (sctx->cur_ino != sctx->cmp_key->objectid) {
5776+
inconsistent_snapshot_error(sctx, result, "xattr");
5777+
return -EIO;
5778+
}
57405779

57415780
if (!sctx->cur_inode_new_gen && !sctx->cur_inode_deleted) {
57425781
if (result == BTRFS_COMPARE_TREE_NEW)
@@ -5760,7 +5799,10 @@ static int changed_extent(struct send_ctx *sctx,
57605799
{
57615800
int ret = 0;
57625801

5763-
BUG_ON(sctx->cur_ino != sctx->cmp_key->objectid);
5802+
if (sctx->cur_ino != sctx->cmp_key->objectid) {
5803+
inconsistent_snapshot_error(sctx, result, "extent");
5804+
return -EIO;
5805+
}
57645806

57655807
if (!sctx->cur_inode_new_gen && !sctx->cur_inode_deleted) {
57665808
if (result != BTRFS_COMPARE_TREE_DELETED)

0 commit comments

Comments
 (0)