Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -1806,6 +1806,8 @@ int do_chmod(const char *path, mode_t mode)
code = 1;
# elif defined AT_FDCWD && defined AT_SYMLINK_NOFOLLOW
code = fchmodat(AT_FDCWD, path, mode & CHMOD_BITS, AT_SYMLINK_NOFOLLOW);
if (code < 0 && (errno == ENOTSUP || errno == EOPNOTSUPP || errno == ENOSYS))
code = 1;
# else
code = 1;
# endif
Expand Down
41 changes: 41 additions & 0 deletions testsuite/symlink-receiver-perms_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3

import os
import stat
import struct

from rsyncfns import SCRATCHDIR, makepath, run_rsync, test_fail

source = SCRATCHDIR / 'source'
destination = SCRATCHDIR / 'destination'
makepath(source)
makepath(destination)
target = destination / 'target'
target.write_text('target contents\n')
target.chmod(0o640)
links = [('link', 'target'), ('dangling', 'missing')]
for name, link in links:
os.symlink(link, source / name)

batch = SCRATCHDIR / 'symlinks.batch'
run_rsync('-rlp', '--checksum-seed=1', f'--only-write-batch={batch}',
f'{source}/', f'{destination}/')

# Supply BSD-style link modes without relying on the source filesystem
mode = stat.S_IMODE((source / 'link').lstat().st_mode)
wanted = 0o400 if mode != 0o400 else 0o755
encoded_mode = struct.pack('<I', 0o120000 | mode)
data = batch.read_bytes()
if data.count(encoded_mode) != 1:
test_fail('expected one shared symlink mode in the batch')
batch.write_bytes(data.replace(encoded_mode, struct.pack('<I', 0o120000 | wanted)))

for _ in range(2):
run_rsync('-rlp', f'--read-batch={batch}', f'{destination}/')
for name, link in links:
if not (destination / name).is_symlink() or os.readlink(destination / name) != link:
test_fail(f'{name}: symlink was not preserved')
if stat.S_IMODE(target.stat().st_mode) != 0o640 or target.read_text() != 'target contents\n':
test_fail('symlink chmod changed the target')
if (destination / 'missing').exists():
test_fail('dangling symlink target was created')
Loading