Commit 84471e24 authored by Filipe Manana's avatar Filipe Manana Committed by Chris Mason

Btrfs: incremental send, don't rename a directory too soon

There's one more case where we can't issue a rename operation for a
directory as soon as we process it. We used to delay directory renames
only if they have some ancestor directory with a higher inode number
that got renamed too, but there's another case where we need to delay
the rename too - when a directory A is renamed to the old name of a
directory B but that directory B has its rename delayed because it
has now (in the send root) an ancestor with a higher inode number that
was renamed. If we don't delay the directory rename in this case, the
receiving end of the send stream will attempt to rename A to the old
name of B before B got renamed to its new name, which results in a
"directory not empty" error. So fix this by delaying directory renames
for this case too.

Steps to reproduce:

  $ mkfs.btrfs -f /dev/sdb
  $ mount /dev/sdb /mnt

  $ mkdir /mnt/a
  $ mkdir /mnt/b
  $ mkdir /mnt/c
  $ touch /mnt/a/file

  $ btrfs subvolume snapshot -r /mnt /mnt/snap1

  $ mv /mnt/c /mnt/x
  $ mv /mnt/a /mnt/x/y
  $ mv /mnt/b /mnt/a

  $ btrfs subvolume snapshot -r /mnt /mnt/snap2

  $ btrfs send /mnt/snap1 -f /tmp/1.send
  $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/2.send

  $ mkfs.btrfs -f /dev/sdc
  $ mount /dev/sdc /mnt2
  $ btrfs receive /mnt2 -f /tmp/1.send
  $ btrfs receive /mnt2 -f /tmp/2.send
  ERROR: rename b -> a failed. Directory not empty

