|
@@ -100,6 +100,7 @@ struct send_ctx {
|
|
u64 cur_inode_rdev;
|
|
u64 cur_inode_rdev;
|
|
u64 cur_inode_last_extent;
|
|
u64 cur_inode_last_extent;
|
|
u64 cur_inode_next_write_offset;
|
|
u64 cur_inode_next_write_offset;
|
|
|
|
+ bool ignore_cur_inode;
|
|
|
|
|
|
u64 send_progress;
|
|
u64 send_progress;
|
|
|
|
|
|
@@ -5796,6 +5797,9 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
|
|
int pending_move = 0;
|
|
int pending_move = 0;
|
|
int refs_processed = 0;
|
|
int refs_processed = 0;
|
|
|
|
|
|
|
|
+ if (sctx->ignore_cur_inode)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
ret = process_recorded_refs_if_needed(sctx, at_end, &pending_move,
|
|
ret = process_recorded_refs_if_needed(sctx, at_end, &pending_move,
|
|
&refs_processed);
|
|
&refs_processed);
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
@@ -5914,6 +5918,93 @@ out:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+struct parent_paths_ctx {
|
|
|
|
+ struct list_head *refs;
|
|
|
|
+ struct send_ctx *sctx;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int record_parent_ref(int num, u64 dir, int index, struct fs_path *name,
|
|
|
|
+ void *ctx)
|
|
|
|
+{
|
|
|
|
+ struct parent_paths_ctx *ppctx = ctx;
|
|
|
|
+
|
|
|
|
+ return record_ref(ppctx->sctx->parent_root, dir, name, ppctx->sctx,
|
|
|
|
+ ppctx->refs);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Issue unlink operations for all paths of the current inode found in the
|
|
|
|
+ * parent snapshot.
|
|
|
|
+ */
|
|
|
|
+static int btrfs_unlink_all_paths(struct send_ctx *sctx)
|
|
|
|
+{
|
|
|
|
+ LIST_HEAD(deleted_refs);
|
|
|
|
+ struct btrfs_path *path;
|
|
|
|
+ struct btrfs_key key;
|
|
|
|
+ struct parent_paths_ctx ctx;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ path = alloc_path_for_send();
|
|
|
|
+ if (!path)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ key.objectid = sctx->cur_ino;
|
|
|
|
+ key.type = BTRFS_INODE_REF_KEY;
|
|
|
|
+ key.offset = 0;
|
|
|
|
+ ret = btrfs_search_slot(NULL, sctx->parent_root, &key, path, 0, 0);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ ctx.refs = &deleted_refs;
|
|
|
|
+ ctx.sctx = sctx;
|
|
|
|
+
|
|
|
|
+ while (true) {
|
|
|
|
+ struct extent_buffer *eb = path->nodes[0];
|
|
|
|
+ int slot = path->slots[0];
|
|
|
|
+
|
|
|
|
+ if (slot >= btrfs_header_nritems(eb)) {
|
|
|
|
+ ret = btrfs_next_leaf(sctx->parent_root, path);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+ else if (ret > 0)
|
|
|
|
+ break;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ btrfs_item_key_to_cpu(eb, &key, slot);
|
|
|
|
+ if (key.objectid != sctx->cur_ino)
|
|
|
|
+ break;
|
|
|
|
+ if (key.type != BTRFS_INODE_REF_KEY &&
|
|
|
|
+ key.type != BTRFS_INODE_EXTREF_KEY)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ ret = iterate_inode_ref(sctx->parent_root, path, &key, 1,
|
|
|
|
+ record_parent_ref, &ctx);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ path->slots[0]++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ while (!list_empty(&deleted_refs)) {
|
|
|
|
+ struct recorded_ref *ref;
|
|
|
|
+
|
|
|
|
+ ref = list_first_entry(&deleted_refs, struct recorded_ref, list);
|
|
|
|
+ ret = send_unlink(sctx, ref->full_path);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+ fs_path_free(ref->full_path);
|
|
|
|
+ list_del(&ref->list);
|
|
|
|
+ kfree(ref);
|
|
|
|
+ }
|
|
|
|
+ ret = 0;
|
|
|
|
+out:
|
|
|
|
+ btrfs_free_path(path);
|
|
|
|
+ if (ret)
|
|
|
|
+ __free_recorded_refs(&deleted_refs);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
static int changed_inode(struct send_ctx *sctx,
|
|
static int changed_inode(struct send_ctx *sctx,
|
|
enum btrfs_compare_tree_result result)
|
|
enum btrfs_compare_tree_result result)
|
|
{
|
|
{
|
|
@@ -5928,6 +6019,7 @@ static int changed_inode(struct send_ctx *sctx,
|
|
sctx->cur_inode_new_gen = 0;
|
|
sctx->cur_inode_new_gen = 0;
|
|
sctx->cur_inode_last_extent = (u64)-1;
|
|
sctx->cur_inode_last_extent = (u64)-1;
|
|
sctx->cur_inode_next_write_offset = 0;
|
|
sctx->cur_inode_next_write_offset = 0;
|
|
|
|
+ sctx->ignore_cur_inode = false;
|
|
|
|
|
|
/*
|
|
/*
|
|
* Set send_progress to current inode. This will tell all get_cur_xxx
|
|
* Set send_progress to current inode. This will tell all get_cur_xxx
|
|
@@ -5968,6 +6060,33 @@ static int changed_inode(struct send_ctx *sctx,
|
|
sctx->cur_inode_new_gen = 1;
|
|
sctx->cur_inode_new_gen = 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Normally we do not find inodes with a link count of zero (orphans)
|
|
|
|
+ * because the most common case is to create a snapshot and use it
|
|
|
|
+ * for a send operation. However other less common use cases involve
|
|
|
|
+ * using a subvolume and send it after turning it to RO mode just
|
|
|
|
+ * after deleting all hard links of a file while holding an open
|
|
|
|
+ * file descriptor against it or turning a RO snapshot into RW mode,
|
|
|
|
+ * keep an open file descriptor against a file, delete it and then
|
|
|
|
+ * turn the snapshot back to RO mode before using it for a send
|
|
|
|
+ * operation. So if we find such cases, ignore the inode and all its
|
|
|
|
+ * items completely if it's a new inode, or if it's a changed inode
|
|
|
|
+ * make sure all its previous paths (from the parent snapshot) are all
|
|
|
|
+ * unlinked and all other the inode items are ignored.
|
|
|
|
+ */
|
|
|
|
+ if (result == BTRFS_COMPARE_TREE_NEW ||
|
|
|
|
+ result == BTRFS_COMPARE_TREE_CHANGED) {
|
|
|
|
+ u32 nlinks;
|
|
|
|
+
|
|
|
|
+ nlinks = btrfs_inode_nlink(sctx->left_path->nodes[0], left_ii);
|
|
|
|
+ if (nlinks == 0) {
|
|
|
|
+ sctx->ignore_cur_inode = true;
|
|
|
|
+ if (result == BTRFS_COMPARE_TREE_CHANGED)
|
|
|
|
+ ret = btrfs_unlink_all_paths(sctx);
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
if (result == BTRFS_COMPARE_TREE_NEW) {
|
|
if (result == BTRFS_COMPARE_TREE_NEW) {
|
|
sctx->cur_inode_gen = left_gen;
|
|
sctx->cur_inode_gen = left_gen;
|
|
sctx->cur_inode_new = 1;
|
|
sctx->cur_inode_new = 1;
|
|
@@ -6306,15 +6425,17 @@ static int changed_cb(struct btrfs_path *left_path,
|
|
key->objectid == BTRFS_FREE_SPACE_OBJECTID)
|
|
key->objectid == BTRFS_FREE_SPACE_OBJECTID)
|
|
goto out;
|
|
goto out;
|
|
|
|
|
|
- if (key->type == BTRFS_INODE_ITEM_KEY)
|
|
|
|
|
|
+ if (key->type == BTRFS_INODE_ITEM_KEY) {
|
|
ret = changed_inode(sctx, result);
|
|
ret = changed_inode(sctx, result);
|
|
- else if (key->type == BTRFS_INODE_REF_KEY ||
|
|
|
|
- key->type == BTRFS_INODE_EXTREF_KEY)
|
|
|
|
- ret = changed_ref(sctx, result);
|
|
|
|
- else if (key->type == BTRFS_XATTR_ITEM_KEY)
|
|
|
|
- ret = changed_xattr(sctx, result);
|
|
|
|
- else if (key->type == BTRFS_EXTENT_DATA_KEY)
|
|
|
|
- ret = changed_extent(sctx, result);
|
|
|
|
|
|
+ } else if (!sctx->ignore_cur_inode) {
|
|
|
|
+ if (key->type == BTRFS_INODE_REF_KEY ||
|
|
|
|
+ key->type == BTRFS_INODE_EXTREF_KEY)
|
|
|
|
+ ret = changed_ref(sctx, result);
|
|
|
|
+ else if (key->type == BTRFS_XATTR_ITEM_KEY)
|
|
|
|
+ ret = changed_xattr(sctx, result);
|
|
|
|
+ else if (key->type == BTRFS_EXTENT_DATA_KEY)
|
|
|
|
+ ret = changed_extent(sctx, result);
|
|
|
|
+ }
|
|
|
|
|
|
out:
|
|
out:
|
|
return ret;
|
|
return ret;
|