studio/install: repair upstream llama.cpp prebuilt mangled symlinks (#5465)
The macos-arm64 prebuilt tarball for llama.cpp b9165 and b9169 ships symlinks whose linkname is missing both the directory separator AND the leading character of the target basename: llama-b9165/libggml-rpc.0.dylib -> llama-b9165ibggml-rpc.0.11.1.dylib extract_tar_safely correctly classified those as unresolved and made install.sh fall back to source-build, which Mac CI then fails as a hard error (Studio must use the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon). Add _try_repair_missing_slash inside safe_link_target: when a linkname starts with the member's top-level dir but no following slash, search the archive for an entry under that dir whose name ends with the mangled suffix. Accept only when the suffix uniquely identifies a real archive entry, so legitimate archives are untouched. Verified against /tmp/llama-b9165.tar.gz: all 18 link entries repair to real files in the archive.
This commit is contained in:
parent
4b23af48b1
commit
4f59c8e539
1 changed files with 46 additions and 2 deletions
|
|
@ -3433,10 +3433,52 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
|
|||
) from exc
|
||||
return target
|
||||
|
||||
def _try_repair_missing_slash(
|
||||
member_name: str, link_name: str, archive_names: set[str]
|
||||
) -> str | None:
|
||||
"""Some upstream llama.cpp Mac releases (e.g. b9165, b9169) ship
|
||||
symlinks whose linkname is missing the directory separator AND
|
||||
the leading character of the file basename between the
|
||||
top-level dir and the rest of the path:
|
||||
|
||||
llama-b9165/libggml-rpc.0.dylib -> llama-b9165ibggml-rpc.0.11.1.dylib
|
||||
|
||||
That cannot be resolved as written. Detect the pattern
|
||||
(linkname starts with the top-level dir name but no following
|
||||
slash) and search archive entries under that dir for a real
|
||||
file whose basename ends with the mangled suffix. Only accept
|
||||
when the suffix uniquely identifies a real archive entry."""
|
||||
if "/" not in member_name or "/" in link_name:
|
||||
return None
|
||||
top, _, _ = member_name.partition("/")
|
||||
if not link_name.startswith(top) or len(link_name) <= len(top):
|
||||
return None
|
||||
bad_suffix = link_name[len(top) :]
|
||||
if not bad_suffix or bad_suffix.startswith("/"):
|
||||
return None
|
||||
prefix = f"{top}/"
|
||||
candidates = [
|
||||
name
|
||||
for name in archive_names
|
||||
if name.startswith(prefix)
|
||||
and "/" not in name[len(prefix) :]
|
||||
and name[len(prefix) :].endswith(bad_suffix)
|
||||
]
|
||||
if len(candidates) != 1:
|
||||
return None
|
||||
return candidates[0]
|
||||
|
||||
def safe_link_target(
|
||||
base: Path, member_name: str, link_name: str, target: Path
|
||||
base: Path,
|
||||
member_name: str,
|
||||
link_name: str,
|
||||
target: Path,
|
||||
archive_names: set[str],
|
||||
) -> tuple[str, Path]:
|
||||
normalized = link_name.replace("\\", "/")
|
||||
repaired = _try_repair_missing_slash(member_name, normalized, archive_names)
|
||||
if repaired is not None:
|
||||
normalized = repaired
|
||||
link_path = Path(normalized)
|
||||
if link_path.is_absolute():
|
||||
raise PrebuiltFallback(
|
||||
|
|
@ -3473,8 +3515,10 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
|
|||
|
||||
def extract_tar_safely(source: Path, base: Path) -> None:
|
||||
pending_links: list[tuple[tarfile.TarInfo, Path]] = []
|
||||
archive_names: set[str] = set()
|
||||
with tarfile.open(source, "r:gz") as archive:
|
||||
for member in archive.getmembers():
|
||||
archive_names.add(member.name)
|
||||
target = safe_extract_path(base, member.name)
|
||||
if member.isdir():
|
||||
target.mkdir(parents = True, exist_ok = True)
|
||||
|
|
@ -3501,7 +3545,7 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
|
|||
progressed = False
|
||||
for member, target in unresolved:
|
||||
normalized_link, resolved_target = safe_link_target(
|
||||
base, member.name, member.linkname, target
|
||||
base, member.name, member.linkname, target, archive_names
|
||||
)
|
||||
if not resolved_target.exists() and not resolved_target.is_symlink():
|
||||
next_round.append((member, target))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue