https://github.com/RsyncProject/rsync/commit/ea645201fefbba7f6b6b22b621856dba3c01cdd3

From ea645201fefbba7f6b6b22b621856dba3c01cdd3 Mon Sep 17 00:00:00 2001
From: Zen Dodd <mail@steadytao.com>
Date: Fri, 6 Nov 2026 20:51:34 +1000
Subject: [PATCH] syscall: skip unsupported symlink chmod (#1148)

---
 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('<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')

