test_fail(f"{_tag(label)}{path} exists but should not")
+_rb_cache = None
+
+
+def resolve_beneath_supported() -> bool:
+ """True if this rsync can FOLLOW an in-tree directory symlink under its
+ secure resolver -- i.e. update a file through a dir-symlink on the receiver
+ (--keep-dirlinks; issue #715).
+
+ False wherever the portable per-component O_NOFOLLOW fallback is the active
+ resolver: a platform with no kernel "beneath" primitive, Linux < 5.6, a
+ seccomp-blocked openat2, or a --disable-openat2 build. There the delta
+ update through the symlinked directory fails verification. Probed
+ functionally (an initial transfer plus a delta update through a dir-symlink)
+ so it tracks the actual binary rather than a platform name, and cached."""
+ global _rb_cache
+ if _rb_cache is not None:
+ return _rb_cache
+ probe = SCRATCHDIR / '.rb_probe'
+ rmtree(probe)
+ (probe / 'home' / 'real').mkdir(parents=True)
+ os.symlink('real', probe / 'home' / 'link')
+ (probe / 'src' / 'link').mkdir(parents=True)
+ f = probe / 'src' / 'link' / 'f'
+ make_data_file(f, 40000)
+
+ def push():
+ subprocess.run(
+ rsync_argv('-KRl', '--no-whole-file', 'link/f',
+ f"{probe / 'home'}/"),
+ cwd=str(probe / 'src'),
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ push()
+ with open(f, 'ab') as fh: # size change -> forces a delta update
+ fh.write(b'appended tail for delta\n')
+ push()
+ dst = probe / 'home' / 'real' / 'f'
+ _rb_cache = dst.is_file() and filecmp.cmp(str(f), str(dst), shallow=False)
+ rmtree(probe)
+ return _rb_cache
+
+
def write_daemon_conf(modules, globals=None, *,
name: str = 'test-rsyncd.conf') -> 'Path':
"""Write a custom rsyncd.conf for daemon-parameter tests.
import filecmp
import os
-import platform
import subprocess
import time
from rsyncfns import (
RSYNC, SCRATCHDIR, SRCDIR, TMPDIR,
- make_data_file, rsync_argv, test_fail, test_skipped,
+ make_data_file, resolve_beneath_supported, rsync_argv, test_fail,
+ test_skipped,
)
-if platform.system() in ('SunOS', 'OpenBSD', 'NetBSD') or platform.system().startswith('CYGWIN'):
+# Following an in-tree directory symlink under the secure resolver needs a
+# kernel "beneath" primitive (RESOLVE_BENEATH/O_RESOLVE_BENEATH). Probe the
+# actual binary rather than guessing from the platform name: this also skips
+# correctly on Linux < 5.6, a seccomp-blocked openat2, and a --disable-openat2
+# build, where the portable O_NOFOLLOW fallback rejects the in-tree symlink
+# (issue #715).
+if not resolve_beneath_supported():
test_skipped(
- f"secure_relative_open lacks RESOLVE_BENEATH equivalent on "
- f"{platform.system()}; issue #715 still affects this platform"
+ "this rsync can't follow an in-tree directory symlink under its "
+ "secure resolver (no RESOLVE_BENEATH equivalent / --disable-openat2); "
+ "issue #715 still affects this configuration"
)
os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')