Commit 0b3f407e authored by Filipe Manana's avatar Filipe Manana Committed by David Sterba

btrfs: send: fix wrong file path when there is an inode with a pending rmdir

When doing an incremental send, if we have a new inode that happens to
have the same number that an old directory inode had in the base snapshot
and that old directory has a pending rmdir operation, we end up computing
a wrong path for the new inode, causing the receiver to fail.

Example reproducer:

  $ cat test-send-rmdir.sh
  #!/bin/bash

  DEV=/dev/sdi
  MNT=/mnt/sdi

  mkfs.btrfs -f $DEV >/dev/null
  mount $DEV $MNT

  mkdir $MNT/dir
  touch $MNT/dir/file1
  touch $MNT/dir/file2
  touch $MNT/dir/file3

  # Filesystem looks like:
  #
  # .                                     (ino 256)
  # |----- dir/                           (ino 257)
  #         |----- file1                  (ino 258)
  #         |----- file2                  (ino 259)
  #         |----- file3                  (ino 260)
  #

  btrfs subvolume snapshot -r $MNT $MNT/snap1
  btrfs send -f /tmp/snap1.send $MNT/snap1

  # Now remove our directory and all its files.
  rm -fr $MNT/dir

  # Unmount the filesystem and mount it again. This is to ensure that
  # the next inode that is created ends up with the same inode number
  # that our directory "dir" had, 257, which is the first free "objectid"
  # available after mounting again the filesystem.
  umount $MNT
  mount $DEV $MNT

  # Now create a new file (it could be a directory as well).
  touch $MNT/newfile

  # Filesystem now looks like:
  #
  # .                                     (ino 256)
  # |----- newfile                        (ino 257)
  #

  btrfs subvolume snapshot -r $MNT $MNT/snap2
  btrfs send -f /tmp/snap2.send -p $MNT/snap1 $MNT/snap2

  # Now unmount the filesystem, create a new one, mount it and try to apply
  # both send streams to recreate both snapshots.
  umount $DEV

  mkfs.btrfs -f $DEV >/dev/null

  mount $DEV $MNT

  btrfs receive -f /tmp/snap1.send $MNT
  btrfs receive -f /tmp/snap2.send $MNT

  umount $MNT

When running the test, the receive operation for the incremental stream
fails:

  $ ./test-send-rmdir.sh
  Create a readonly snapshot of '/mnt/sdi' in '/mnt/sdi/snap1'
  At subvol /mnt/sdi/snap1
  Create a readonly snapshot of '/mnt/sdi' in '/mnt/sdi/snap2'
  At subvol /mnt/sdi/snap2
  At subvol snap1
  At snapshot snap2
  ERROR: chown o257-9-0 failed: No such file or directory

So fix this by tracking directories that have a pending rmdir by inode
number and generation number, instead of only inode number.

