Bug 13991 - rsync --delete --one-file-system skips deletes after crossing filesystems on destination.
Summary: rsync --delete --one-file-system skips deletes after crossing filesystems on ...
Alias: None
Product: rsync
Classification: Unclassified
Component: core (show other bugs)
Version: 3.1.3
Hardware: All Linux
: P5 normal (vote)
Target Milestone: ---
Assignee: Wayne Davison
QA Contact: Rsync QA Contact
Depends on:
Reported: 2019-06-09 21:04 UTC by Bruce Arden
Modified: 2020-06-13 00:54 UTC (History)
0 users

See Also:

Commands to demonstrate further problem (1.25 KB, text/plain)
2019-06-10 08:22 UTC, Bruce Arden
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Bruce Arden 2019-06-09 21:04:30 UTC
When using rsync with the -x (or --one-file-system) option to sync to a destination that crosses filesystems: --delete won't delete files in the second filesystem.

Example done on a Centos7 machine where deleteme1 is left in the destination after deleting from the source:


root@arden-lt ~]# ## Setup environment in lvm
root@arden-lt ~]# cd /tmp
[root@arden-lt tmp]# VG=vg_ardenkvm

[root@arden-lt tmp]# mkdir testsrc testdst
[root@arden-lt tmp]# lvcreate -l 1 $VG/lvtest1
  Volume group name expected (no slash)
  Run `lvcreate --help' for more information.
[root@arden-lt tmp]# lvcreate -l 1 -n lvtest1 $VG
  Logical volume "lvtest1" created.
[root@arden-lt tmp]# mkfs /dev/$VG/lvtest1
mke2fs 1.42.9 (28-Dec-2013)
Discarding device blocks: done                           
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
1024 inodes, 4096 blocks
204 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=4194304
1 block group
8192 blocks per group, 8192 fragments per group
1024 inodes per group

Allocating group tables: done                           
Writing inode tables: done                           
Writing superblocks and filesystem accounting information: done

[root@arden-lt tmp]# mkdir testdst/test1
[root@arden-lt tmp]# mount /dev/$VG/lvtest1 testdst/test1

[root@arden-lt tmp]# mkdir testsrc/test1
[root@arden-lt tmp]# touch testsrc/deleteme testsrc/test1/deleteme1

[root@arden-lt tmp]# ## First sync with no problems

[root@arden-lt tmp]# rsync -axv --del testsrc/. testdst/.
sending incremental file list

sent 213 bytes  received 69 bytes  564.00 bytes/sec
total size is 0  speedup is 0.00
[root@arden-lt tmp]# \rm testsrc/deleteme testsrc/test1/deleteme1

[root@arden-lt tmp]# ## Second sync that should delete files

[root@arden-lt tmp]# rsync -axv --del testsrc/. testdst/.
sending incremental file list
deleting deleteme

sent 72 bytes  received 31 bytes  206.00 bytes/sec
total size is 0  speedup is 0.00

[root@arden-lt tmp]# ## Files on src

[root@arden-lt tmp]# find testsrc -print

[root@arden-lt tmp]# ## Files on dest that should be the same as src, 
[root@arden-lt tmp]# ## but deleteme1 is still there

[root@arden-lt tmp]# find testdst -print

[root@arden-lt tmp]# ## Without -x it works as expected

[root@arden-lt tmp]# rsync -av --del testsrc/. testdst/.
sending incremental file list
deleting test1/lost+found/
deleting test1/deleteme1

sent 66 bytes  received 53 bytes  238.00 bytes/sec
total size is 0  speedup is 0.00
[root@arden-lt tmp]# find testdst -print

[root@arden-lt tmp]# rsync --version
rsync  version 3.1.3  protocol version 31
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
    append, no ACLs, xattrs, iconv, symtimes, prealloc

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.
[root@arden-lt tmp]# 

[OK it's not very good removing lost+found, but that's not the problem].

I think the problem is the code in delete_in_dir() in generator.c:
    if (one_file_system) {
        if (file->flags & FLAG_TOP_DIR)
            filesystem_dev = *fs_dev;
        else if (filesystem_dev != *fs_dev)
As far as I can tell this is unnecessary. --one-file-system should only check the source filesystems not the destination filesystems.

This code can cause worse problems too.  I haven't figured out the simplest way of demonstrating it.  But if there are other filesystems mounted, deletes can fail at the higher level as well - contact me if you want details.
Comment 1 Bruce Arden 2019-06-10 08:22:29 UTC
Created attachment 15235 [details]
Commands to demonstrate further problem

set VG to an LVM volumegroup with spare space

On my machine this gives:
[root@arden-lt tmp]# /bin/rsync -axv --del testsrc/. testsrc/test1 testdst/.
sending incremental file list
deleting deleteme
deleting test1/deleteme1

sent 154 bytes  received 59 bytes  426.00 bytes/sec
total size is 0  speedup is 0.00
[root@arden-lt tmp]# find testsrc -type f -print
[root@arden-lt tmp]# find testdst -type f -print
[root@arden-lt tmp]# 

test1/test11/deleteme11 and test2/deleteme2 are left in the destination
Note that test2/deleteme2 is in the original file system
Comment 2 Wayne Davison 2020-06-13 00:54:01 UTC
Yeah, the behavior when recursing back up out of a mount dir into a dir that is a part of a prior top-dir's tree is not handled right at all.  Your attached test case was a nice help for testing that (though I just used bind mounts from dirs under /dev/shm instead of creating LVM mounts).

I added some code to the delete_in_dir() function to make it keep track of the device values properly and committed it to git.

As for the initial report, the -x option is documented as halting deletions under a mounted dir on the receiving side, so that is how it is supposed to work.  If you want a different behavior you have several options:

You might be able to use --no-x -M-x (on a pull) or -x -M-no-x (on a push) if you want -x on just the sending side. However, I have not tried that (and a local copy would need some kind of remote-shell usage so that the 2 sides can a different options, which the support/lsh script is useful in supporting).

A better approach might be to parse the contents of /proc/mounts and turn mount points into filter rules. You then have full control over which dirs you want to exclude, hide, or protect with no guessing.  You can even put rules into per-dir filter files if the client and sender side need different rules (and one side may not know what the other side needs).