mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-01 02:37:19 -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>
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Coverage of -S (--sparse) at depth.
|
|
|
|
A file with a large hole, several directory levels deep, should arrive sparse
|
|
(its allocated blocks much smaller than its apparent size) and byte-identical
|
|
when copied with -S.
|
|
|
|
We do NOT assert the converse (that a plain copy fills the hole): whether a
|
|
zero-run is stored as a hole when -S is absent is the filesystem's choice, not
|
|
rsync's -- ZFS/APFS transparently sparsify zero blocks, so a --no-sparse copy
|
|
can legitimately stay sparse there.
|
|
"""
|
|
|
|
import os
|
|
|
|
from rsyncfns import (
|
|
FROMDIR, TODIR,
|
|
assert_same, makepath, rmtree, run_rsync, test_fail, test_skipped,
|
|
)
|
|
|
|
src = FROMDIR
|
|
deep = os.path.join('d1', 'd2', 'd3', 'holey')
|
|
SIZE = 4 * 1024 * 1024
|
|
|
|
|
|
def make_sparse(path):
|
|
with open(path, 'wb') as f:
|
|
f.write(b'head')
|
|
f.seek(SIZE - 4)
|
|
f.write(b'tail')
|
|
|
|
|
|
def allocated(path):
|
|
return os.stat(path).st_blocks * 512
|
|
|
|
|
|
rmtree(src)
|
|
rmtree(TODIR)
|
|
makepath(src / 'd1' / 'd2' / 'd3')
|
|
make_sparse(src / deep)
|
|
|
|
# Confirm the source filesystem actually made a sparse file; otherwise the
|
|
# whole premise (and any dest comparison) is meaningless here.
|
|
if allocated(src / deep) >= SIZE:
|
|
test_skipped("source filesystem did not create a sparse file")
|
|
|
|
# --- with -S the hole is preserved at the destination -----------------------
|
|
run_rsync('-a', '-S', f'{src}/', f'{TODIR}/')
|
|
assert_same(TODIR / deep, src / deep, label='-S content')
|
|
if allocated(TODIR / deep) >= SIZE:
|
|
test_fail(f"-S did not preserve the hole at depth "
|
|
f"(allocated {allocated(TODIR / deep)} for a {SIZE}-byte file)")
|
|
|
|
# --- a plain copy reproduces the content too (allocation is FS-defined) ------
|
|
rmtree(TODIR)
|
|
run_rsync('-a', '--no-sparse', f'{src}/', f'{TODIR}/')
|
|
assert_same(TODIR / deep, src / deep, label='no-sparse content')
|
|
|
|
print("sparse: -S preserves a deep hole; content correct with and without it")
|