A test case for fstests follows soon.
Reported-by: default avatarMassimo B. <massimo.b@gmx.net>
Tested-by: default avatarMassimo B. <massimo.b@gmx.net>
Link: https://lore.kernel.org/linux-btrfs/6ae34776e85912960a253a8327068a892998e685.camel@gmx.net/
CC: stable@vger.kernel.org # 4.19+
Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent ae5e070e
...@@ -236,6 +236,7 @@ struct waiting_dir_move { ...@@ -236,6 +236,7 @@ struct waiting_dir_move {
* after this directory is moved, we can try to rmdir the ino rmdir_ino. * after this directory is moved, we can try to rmdir the ino rmdir_ino.
*/ */
u64 rmdir_ino; u64 rmdir_ino;
u64 rmdir_gen;
bool orphanized; bool orphanized;
}; };
...@@ -316,7 +317,7 @@ static int is_waiting_for_move(struct send_ctx *sctx, u64 ino); ...@@ -316,7 +317,7 @@ static int is_waiting_for_move(struct send_ctx *sctx, u64 ino);
static struct waiting_dir_move * static struct waiting_dir_move *
get_waiting_dir_move(struct send_ctx *sctx, u64 ino); get_waiting_dir_move(struct send_ctx *sctx, u64 ino);
static int is_waiting_for_rm(struct send_ctx *sctx, u64 dir_ino); static int is_waiting_for_rm(struct send_ctx *sctx, u64 dir_ino, u64 gen);
static int need_send_hole(struct send_ctx *sctx) static int need_send_hole(struct send_ctx *sctx)
{ {
...@@ -2299,7 +2300,7 @@ static int get_cur_path(struct send_ctx *sctx, u64 ino, u64 gen, ...@@ -2299,7 +2300,7 @@ static int get_cur_path(struct send_ctx *sctx, u64 ino, u64 gen,
fs_path_reset(name); fs_path_reset(name);
if (is_waiting_for_rm(sctx, ino)) { if (is_waiting_for_rm(sctx, ino, gen)) {
ret = gen_unique_name(sctx, ino, gen, name); ret = gen_unique_name(sctx, ino, gen, name);
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -2858,8 +2859,8 @@ static int orphanize_inode(struct send_ctx *sctx, u64 ino, u64 gen, ...@@ -2858,8 +2859,8 @@ static int orphanize_inode(struct send_ctx *sctx, u64 ino, u64 gen,
return ret; return ret;
} }
static struct orphan_dir_info * static struct orphan_dir_info *add_orphan_dir_info(struct send_ctx *sctx,
add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino) u64 dir_ino, u64 dir_gen)
{ {
struct rb_node **p = &sctx->orphan_dirs.rb_node; struct rb_node **p = &sctx->orphan_dirs.rb_node;
struct rb_node *parent = NULL; struct rb_node *parent = NULL;
...@@ -2868,20 +2869,23 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino) ...@@ -2868,20 +2869,23 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
while (*p) { while (*p) {
parent = *p; parent = *p;
entry = rb_entry(parent, struct orphan_dir_info, node); entry = rb_entry(parent, struct orphan_dir_info, node);
if (dir_ino < entry->ino) { if (dir_ino < entry->ino)
p = &(*p)->rb_left; p = &(*p)->rb_left;
} else if (dir_ino > entry->ino) { else if (dir_ino > entry->ino)
p = &(*p)->rb_right; p = &(*p)->rb_right;
} else { else if (dir_gen < entry->gen)
p = &(*p)->rb_left;
else if (dir_gen > entry->gen)
p = &(*p)->rb_right;
else
return entry; return entry;
}
} }
odi = kmalloc(sizeof(*odi), GFP_KERNEL); odi = kmalloc(sizeof(*odi), GFP_KERNEL);
if (!odi) if (!odi)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
odi->ino = dir_ino; odi->ino = dir_ino;
odi->gen = 0; odi->gen = dir_gen;
odi->last_dir_index_offset = 0; odi->last_dir_index_offset = 0;
rb_link_node(&odi->node, parent, p); rb_link_node(&odi->node, parent, p);
...@@ -2889,8 +2893,8 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino) ...@@ -2889,8 +2893,8 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
return odi; return odi;
} }
static struct orphan_dir_info * static struct orphan_dir_info *get_orphan_dir_info(struct send_ctx *sctx,
get_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino) u64 dir_ino, u64 gen)
{ {
struct rb_node *n = sctx->orphan_dirs.rb_node; struct rb_node *n = sctx->orphan_dirs.rb_node;
struct orphan_dir_info *entry; struct orphan_dir_info *entry;
...@@ -2901,15 +2905,19 @@ get_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino) ...@@ -2901,15 +2905,19 @@ get_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
n = n->rb_left; n = n->rb_left;
else if (dir_ino > entry->ino) else if (dir_ino > entry->ino)
n = n->rb_right; n = n->rb_right;
else if (gen < entry->gen)
n = n->rb_left;
else if (gen > entry->gen)
n = n->rb_right;
else else
return entry; return entry;
} }
return NULL; return NULL;
} }
static int is_waiting_for_rm(struct send_ctx *sctx, u64 dir_ino) static int is_waiting_for_rm(struct send_ctx *sctx, u64 dir_ino, u64 gen)
{ {
struct orphan_dir_info *odi = get_orphan_dir_info(sctx, dir_ino); struct orphan_dir_info *odi = get_orphan_dir_info(sctx, dir_ino, gen);
return odi != NULL; return odi != NULL;
} }
...@@ -2954,7 +2962,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen, ...@@ -2954,7 +2962,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
key.type = BTRFS_DIR_INDEX_KEY; key.type = BTRFS_DIR_INDEX_KEY;
key.offset = 0; key.offset = 0;
odi = get_orphan_dir_info(sctx, dir); odi = get_orphan_dir_info(sctx, dir, dir_gen);
if (odi) if (odi)
key.offset = odi->last_dir_index_offset; key.offset = odi->last_dir_index_offset;
...@@ -2985,7 +2993,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen, ...@@ -2985,7 +2993,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
dm = get_waiting_dir_move(sctx, loc.objectid); dm = get_waiting_dir_move(sctx, loc.objectid);
if (dm) { if (dm) {
odi = add_orphan_dir_info(sctx, dir); odi = add_orphan_dir_info(sctx, dir, dir_gen);
if (IS_ERR(odi)) { if (IS_ERR(odi)) {
ret = PTR_ERR(odi); ret = PTR_ERR(odi);
goto out; goto out;
...@@ -2993,12 +3001,13 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen, ...@@ -2993,12 +3001,13 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
odi->gen = dir_gen; odi->gen = dir_gen;
odi->last_dir_index_offset = found_key.offset; odi->last_dir_index_offset = found_key.offset;
dm->rmdir_ino = dir; dm->rmdir_ino = dir;
dm->rmdir_gen = dir_gen;
ret = 0; ret = 0;
goto out; goto out;
} }
if (loc.objectid > send_progress) { if (loc.objectid > send_progress) {
odi = add_orphan_dir_info(sctx, dir); odi = add_orphan_dir_info(sctx, dir, dir_gen);
if (IS_ERR(odi)) { if (IS_ERR(odi)) {
ret = PTR_ERR(odi); ret = PTR_ERR(odi);
goto out; goto out;
...@@ -3038,6 +3047,7 @@ static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino, bool orphanized) ...@@ -3038,6 +3047,7 @@ static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino, bool orphanized)
return -ENOMEM; return -ENOMEM;
dm->ino = ino; dm->ino = ino;
dm->rmdir_ino = 0; dm->rmdir_ino = 0;
dm->rmdir_gen = 0;
dm->orphanized = orphanized; dm->orphanized = orphanized;
while (*p) { while (*p) {
...@@ -3183,7 +3193,7 @@ static int path_loop(struct send_ctx *sctx, struct fs_path *name, ...@@ -3183,7 +3193,7 @@ static int path_loop(struct send_ctx *sctx, struct fs_path *name,
while (ino != BTRFS_FIRST_FREE_OBJECTID) { while (ino != BTRFS_FIRST_FREE_OBJECTID) {
fs_path_reset(name); fs_path_reset(name);
if (is_waiting_for_rm(sctx, ino)) if (is_waiting_for_rm(sctx, ino, gen))
break; break;
if (is_waiting_for_move(sctx, ino)) { if (is_waiting_for_move(sctx, ino)) {
if (*ancestor_ino == 0) if (*ancestor_ino == 0)
...@@ -3223,6 +3233,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3223,6 +3233,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
u64 parent_ino, parent_gen; u64 parent_ino, parent_gen;
struct waiting_dir_move *dm = NULL; struct waiting_dir_move *dm = NULL;
u64 rmdir_ino = 0; u64 rmdir_ino = 0;
u64 rmdir_gen;
u64 ancestor; u64 ancestor;
bool is_orphan; bool is_orphan;
int ret; int ret;
...@@ -3237,6 +3248,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3237,6 +3248,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
dm = get_waiting_dir_move(sctx, pm->ino); dm = get_waiting_dir_move(sctx, pm->ino);
ASSERT(dm); ASSERT(dm);
rmdir_ino = dm->rmdir_ino; rmdir_ino = dm->rmdir_ino;
rmdir_gen = dm->rmdir_gen;
is_orphan = dm->orphanized; is_orphan = dm->orphanized;
free_waiting_dir_move(sctx, dm); free_waiting_dir_move(sctx, dm);
...@@ -3273,6 +3285,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3273,6 +3285,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
dm = get_waiting_dir_move(sctx, pm->ino); dm = get_waiting_dir_move(sctx, pm->ino);
ASSERT(dm); ASSERT(dm);
dm->rmdir_ino = rmdir_ino; dm->rmdir_ino = rmdir_ino;
dm->rmdir_gen = rmdir_gen;
} }
goto out; goto out;
} }
...@@ -3291,7 +3304,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm) ...@@ -3291,7 +3304,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
struct orphan_dir_info *odi; struct orphan_dir_info *odi;
u64 gen; u64 gen;
odi = get_orphan_dir_info(sctx, rmdir_ino); odi = get_orphan_dir_info(sctx, rmdir_ino, rmdir_gen);
if (!odi) { if (!odi) {
/* already deleted */ /* already deleted */
goto finish; goto finish;
......
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