fix(pm): retain chained library aliases on no-symlink hosts
Resolve deferred relative file links until no further alias can be materialized. Keep containment checks and leave unresolved cross-package copyright links alone.
This commit is contained in:
@@ -288,14 +288,23 @@ class DebPackage(Package):
|
||||
target.chmod(member.mode & 0o777)
|
||||
except OSError:
|
||||
pass
|
||||
for _linkname, target, resolved_path in deferred_links:
|
||||
if not _contained(resolved_path):
|
||||
raise InstallError(
|
||||
self.name,
|
||||
f"symlink {target.name} resolves outside the staged tree",
|
||||
)
|
||||
if not (target.exists() or target.is_symlink()) and resolved_path.is_file():
|
||||
shutil.copy2(resolved_path, target)
|
||||
while deferred_links:
|
||||
pending = []
|
||||
for linkname, target, resolved_path in deferred_links:
|
||||
if not _contained(resolved_path):
|
||||
raise InstallError(
|
||||
self.name,
|
||||
f"symlink {target.name} resolves outside the staged tree",
|
||||
)
|
||||
if target.exists() or target.is_symlink():
|
||||
continue
|
||||
if resolved_path.is_file():
|
||||
shutil.copy2(resolved_path, target)
|
||||
else:
|
||||
pending.append((linkname, target, resolved_path))
|
||||
if len(pending) == len(deferred_links):
|
||||
break
|
||||
deferred_links = pending
|
||||
# Termux debs carry owner-only modes across the whole tree (700 on
|
||||
# binaries n libs, 600 on stdlib .py files) -- postinst would
|
||||
# normalize on a real phone, but pm extracts without postinst, and
|
||||
|
||||
@@ -65,6 +65,25 @@ def _symlink_member(name: str, linkname: str) -> tarfile.TarInfo:
|
||||
return info
|
||||
|
||||
|
||||
def test_chained_aliases_survive_without_symlink_support(tmp_path, monkeypatch):
|
||||
def unavailable(*args, **kwargs):
|
||||
raise OSError("symlinks unavailable")
|
||||
|
||||
monkeypatch.setattr(Path, "symlink_to", unavailable)
|
||||
deb = tmp_path / "aliases.deb"
|
||||
lib = "data/data/com.termux/files/usr/lib"
|
||||
_build_deb(deb, [
|
||||
_symlink_member(f"{lib}/libexample.so", "libexample.so.1"),
|
||||
_symlink_member(f"{lib}/libexample.so.1", "libexample.so.1.2"),
|
||||
(f"{lib}/libexample.so.1.2", b"library payload"),
|
||||
])
|
||||
staged = tmp_path / "staged"
|
||||
staged.mkdir()
|
||||
_P().unpack(deb, staged, "linux-arm64-bionic")
|
||||
for name in ("libexample.so", "libexample.so.1", "libexample.so.1.2"):
|
||||
assert (staged / lib / name).read_bytes() == b"library payload"
|
||||
|
||||
|
||||
def test_absolute_symlink_target_rejected(tmp_path: Path):
|
||||
"""A member whose symlink target is ABSOLUTE must be refused outright:
|
||||
the link points outside the staged tree the moment it is created."""
|
||||
|
||||
Reference in New Issue
Block a user