mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-16 18:10:55 -04:00
Set each attribute distinctively on a file AND a directory at every level of a
>=3-deep tree and verify it per entry after transfer (metadata is applied as a
single-component op on an entry whose parent chain the resolver restructure
rewrites):
metadata-depth -p preserves exact file/dir modes; -t preserves file
mtimes; --chmod=D710,F600 rewrites them.
omit-times -O omits directory times (files still preserved); -J omits
symlink times.
sparse -S preserves a deep file's hole (allocated << size);
--no-sparse fills it.
xattrs-depth -X reproduces a user xattr on every entry (gated on xattr
support).
acls-depth -A reproduces a POSIX ACL on every entry (gated on ACL
support + setfacl/getfacl).
ownership-depth --groupmap and --chown=:GROUP remap the group of every
entry (non-root, to a secondary group); -o/--usermap gated
on root.
All green on master and under --protocol=29/30.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Coverage of -A (acls) at depth.
|
|
|
|
acls_test.py exercises a shallow tree; this companion sets a distinctive POSIX
|
|
ACL on a file AND a directory at every level of a >=3-deep tree and checks that
|
|
-A reproduces them (the ACL is applied per entry, on a name whose parent chain
|
|
the resolver restructure rewrites).
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
|
|
from rsyncfns import (
|
|
FROMDIR, TODIR,
|
|
make_tree, rmtree, run_rsync, test_fail, test_skipped,
|
|
walk_dirs, walk_files,
|
|
)
|
|
|
|
vv = run_rsync('-VV', check=True, capture_output=True).stdout
|
|
if '"ACLs": true' not in vv:
|
|
test_skipped("rsync built without ACL support")
|
|
if not (shutil.which('setfacl') and shutil.which('getfacl')):
|
|
test_skipped("setfacl/getfacl not available")
|
|
|
|
src = FROMDIR
|
|
rmtree(src)
|
|
rmtree(TODIR)
|
|
make_tree(src, depth=3)
|
|
|
|
entries = [p.relative_to(src) for p in (walk_dirs(src) + walk_files(src))]
|
|
entries.sort()
|
|
|
|
# Grant uid 0 an explicit r-x ACL on every entry (valid for a non-root owner to
|
|
# set on its own files; needs no extra accounts).
|
|
for rel in entries:
|
|
r = subprocess.run(['setfacl', '-m', 'u:0:r-x', str(src / rel)])
|
|
if r.returncode != 0:
|
|
test_skipped("filesystem does not support setting ACLs")
|
|
|
|
|
|
def getfacl(path):
|
|
# Strip the comment header (# file:/# owner:/# group:) so the comparison
|
|
# is path-independent; this getfacl doesn't accept the GNU -c flag.
|
|
out = subprocess.check_output(['getfacl', str(path)], text=True,
|
|
stderr=subprocess.DEVNULL)
|
|
return ''.join(ln for ln in out.splitlines(keepends=True)
|
|
if not ln.startswith('#'))
|
|
|
|
|
|
run_rsync('-aA', f'{src}/', f'{TODIR}/')
|
|
|
|
for rel in entries:
|
|
want = getfacl(src / rel)
|
|
got = getfacl(TODIR / rel)
|
|
# getfacl renders uid 0 as "root"; accept either spelling.
|
|
if 'user:root:r-x' not in got and 'user:0:r-x' not in got:
|
|
test_fail(f"-A did not reproduce the named-user ACL on {rel}:\n{got}")
|
|
if want != got:
|
|
test_fail(f"-A: ACL of {rel} differs\n--- source ---\n{want}"
|
|
f"--- dest ---\n{got}")
|
|
|
|
print("acls-depth: -A reproduced a POSIX ACL on every entry at depth")
|