btrfs: send: fix wrong file path when there is an inode with a pending rmdir
commit 0b3f407e6728d990ae1630a02c7b952c21c288d3 upstream. 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: Massimo B. <massimo.b@gmx.net> Tested-by: Massimo 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: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
0cb0b876f1
commit
64d06c7f2f
@ -238,6 +238,7 @@ struct waiting_dir_move {
|
||||
* after this directory is moved, we can try to rmdir the ino rmdir_ino.
|
||||
*/
|
||||
u64 rmdir_ino;
|
||||
u64 rmdir_gen;
|
||||
bool orphanized;
|
||||
};
|
||||
|
||||
@ -323,7 +324,7 @@ static int is_waiting_for_move(struct send_ctx *sctx, u64 ino);
|
||||
static struct waiting_dir_move *
|
||||
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)
|
||||
{
|
||||
@ -2306,7 +2307,7 @@ static int get_cur_path(struct send_ctx *sctx, u64 ino, u64 gen,
|
||||
|
||||
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);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
@ -2865,8 +2866,8 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct orphan_dir_info *
|
||||
add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
|
||||
static struct orphan_dir_info *add_orphan_dir_info(struct send_ctx *sctx,
|
||||
u64 dir_ino, u64 dir_gen)
|
||||
{
|
||||
struct rb_node **p = &sctx->orphan_dirs.rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
@ -2875,20 +2876,23 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
entry = rb_entry(parent, struct orphan_dir_info, node);
|
||||
if (dir_ino < entry->ino) {
|
||||
if (dir_ino < entry->ino)
|
||||
p = &(*p)->rb_left;
|
||||
} else if (dir_ino > entry->ino) {
|
||||
else if (dir_ino > entry->ino)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
odi = kmalloc(sizeof(*odi), GFP_KERNEL);
|
||||
if (!odi)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
odi->ino = dir_ino;
|
||||
odi->gen = 0;
|
||||
odi->gen = dir_gen;
|
||||
odi->last_dir_index_offset = 0;
|
||||
|
||||
rb_link_node(&odi->node, parent, p);
|
||||
@ -2896,8 +2900,8 @@ add_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
|
||||
return odi;
|
||||
}
|
||||
|
||||
static struct orphan_dir_info *
|
||||
get_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
|
||||
static struct orphan_dir_info *get_orphan_dir_info(struct send_ctx *sctx,
|
||||
u64 dir_ino, u64 gen)
|
||||
{
|
||||
struct rb_node *n = sctx->orphan_dirs.rb_node;
|
||||
struct orphan_dir_info *entry;
|
||||
@ -2908,15 +2912,19 @@ get_orphan_dir_info(struct send_ctx *sctx, u64 dir_ino)
|
||||
n = n->rb_left;
|
||||
else if (dir_ino > entry->ino)
|
||||
n = n->rb_right;
|
||||
else if (gen < entry->gen)
|
||||
n = n->rb_left;
|
||||
else if (gen > entry->gen)
|
||||
n = n->rb_right;
|
||||
else
|
||||
return entry;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -2961,7 +2969,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
|
||||
key.type = BTRFS_DIR_INDEX_KEY;
|
||||
key.offset = 0;
|
||||
|
||||
odi = get_orphan_dir_info(sctx, dir);
|
||||
odi = get_orphan_dir_info(sctx, dir, dir_gen);
|
||||
if (odi)
|
||||
key.offset = odi->last_dir_index_offset;
|
||||
|
||||
@ -2992,7 +3000,7 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
|
||||
|
||||
dm = get_waiting_dir_move(sctx, loc.objectid);
|
||||
if (dm) {
|
||||
odi = add_orphan_dir_info(sctx, dir);
|
||||
odi = add_orphan_dir_info(sctx, dir, dir_gen);
|
||||
if (IS_ERR(odi)) {
|
||||
ret = PTR_ERR(odi);
|
||||
goto out;
|
||||
@ -3000,12 +3008,13 @@ static int can_rmdir(struct send_ctx *sctx, u64 dir, u64 dir_gen,
|
||||
odi->gen = dir_gen;
|
||||
odi->last_dir_index_offset = found_key.offset;
|
||||
dm->rmdir_ino = dir;
|
||||
dm->rmdir_gen = dir_gen;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
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)) {
|
||||
ret = PTR_ERR(odi);
|
||||
goto out;
|
||||
@ -3045,6 +3054,7 @@ static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino, bool orphanized)
|
||||
return -ENOMEM;
|
||||
dm->ino = ino;
|
||||
dm->rmdir_ino = 0;
|
||||
dm->rmdir_gen = 0;
|
||||
dm->orphanized = orphanized;
|
||||
|
||||
while (*p) {
|
||||
@ -3190,7 +3200,7 @@ static int path_loop(struct send_ctx *sctx, struct fs_path *name,
|
||||
while (ino != BTRFS_FIRST_FREE_OBJECTID) {
|
||||
fs_path_reset(name);
|
||||
|
||||
if (is_waiting_for_rm(sctx, ino))
|
||||
if (is_waiting_for_rm(sctx, ino, gen))
|
||||
break;
|
||||
if (is_waiting_for_move(sctx, ino)) {
|
||||
if (*ancestor_ino == 0)
|
||||
@ -3230,6 +3240,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
|
||||
u64 parent_ino, parent_gen;
|
||||
struct waiting_dir_move *dm = NULL;
|
||||
u64 rmdir_ino = 0;
|
||||
u64 rmdir_gen;
|
||||
u64 ancestor;
|
||||
bool is_orphan;
|
||||
int ret;
|
||||
@ -3244,6 +3255,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
|
||||
dm = get_waiting_dir_move(sctx, pm->ino);
|
||||
ASSERT(dm);
|
||||
rmdir_ino = dm->rmdir_ino;
|
||||
rmdir_gen = dm->rmdir_gen;
|
||||
is_orphan = dm->orphanized;
|
||||
free_waiting_dir_move(sctx, dm);
|
||||
|
||||
@ -3280,6 +3292,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
|
||||
dm = get_waiting_dir_move(sctx, pm->ino);
|
||||
ASSERT(dm);
|
||||
dm->rmdir_ino = rmdir_ino;
|
||||
dm->rmdir_gen = rmdir_gen;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
@ -3298,7 +3311,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
|
||||
struct orphan_dir_info *odi;
|
||||
u64 gen;
|
||||
|
||||
odi = get_orphan_dir_info(sctx, rmdir_ino);
|
||||
odi = get_orphan_dir_info(sctx, rmdir_ino, rmdir_gen);
|
||||
if (!odi) {
|
||||
/* already deleted */
|
||||
goto finish;
|
||||
|
Loading…
x
Reference in New Issue
Block a user