Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions extcli/src/rootfs/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"""

import os
import posixpath
import tarfile
import time

Expand Down Expand Up @@ -49,9 +50,11 @@ def measure(root):
def archive(root, target, on_progress=None):
"""Writes the container to `target`. Returns (ok, detail).

Symlinks are stored as symlinks, not followed: a rootfs is full of
absolute ones pointing at its own `/`, and following them would both
inflate the archive and bake this phone's paths into it.
Symlinks are stored as symlinks, not followed. Absolute links that point
at the rootfs's own `/` are made relative: they still resolve to the same
member after restore, but archives also unpack with Python's safe default
extraction filter. Following links would both inflate the archive and
bake this phone's paths into it.
"""
if not os.path.isdir(root):
return False, "there is no container to export"
Expand All @@ -68,7 +71,7 @@ def entries():
for path in entries():
try:
archive_file.add(path, arcname=os.path.relpath(path, root),
recursive=False)
recursive=False, filter=_portable_link)
except (OSError, ValueError) as e:
# a socket, or something that went away mid-walk; the rest
# of the container is still worth having
Expand All @@ -92,3 +95,18 @@ def entries():
except Exception:
pass
return True, target


def _portable_link(info):
"""Return a tar member whose rootfs-local absolute link is relative.

Alpine uses links such as ``/bin/busybox`` to mean a file below its own
root, not a file in the Android host filesystem. A relative target has
that same meaning once the archive is restored and, unlike an absolute
target, is accepted by :mod:`tarfile`'s data filter.
"""
if info.issym() and info.linkname.startswith("/"):
info.linkname = posixpath.relpath(
info.linkname.lstrip("/"), posixpath.dirname(info.name) or "."
)
return info
5 changes: 2 additions & 3 deletions tests/test_rootfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2573,8 +2573,7 @@ def test_the_archive_holds_the_container(tmp_path):


def test_a_symlink_is_stored_as_itself(tmp_path):
"""Following them would bake this phone's paths into the archive, and
inflate it with a copy of every file a link points at."""
"""Links remain links, but rootfs-local absolute targets become portable."""
import tarfile

from extcli_src.rootfs import export
Expand All @@ -2585,7 +2584,7 @@ def test_a_symlink_is_stored_as_itself(tmp_path):
with tarfile.open(target) as archive:
link = archive.getmember("bin/sh")
assert link.issym()
assert link.linkname == "/bin/busybox"
assert link.linkname == "busybox"


def test_the_archive_can_be_unpacked_back(tmp_path):
Expand Down
Loading