From d094038f0048267e2d66d881bf6ffc6103ac133d Mon Sep 17 00:00:00 2001 From: Zen Dodd Date: Thu, 5 Nov 2026 07:59:54 +1100 Subject: [PATCH] syscall: skip unsupported symlink chmod --- syscall.c | 2 ++ testsuite/symlink-receiver-perms_test.py | 41 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 testsuite/symlink-receiver-perms_test.py diff --git a/syscall.c b/syscall.c index dd41da845..e0bae3807 100644 --- a/syscall.c +++ b/syscall.c @@ -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 diff --git a/testsuite/symlink-receiver-perms_test.py b/testsuite/symlink-receiver-perms_test.py new file mode 100644 index 000000000..45b1fb6b4 --- /dev/null +++ b/testsuite/symlink-receiver-perms_test.py @@ -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('