lkml.org 
[lkml]   [2021]   [Jul]   [12]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.13 048/800] btrfs: send: fix invalid path for unlink operations after parent orphanization
    Date
    From: Filipe Manana <fdmanana@suse.com>

    commit d8ac76cdd1755b21e8c008c28d0b7251c0b14986 upstream.

    During an incremental send operation, when processing the new references
    for the current inode, we might send an unlink operation for another inode
    that has a conflicting path and has more than one hard link. However this
    path was computed and cached before we processed previous new references
    for the current inode. We may have orphanized a directory of that path
    while processing a previous new reference, in which case the path will
    be invalid and cause the receiver process to fail.

    The following reproducer triggers the problem and explains how/why it
    happens in its comments:

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

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

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

    # Create our test files and directory. Inode 259 (file3) has two hard
    # links.
    touch $MNT/file1
    touch $MNT/file2
    touch $MNT/file3

    mkdir $MNT/A
    ln $MNT/file3 $MNT/A/hard_link

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

    # Now create the base snapshot, which is going to be the parent snapshot
    # for a later incremental send.
    btrfs subvolume snapshot -r $MNT $MNT/snap1
    btrfs send -f /tmp/snap1.send $MNT/snap1

    # Move inode 257 into directory inode 260. This results in computing the
    # path for inode 260 as "/A" and caching it.
    mv $MNT/file1 $MNT/A/file1

    # Move inode 258 (file2) into directory inode 260, with a name of
    # "hard_link", moving first inode 259 away since it currently has that
    # location and name.
    mv $MNT/A/hard_link $MNT/tmp
    mv $MNT/file2 $MNT/A/hard_link

    # Now rename inode 260 to something else (B for example) and then create
    # a hard link for inode 258 that has the old name and location of inode
    # 260 ("/A").
    mv $MNT/A $MNT/B
    ln $MNT/B/hard_link $MNT/A

    # Filesystem now looks like:
    #
    # . (ino 256)
    # |----- tmp (ino 259)
    # |----- file3 (ino 259)
    # |----- B/ (ino 260)
    # | |---- file1 (ino 257)
    # | |---- hard_link (ino 258)
    # |
    # |----- A (ino 258)

    # Create another snapshot of our subvolume and use it for an incremental
    # send.
    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

    # First add the first snapshot to the new filesystem by applying the
    # first send stream.
    btrfs receive -f /tmp/snap1.send $MNT

    # The incremental receive operation below used to fail with the
    # following error:
    #
    # ERROR: unlink A/hard_link failed: No such file or directory
    #
    # This is because when send is processing inode 257, it generates the
    # path for inode 260 as "/A", since that inode is its parent in the send
    # snapshot, and caches that path.
    #
    # Later when processing inode 258, it first processes its new reference
    # that has the path of "/A", which results in orphanizing inode 260
    # because there is a a path collision. This results in issuing a rename
    # operation from "/A" to "/o260-6-0".
    #
    # Finally when processing the new reference "B/hard_link" for inode 258,
    # it notices that it collides with inode 259 (not yet processed, because
    # it has a higher inode number), since that inode has the name
    # "hard_link" under the directory inode 260. It also checks that inode
    # 259 has two hardlinks, so it decides to issue a unlink operation for
    # the name "hard_link" for inode 259. However the path passed to the
    # unlink operation is "/A/hard_link", which is incorrect since currently
    # "/A" does not exists, due to the orphanization of inode 260 mentioned
    # before. The path is incorrect because it was computed and cached
    # before the orphanization. This results in the receiver to fail with
    # the above error.
    btrfs receive -f /tmp/snap2.send $MNT

    umount $MNT

    When running the test, it fails like this:

    $ ./test-send-unlink.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: unlink A/hard_link failed: No such file or directory

    Fix this by recomputing a path before issuing an unlink operation when
    processing the new references for the current inode if we previously
    have orphanized a directory.

    A test case for fstests will follow soon.

    CC: stable@vger.kernel.org # 4.4+
    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>

    ---
    fs/btrfs/send.c | 11 +++++++++++
    1 file changed, 11 insertions(+)

    --- a/fs/btrfs/send.c
    +++ b/fs/btrfs/send.c
    @@ -4064,6 +4064,17 @@ static int process_recorded_refs(struct
    if (ret < 0)
    goto out;
    } else {
    + /*
    + * If we previously orphanized a directory that
    + * collided with a new reference that we already
    + * processed, recompute the current path because
    + * that directory may be part of the path.
    + */
    + if (orphanized_dir) {
    + ret = refresh_ref_path(sctx, cur);
    + if (ret < 0)
    + goto out;
    + }
    ret = send_unlink(sctx, cur->full_path);
    if (ret < 0)
    goto out;

    \
     
     \ /
      Last update: 2021-07-12 10:11    [W:4.184 / U:1.172 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site