Skip to content

--update incorrectly skips update when source is a file and destination is a symlink #827

@Itxaka

Description

@Itxaka

According to the manual for --update:

This forces rsync to skip any files which exist on the destination and have a  modified  time  that  is
newer  than  the  source  file.  (If  an existing destination file has a modification time equal to the
source file's, it will be updated if the sizes are different.)

Note that this does not affect the copying of dirs, symlinks, or other special files.  Also, a  differ‐
ence  of file format between the sender and receiver is always considered to be important enough for an
update, no matter what date is on the objects.  In other words, if the source has a directory where the
destination has a file, the transfer would occur regardless of the timestamps.

From what I understood in there, this means that if files are not the same format, it will always overwrite the destination. In this case, a file and a symlink are not the same format. But this is currently not the case if the destination is a symlink.

You can easily reproduce this with the following code:

rm -rf /tmp/rsynctest_src /tmp/rsynctest_dst

mkdir -p /tmp/rsynctest_src
echo "Hello world" > /tmp/rsynctest_src/foo

mkdir -p /tmp/rsynctest_dst
ln -s /should/not/exist /tmp/rsynctest_dst/foo
touch -h -d 'tomorrow' /tmp/rsynctest_dst/foo


stat /tmp/rsynctest_src/foo
stat /tmp/rsynctest_dst/foo


rsync -auvvv /tmp/rsynctest_src/ /tmp/rsynctest_dst/

stat /tmp/rsynctest_dst/foo
ls -l /tmp/rsynctest_dst/foo
file /tmp/rsynctest_dst/foo

From this I would expect the destination file to be overwritten as its not the same format, but it is fully skipped:

sending incremental file list
[sender] make_file(.,*,0)
[sender] pushing local filters for /tmp/rsynctest_src/
[sender] make_file(foo,*,2)
send_file_list done
send_files starting
server_recv(2) starting pid=3773048
recv_file_name(.)
recv_file_name(foo)
received 2 names
recv_file_list done
get_local_name count=2 /tmp/rsynctest_dst/
generator starting pid=3773048
delta-transmission disabled for local transfer or --whole-file
recv_generator(.,0)
recv_generator(.,1)
recv_generator(foo,2)
foo is newer
send_files(0, /tmp/rsynctest_src/.)
recv_files(2) starting
recv_files(.)
generate_files phase=1
send_files phase=1
recv_files phase=1
generate_files phase=2
send_files phase=2
send files finished
total: matches=0  hash_hits=0  false_alarms=0 data=0
recv_files phase=2
recv_files finished
generate_files phase=3
generate_files finished

sent 80 bytes  received 512 bytes  1.184,00 bytes/sec
total size is 12  speedup is 0,02
[sender] _exit_cleanup(code=0, file=main.c, line=1338): about to call exit(0)

But somehow, doing it the other way does respect what the manual says. Doing source symlink and destination file, does overwrite, even if destination has a newer mtime:

rm -rf /tmp/rsynctest_src /tmp/rsynctest_dst

mkdir -p /tmp/rsynctest_src
ln -s /some/target/path /tmp/rsynctest_src/foo

mkdir -p /tmp/rsynctest_dst
echo "old data" > /tmp/rsynctest_dst/foo
touch -d 'tomorrow' /tmp/rsynctest_dst/foo

# Confirm initial file types:
stat /tmp/rsynctest_src/foo
stat /tmp/rsynctest_dst/foo

# Run rsync:
rsync -auvvv /tmp/rsynctest_src/ /tmp/rsynctest_dst/

stat /tmp/rsynctest_dst/foo
ls -l /tmp/rsynctest_dst/foo
file /tmp/rsynctest_dst/foo
sending incremental file list
[sender] make_file(.,*,0)
[sender] pushing local filters for /tmp/rsynctest_src/
[sender] make_file(foo,*,2)
send_file_list done
send_files starting
server_recv(2) starting pid=3772021
recv_file_name(.)
recv_file_name(foo)
received 2 names
recv_file_list done
get_local_name count=2 /tmp/rsynctest_dst/
generator starting pid=3772021
delta-transmission disabled for local transfer or --whole-file
recv_generator(.,0)
recv_generator(.,1)
recv_generator(foo,2)
send_files(0, /tmp/rsynctest_src/.)
send_files(2, /tmp/rsynctest_src/foo)
foo -> /some/target/path
recv_files(2) starting
recv_files(.)
recv_files(foo)
generate_files phase=1
send_files phase=1
recv_files phase=1
generate_files phase=2
send_files phase=2
send files finished
total: matches=0  hash_hits=0  false_alarms=0 data=0
recv_files phase=2
recv_files finished
generate_files phase=3
generate_files finished

So Im not sure whats the correct behavior here, I would expect what the docs say but the reality differs.

Not sure if this is a bug or I'm doing something wrong?

rsync  version 3.4.1  protocol version 32
Copyright (C) 1996-2025 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, symlinks, symtimes, hardlinks, hardlink-specials,
    hardlink-symlinks, IPv6, atimes, batchfiles, inplace, append, ACLs,
    xattrs, optional secluded-args, iconv, prealloc, stop-at, no crtimes
Optimizations:
    SIMD-roll, no asm-roll, openssl-crypto, no asm-MD5
Checksum list:
    xxh128 xxh3 xxh64 (xxhash) md5 md4 sha1 none
Compress list:
    zstd lz4 zlibx zlib none
Daemon auth list:
    sha512 sha256 sha1 md5 md4

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.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions