|
@@ -2630,6 +2630,22 @@ static int record_ref(struct list_head *head, u64 dir,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int dup_ref(struct recorded_ref *ref, struct list_head *list)
|
|
|
+{
|
|
|
+ struct recorded_ref *new;
|
|
|
+
|
|
|
+ new = kmalloc(sizeof(*ref), GFP_NOFS);
|
|
|
+ if (!new)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ new->dir = ref->dir;
|
|
|
+ new->dir_gen = ref->dir_gen;
|
|
|
+ new->full_path = NULL;
|
|
|
+ INIT_LIST_HEAD(&new->list);
|
|
|
+ list_add_tail(&new->list, list);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static void __free_recorded_refs(struct list_head *head)
|
|
|
{
|
|
|
struct recorded_ref *cur;
|
|
@@ -2744,9 +2760,7 @@ static int process_recorded_refs(struct send_ctx *sctx)
|
|
|
int ret = 0;
|
|
|
struct recorded_ref *cur;
|
|
|
struct recorded_ref *cur2;
|
|
|
- struct ulist *check_dirs = NULL;
|
|
|
- struct ulist_iterator uit;
|
|
|
- struct ulist_node *un;
|
|
|
+ struct list_head check_dirs;
|
|
|
struct fs_path *valid_path = NULL;
|
|
|
u64 ow_inode = 0;
|
|
|
u64 ow_gen;
|
|
@@ -2760,6 +2774,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
* which is always '..'
|
|
|
*/
|
|
|
BUG_ON(sctx->cur_ino <= BTRFS_FIRST_FREE_OBJECTID);
|
|
|
+ INIT_LIST_HEAD(&check_dirs);
|
|
|
|
|
|
valid_path = fs_path_alloc();
|
|
|
if (!valid_path) {
|
|
@@ -2767,12 +2782,6 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- check_dirs = ulist_alloc(GFP_NOFS);
|
|
|
- if (!check_dirs) {
|
|
|
- ret = -ENOMEM;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
/*
|
|
|
* First, check if the first ref of the current inode was overwritten
|
|
|
* before. If yes, we know that the current inode was already orphanized
|
|
@@ -2909,8 +2918,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
goto out;
|
|
|
}
|
|
|
}
|
|
|
- ret = ulist_add(check_dirs, cur->dir, cur->dir_gen,
|
|
|
- GFP_NOFS);
|
|
|
+ ret = dup_ref(cur, &check_dirs);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
}
|
|
@@ -2938,8 +2946,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
}
|
|
|
|
|
|
list_for_each_entry(cur, &sctx->deleted_refs, list) {
|
|
|
- ret = ulist_add(check_dirs, cur->dir, cur->dir_gen,
|
|
|
- GFP_NOFS);
|
|
|
+ ret = dup_ref(cur, &check_dirs);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
}
|
|
@@ -2950,8 +2957,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
*/
|
|
|
cur = list_entry(sctx->deleted_refs.next, struct recorded_ref,
|
|
|
list);
|
|
|
- ret = ulist_add(check_dirs, cur->dir, cur->dir_gen,
|
|
|
- GFP_NOFS);
|
|
|
+ ret = dup_ref(cur, &check_dirs);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
} else if (!S_ISDIR(sctx->cur_inode_mode)) {
|
|
@@ -2971,12 +2977,10 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
}
|
|
|
- ret = ulist_add(check_dirs, cur->dir, cur->dir_gen,
|
|
|
- GFP_NOFS);
|
|
|
+ ret = dup_ref(cur, &check_dirs);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
}
|
|
|
-
|
|
|
/*
|
|
|
* If the inode is still orphan, unlink the orphan. This may
|
|
|
* happen when a previous inode did overwrite the first ref
|
|
@@ -2998,33 +3002,32 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
* deletion and if it's finally possible to perform the rmdir now.
|
|
|
* We also update the inode stats of the parent dirs here.
|
|
|
*/
|
|
|
- ULIST_ITER_INIT(&uit);
|
|
|
- while ((un = ulist_next(check_dirs, &uit))) {
|
|
|
+ list_for_each_entry(cur, &check_dirs, list) {
|
|
|
/*
|
|
|
* In case we had refs into dirs that were not processed yet,
|
|
|
* we don't need to do the utime and rmdir logic for these dirs.
|
|
|
* The dir will be processed later.
|
|
|
*/
|
|
|
- if (un->val > sctx->cur_ino)
|
|
|
+ if (cur->dir > sctx->cur_ino)
|
|
|
continue;
|
|
|
|
|
|
- ret = get_cur_inode_state(sctx, un->val, un->aux);
|
|
|
+ ret = get_cur_inode_state(sctx, cur->dir, cur->dir_gen);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
|
|
|
if (ret == inode_state_did_create ||
|
|
|
ret == inode_state_no_change) {
|
|
|
/* TODO delayed utimes */
|
|
|
- ret = send_utimes(sctx, un->val, un->aux);
|
|
|
+ ret = send_utimes(sctx, cur->dir, cur->dir_gen);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
} else if (ret == inode_state_did_delete) {
|
|
|
- ret = can_rmdir(sctx, un->val, sctx->cur_ino);
|
|
|
+ ret = can_rmdir(sctx, cur->dir, sctx->cur_ino);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
if (ret) {
|
|
|
- ret = get_cur_path(sctx, un->val, un->aux,
|
|
|
- valid_path);
|
|
|
+ ret = get_cur_path(sctx, cur->dir,
|
|
|
+ cur->dir_gen, valid_path);
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
ret = send_rmdir(sctx, valid_path);
|
|
@@ -3037,8 +3040,8 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
|
|
|
ret = 0;
|
|
|
|
|
|
out:
|
|
|
+ __free_recorded_refs(&check_dirs);
|
|
|
free_recorded_refs(sctx);
|
|
|
- ulist_free(check_dirs);
|
|
|
fs_path_free(valid_path);
|
|
|
return ret;
|
|
|
}
|
|
@@ -3139,6 +3142,8 @@ out:
|
|
|
|
|
|
struct find_ref_ctx {
|
|
|
u64 dir;
|
|
|
+ u64 dir_gen;
|
|
|
+ struct btrfs_root *root;
|
|
|
struct fs_path *name;
|
|
|
int found_idx;
|
|
|
};
|
|
@@ -3148,9 +3153,21 @@ static int __find_iref(int num, u64 dir, int index,
|
|
|
void *ctx_)
|
|
|
{
|
|
|
struct find_ref_ctx *ctx = ctx_;
|
|
|
+ u64 dir_gen;
|
|
|
+ int ret;
|
|
|
|
|
|
if (dir == ctx->dir && fs_path_len(name) == fs_path_len(ctx->name) &&
|
|
|
strncmp(name->start, ctx->name->start, fs_path_len(name)) == 0) {
|
|
|
+ /*
|
|
|
+ * To avoid doing extra lookups we'll only do this if everything
|
|
|
+ * else matches.
|
|
|
+ */
|
|
|
+ ret = get_inode_info(ctx->root, dir, NULL, &dir_gen, NULL,
|
|
|
+ NULL, NULL, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ if (dir_gen != ctx->dir_gen)
|
|
|
+ return 0;
|
|
|
ctx->found_idx = num;
|
|
|
return 1;
|
|
|
}
|
|
@@ -3160,14 +3177,16 @@ static int __find_iref(int num, u64 dir, int index,
|
|
|
static int find_iref(struct btrfs_root *root,
|
|
|
struct btrfs_path *path,
|
|
|
struct btrfs_key *key,
|
|
|
- u64 dir, struct fs_path *name)
|
|
|
+ u64 dir, u64 dir_gen, struct fs_path *name)
|
|
|
{
|
|
|
int ret;
|
|
|
struct find_ref_ctx ctx;
|
|
|
|
|
|
ctx.dir = dir;
|
|
|
ctx.name = name;
|
|
|
+ ctx.dir_gen = dir_gen;
|
|
|
ctx.found_idx = -1;
|
|
|
+ ctx.root = root;
|
|
|
|
|
|
ret = iterate_inode_ref(root, path, key, 0, __find_iref, &ctx);
|
|
|
if (ret < 0)
|
|
@@ -3183,11 +3202,17 @@ static int __record_changed_new_ref(int num, u64 dir, int index,
|
|
|
struct fs_path *name,
|
|
|
void *ctx)
|
|
|
{
|
|
|
+ u64 dir_gen;
|
|
|
int ret;
|
|
|
struct send_ctx *sctx = ctx;
|
|
|
|
|
|
+ ret = get_inode_info(sctx->send_root, dir, NULL, &dir_gen, NULL,
|
|
|
+ NULL, NULL, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
ret = find_iref(sctx->parent_root, sctx->right_path,
|
|
|
- sctx->cmp_key, dir, name);
|
|
|
+ sctx->cmp_key, dir, dir_gen, name);
|
|
|
if (ret == -ENOENT)
|
|
|
ret = __record_new_ref(num, dir, index, name, sctx);
|
|
|
else if (ret > 0)
|
|
@@ -3200,11 +3225,17 @@ static int __record_changed_deleted_ref(int num, u64 dir, int index,
|
|
|
struct fs_path *name,
|
|
|
void *ctx)
|
|
|
{
|
|
|
+ u64 dir_gen;
|
|
|
int ret;
|
|
|
struct send_ctx *sctx = ctx;
|
|
|
|
|
|
+ ret = get_inode_info(sctx->parent_root, dir, NULL, &dir_gen, NULL,
|
|
|
+ NULL, NULL, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
ret = find_iref(sctx->send_root, sctx->left_path, sctx->cmp_key,
|
|
|
- dir, name);
|
|
|
+ dir, dir_gen, name);
|
|
|
if (ret == -ENOENT)
|
|
|
ret = __record_deleted_ref(num, dir, index, name, sctx);
|
|
|
else if (ret > 0)
|
|
@@ -4381,6 +4412,64 @@ static int changed_extent(struct send_ctx *sctx,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int dir_changed(struct send_ctx *sctx, u64 dir)
|
|
|
+{
|
|
|
+ u64 orig_gen, new_gen;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = get_inode_info(sctx->send_root, dir, NULL, &new_gen, NULL, NULL,
|
|
|
+ NULL, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = get_inode_info(sctx->parent_root, dir, NULL, &orig_gen, NULL,
|
|
|
+ NULL, NULL, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return (orig_gen != new_gen) ? 1 : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int compare_refs(struct send_ctx *sctx, struct btrfs_path *path,
|
|
|
+ struct btrfs_key *key)
|
|
|
+{
|
|
|
+ struct btrfs_inode_extref *extref;
|
|
|
+ struct extent_buffer *leaf;
|
|
|
+ u64 dirid = 0, last_dirid = 0;
|
|
|
+ unsigned long ptr;
|
|
|
+ u32 item_size;
|
|
|
+ u32 cur_offset = 0;
|
|
|
+ int ref_name_len;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /* Easy case, just check this one dirid */
|
|
|
+ if (key->type == BTRFS_INODE_REF_KEY) {
|
|
|
+ dirid = key->offset;
|
|
|
+
|
|
|
+ ret = dir_changed(sctx, dirid);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ leaf = path->nodes[0];
|
|
|
+ item_size = btrfs_item_size_nr(leaf, path->slots[0]);
|
|
|
+ ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
|
|
|
+ while (cur_offset < item_size) {
|
|
|
+ extref = (struct btrfs_inode_extref *)(ptr +
|
|
|
+ cur_offset);
|
|
|
+ dirid = btrfs_inode_extref_parent(leaf, extref);
|
|
|
+ ref_name_len = btrfs_inode_extref_name_len(leaf, extref);
|
|
|
+ cur_offset += ref_name_len + sizeof(*extref);
|
|
|
+ if (dirid == last_dirid)
|
|
|
+ continue;
|
|
|
+ ret = dir_changed(sctx, dirid);
|
|
|
+ if (ret)
|
|
|
+ break;
|
|
|
+ last_dirid = dirid;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Updates compare related fields in sctx and simply forwards to the actual
|
|
|
* changed_xxx functions.
|
|
@@ -4396,6 +4485,19 @@ static int changed_cb(struct btrfs_root *left_root,
|
|
|
int ret = 0;
|
|
|
struct send_ctx *sctx = ctx;
|
|
|
|
|
|
+ if (result == BTRFS_COMPARE_TREE_SAME) {
|
|
|
+ if (key->type != BTRFS_INODE_REF_KEY &&
|
|
|
+ key->type != BTRFS_INODE_EXTREF_KEY)
|
|
|
+ return 0;
|
|
|
+ ret = compare_refs(sctx, left_path, key);
|
|
|
+ if (!ret)
|
|
|
+ return 0;
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ result = BTRFS_COMPARE_TREE_CHANGED;
|
|
|
+ ret = 0;
|
|
|
+ }
|
|
|
+
|
|
|
sctx->left_path = left_path;
|
|
|
sctx->right_path = right_path;
|
|
|
sctx->cmp_key = key;
|