mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
fix: zip extract permission bits (#2102)
* Restore Unix executable bits when extracting zip archives - SafeZipExtractor._extract_member now chmods each extracted file with the Unix mode stored in ZipInfo.external_attr (POSIX only, no-op when the archive carries no Unix attributes, e.g. Windows-authored zips). - stdlib zipfile never restores permission bits itself; a fix is tracked upstream at https://github.com/python/cpython/pull/150061. - Fixes archives with more than one executable losing their exec bit after extraction, e.g. the bundled JBR inside the Kotlin Language Server distribution (jbr/bin/java and native libs), which previously only had its single top-level launcher script chmod'd by the language-server code. Fixes oraios/serena#2100 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
9fa4237107
commit
b83b655ced
3 files changed
+34
No files matched your search
@@ -107,6 +107,14 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
thread (#2038)
|
||||
|
||||
* Language Servers:
|
||||
- Fix: `SafeZipExtractor` discarded Unix executable permission bits stored in extracted
|
||||
archives' `ZipInfo.external_attr` (a long-standing stdlib `zipfile` limitation,
|
||||
tracked upstream at https://github.com/python/cpython/pull/150061), leaving every
|
||||
extracted file with default, non-executable permissions. This broke language servers
|
||||
whose archive contains more than the single top-level launcher script that
|
||||
per-language-server setup code re-chmods, e.g. the Kotlin Language Server's bundled
|
||||
JetBrains Runtime (`jbr/bin/java` and native libs), whose launcher failed to exec it
|
||||
with a permission error. Executable bits are now restored for every extracted file (#2100)
|
||||
- Add Astro language server support via `@astrojs/language-server` with a companion TypeScript language server (`@astrojs/ts-plugin`) for cross-file code intelligence (#2085)
|
||||
- Fix: Dart analysis server no longer receives rootUri/rootPath, which added the monorepo root as an extra analysis root and could pin a CPU core at idle (#2045)
|
||||
- Fix: The C# language server opened every `.csproj` found anywhere under the repository root,
|
||||
|
||||
@@ -101,6 +101,13 @@ class SafeZipExtractor:
|
||||
with zip_ref.open(member) as source, open(final_path, "wb") as target:
|
||||
target.write(source.read())
|
||||
|
||||
# stdlib zipfile does not restore Unix permission bits on extraction; tracked
|
||||
# upstream at https://github.com/python/cpython/pull/150061
|
||||
if os.name == "posix":
|
||||
unix_mode = member.external_attr >> 16
|
||||
if unix_mode:
|
||||
os.chmod(final_path, unix_mode)
|
||||
|
||||
if self.verbose:
|
||||
log.info(f"Extracted: {member.filename}")
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
@@ -90,6 +91,24 @@ def test_skip_on_error(monkeypatch, temp_zip_file: Path, tmp_path: Path) -> None
|
||||
assert (dest_dir / "folder" / "file3.txt").exists()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Unix permission bits are not applicable on Windows")
|
||||
def test_restores_executable_permission(tmp_path: Path) -> None:
|
||||
"""Executable bits stored in the archive's external_attr should be restored on extraction."""
|
||||
zip_path = tmp_path / "exec.zip"
|
||||
with zipfile.ZipFile(zip_path, "w") as zipf:
|
||||
info = zipfile.ZipInfo("bin/tool")
|
||||
info.external_attr = 0o755 << 16
|
||||
zipf.writestr(info, "#!/bin/sh\necho hi\n")
|
||||
|
||||
dest_dir = tmp_path / "extracted"
|
||||
extractor = SafeZipExtractor(zip_path, dest_dir, verbose=False)
|
||||
extractor.extract_all()
|
||||
|
||||
extracted_file = dest_dir / "bin" / "tool"
|
||||
assert extracted_file.exists()
|
||||
assert os.stat(extracted_file).st_mode & 0o111
|
||||
|
||||
|
||||
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows-only test")
|
||||
def test_long_path_normalization(temp_zip_file: Path, tmp_path: Path) -> None:
|
||||
r"""Ensure _normalize_path adds \\?\\ prefix on Windows."""
|
||||
|
||||
Reference in new issue
Block a user