A test case for xfstests follows soon.
Reported-by: default avatarAmes Cornish <ames@cornishes.net>
Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
Signed-off-by: default avatarChris Mason <clm@fb.com>
parent 1932b7be
...@@ -230,6 +230,7 @@ struct pending_dir_move { ...@@ -230,6 +230,7 @@ struct pending_dir_move {
u64 parent_ino; u64 parent_ino;
u64 ino; u64 ino;
u64 gen; u64 gen;
bool is_orphan;
struct list_head update_refs; struct list_head update_refs;
}; };
...@@ -2984,7 +2985,8 @@ static int add_pending_dir_move(struct send_ctx *sctx, ...@@ -2984,7 +2985,8 @@ static int add_pending_dir_move(struct send_ctx *sctx,
u64 ino_gen, u64 ino_gen,
u64 parent_ino, u64 parent_ino,
struct list_head *new_refs, struct list_head *new_refs,
struct list_head *deleted_refs) struct list_head *deleted_refs,
const bool is_orphan)
{ {
struct rb_node **p = &sctx->pending_dir_moves.rb_node; struct rb_node **p = &sctx->pending_dir_moves.rb_node;
struct rb_node *parent = NULL; struct rb_node *parent = NULL;
...@@ -2999,6 +3001,7 @@ static int add_pending_dir_move(struct send_ctx *sctx, ...@@ -2999,6 +3001,7 @@ static int add_pending_dir_move(struct send_ctx *sctx,
pm->parent_ino = parent_ino; pm->parent_ino = parent_ino;
pm->ino = ino; pm->ino = ino;
pm->gen = ino_gen; pm->gen = ino_gen;
pm->is_orphan = is_orphan;
INIT_LIST_HEAD(&pm->list); INIT_LIST_HEAD(&pm->list);
INIT_LIST_HEAD(&pm->update_refs); INIT_LIST_HEAD(&pm->update_refs);
RB_CLEAR_NODE(&pm->node); RB_CLEAR_NODE(&pm->node);
...@@ -3131,16 +3134,20 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3131,16 +3134,20 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
rmdir_ino = dm->rmdir_ino; rmdir_ino = dm->rmdir_ino;
free_waiting_dir_move(sctx, dm); free_waiting_dir_move(sctx, dm);
if (pm->is_orphan) {
ret = gen_unique_name(sctx, pm->ino,
pm->gen, from_path);
} else {
ret = get_first_ref(sctx->parent_root, pm->ino, ret = get_first_ref(sctx->parent_root, pm->ino,
&parent_ino, &parent_gen, name); &parent_ino, &parent_gen, name);
if (ret < 0) if (ret < 0)
goto out; goto out;
ret = get_cur_path(sctx, parent_ino, parent_gen, ret = get_cur_path(sctx, parent_ino, parent_gen,
from_path); from_path);
if (ret < 0) if (ret < 0)
goto out; goto out;
ret = fs_path_add_path(from_path, name); ret = fs_path_add_path(from_path, name);
}
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -3150,7 +3157,8 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3150,7 +3157,8 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
LIST_HEAD(deleted_refs); LIST_HEAD(deleted_refs);
ASSERT(ancestor > BTRFS_FIRST_FREE_OBJECTID); ASSERT(ancestor > BTRFS_FIRST_FREE_OBJECTID);
ret = add_pending_dir_move(sctx, pm->ino, pm->gen, ancestor, ret = add_pending_dir_move(sctx, pm->ino, pm->gen, ancestor,
&pm->update_refs, &deleted_refs); &pm->update_refs, &deleted_refs,
pm->is_orphan);
if (ret < 0) if (ret < 0)
goto out; goto out;
if (rmdir_ino) { if (rmdir_ino) {
...@@ -3283,6 +3291,127 @@ static int apply_children_dir_moves(struct send_ctx *sctx) ...@@ -3283,6 +3291,127 @@ static int apply_children_dir_moves(struct send_ctx *sctx)
return ret; return ret;
} }
/*
* We might need to delay a directory rename even when no ancestor directory
* (in the send root) with a higher inode number than ours (sctx->cur_ino) was
* renamed. This happens when we rename a directory to the old name (the name
* in the parent root) of some other unrelated directory that got its rename
* delayed due to some ancestor with higher number that got renamed.
*
* Example:
*
* Parent snapshot:
* . (ino 256)
* |---- a/ (ino 257)
* | |---- file (ino 260)
* |
* |---- b/ (ino 258)
* |---- c/ (ino 259)
*
* Send snapshot:
* . (ino 256)
* |---- a/ (ino 258)
* |---- x/ (ino 259)
* |---- y/ (ino 257)
* |----- file (ino 260)
*
* Here we can not rename 258 from 'b' to 'a' without the rename of inode 257
* from 'a' to 'x/y' happening first, which in turn depends on the rename of
* inode 259 from 'c' to 'x'. So the order of rename commands the send stream
* must issue is:
*
* 1 - rename 259 from 'c' to 'x'
* 2 - rename 257 from 'a' to 'x/y'
* 3 - rename 258 from 'b' to 'a'
*
* Returns 1 if the rename of sctx->cur_ino needs to be delayed, 0 if it can
* be done right away and < 0 on error.
*/
static int wait_for_dest_dir_move(struct send_ctx *sctx,
struct recorded_ref *parent_ref,
const bool is_orphan)
{
struct btrfs_path *path;
struct btrfs_key key;
struct btrfs_key di_key;
struct btrfs_dir_item *di;
u64 left_gen;
u64 right_gen;
int ret = 0;
if (RB_EMPTY_ROOT(&sctx->waiting_dir_moves))
return 0;
path = alloc_path_for_send();
if (!path)
return -ENOMEM;
key.objectid = parent_ref->dir;
key.type = BTRFS_DIR_ITEM_KEY;
key.offset = btrfs_name_hash(parent_ref->name, parent_ref->name_len);
ret = btrfs_search_slot(NULL, sctx->parent_root, &key, path, 0, 0);
if (ret < 0) {
goto out;
} else if (ret > 0) {
ret = 0;
goto out;
}
di = btrfs_match_dir_item_name(sctx->parent_root, path,
parent_ref->name, parent_ref->name_len);
if (!di) {
ret = 0;
goto out;
}
/*
* di_key.objectid has the number of the inode that has a dentry in the
* parent directory with the same name that sctx->cur_ino is being
* renamed to. We need to check if that inode is in the send root as
* well and if it is currently marked as an inode with a pending rename,
* if it is, we need to delay the rename of sctx->cur_ino as well, so
* that it happens after that other inode is renamed.
*/
btrfs_dir_item_key_to_cpu(path->nodes[0], di, &di_key);
if (di_key.type != BTRFS_INODE_ITEM_KEY) {
ret = 0;
goto out;
}
ret = get_inode_info(sctx->parent_root, di_key.objectid, NULL,
&left_gen, NULL, NULL, NULL, NULL);
if (ret < 0)
goto out;
ret = get_inode_info(sctx->send_root, di_key.objectid, NULL,
&right_gen, NULL, NULL, NULL, NULL);
if (ret < 0) {
if (ret == -ENOENT)
ret = 0;
goto out;
}
/* Different inode, no need to delay the rename of sctx->cur_ino */
if (right_gen != left_gen) {
ret = 0;
goto out;
}
if (is_waiting_for_move(sctx, di_key.objectid)) {
ret = add_pending_dir_move(sctx,
sctx->cur_ino,
sctx->cur_inode_gen,
di_key.objectid,
&sctx->new_refs,
&sctx->deleted_refs,
is_orphan);
if (!ret)
ret = 1;
}
out:
btrfs_free_path(path);
return ret;
}
static int wait_for_parent_move(struct send_ctx *sctx, static int wait_for_parent_move(struct send_ctx *sctx,
struct recorded_ref *parent_ref) struct recorded_ref *parent_ref)
{ {
...@@ -3349,7 +3478,8 @@ static int wait_for_parent_move(struct send_ctx *sctx, ...@@ -3349,7 +3478,8 @@ static int wait_for_parent_move(struct send_ctx *sctx,
sctx->cur_inode_gen, sctx->cur_inode_gen,
ino, ino,
&sctx->new_refs, &sctx->new_refs,
&sctx->deleted_refs); &sctx->deleted_refs,
false);
if (!ret) if (!ret)
ret = 1; ret = 1;
} }
...@@ -3372,6 +3502,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move) ...@@ -3372,6 +3502,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
int did_overwrite = 0; int did_overwrite = 0;
int is_orphan = 0; int is_orphan = 0;
u64 last_dir_ino_rm = 0; u64 last_dir_ino_rm = 0;
bool can_rename = true;
verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino); verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
...@@ -3490,12 +3621,22 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino); ...@@ -3490,12 +3621,22 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
} }
} }
if (S_ISDIR(sctx->cur_inode_mode) && sctx->parent_root) {
ret = wait_for_dest_dir_move(sctx, cur, is_orphan);
if (ret < 0)
goto out;
if (ret == 1) {
can_rename = false;
*pending_move = 1;
}
}
/* /*
* link/move the ref to the new place. If we have an orphan * link/move the ref to the new place. If we have an orphan
* inode, move it and update valid_path. If not, link or move * inode, move it and update valid_path. If not, link or move
* it depending on the inode mode. * it depending on the inode mode.
*/ */
if (is_orphan) { if (is_orphan && can_rename) {
ret = send_rename(sctx, valid_path, cur->full_path); ret = send_rename(sctx, valid_path, cur->full_path);
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -3503,7 +3644,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino); ...@@ -3503,7 +3644,7 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
ret = fs_path_copy(valid_path, cur->full_path); ret = fs_path_copy(valid_path, cur->full_path);
if (ret < 0) if (ret < 0)
goto out; goto out;
} else { } else if (can_rename) {
if (S_ISDIR(sctx->cur_inode_mode)) { if (S_ISDIR(sctx->cur_inode_mode)) {
/* /*
* Dirs can't be linked, so move it. For moved * Dirs can't be linked, so move it. For moved
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment