fix(pm): give Store a long-path root on Windows
Windows cuts plain file calls at 260 characters when LongPathsEnabled is 0, which is the default. PM's copy, walk, digest, rename and delete calls under the store then fail or silently skip files (#130242). Store now spells its root in the extended-length form (\\?\C:\...). Every path built from it with entry() or / inherits that spelling, so the file calls beneath the root no longer hit the limit. Relative paths, and so digests, are unchanged. On POSIX the root is untouched. native() returns the ordinary spelling for text that leaves PM's own file calls (argv, environment values, hashed identities, records and messages). The call sites adopt it in follow-up commits.
This commit is contained in:
committed by
Siddharth Balyan
parent
cbffbeecf4
commit
855997b7d7
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import ntpath
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
@@ -23,6 +24,37 @@ def is_junction(path: Path) -> bool:
|
||||
return os.name == "nt" and path.lstat().st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT
|
||||
|
||||
|
||||
_VERBATIM = "\\\\?\\"
|
||||
_VERBATIM_UNC = "\\\\?\\UNC\\"
|
||||
|
||||
|
||||
def _long_form(text: str) -> str:
|
||||
if text.startswith(_VERBATIM):
|
||||
return text
|
||||
# Windows stops normalising a verbatim path, so it must already be absolute,
|
||||
# backslashed and free of `..` before the prefix goes on.
|
||||
text = ntpath.abspath(text)
|
||||
if text.startswith("\\\\"):
|
||||
return _VERBATIM_UNC + text[2:]
|
||||
return _VERBATIM + text
|
||||
|
||||
|
||||
def long_root(path: Path) -> Path:
|
||||
"""Spell a PM-owned root so Windows file calls beneath it are not cut at
|
||||
MAX_PATH when LongPathsEnabled is 0 (the default). Every path joined onto the
|
||||
result inherits the spelling; relative paths and digests are unchanged."""
|
||||
return Path(_long_form(os.fspath(path))) if os.name == "nt" else path
|
||||
|
||||
|
||||
def native(path: str | os.PathLike[str]) -> str:
|
||||
"""The ordinary spelling, for text that leaves PM's own file calls: argv, cwd,
|
||||
environment values, hashed identities, records and messages."""
|
||||
text = os.fspath(path)
|
||||
if text.startswith(_VERBATIM_UNC):
|
||||
return "\\\\" + text[len(_VERBATIM_UNC):]
|
||||
return text[len(_VERBATIM):] if text.startswith(_VERBATIM) else text
|
||||
|
||||
|
||||
def lock_fd(fd: int, *, wait: bool, timeout: float | None = None) -> bool:
|
||||
"""Take the byte lock; ``timeout`` bounds the retry loop (None waits forever, 0 tries once)."""
|
||||
deadline = None if timeout is None else time.monotonic() + timeout
|
||||
|
||||
@@ -14,7 +14,7 @@ from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import IO
|
||||
|
||||
from pm.filesystem import is_junction, retry_held
|
||||
from pm.filesystem import is_junction, long_root, retry_held
|
||||
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ class Store:
|
||||
Hash-keyed archives survive failed installs. Publication releases them."""
|
||||
|
||||
def __init__(self, root: Path):
|
||||
self.root = root
|
||||
self.root = long_root(root)
|
||||
|
||||
def entry(self, name: str) -> Path:
|
||||
return self.root / name
|
||||
|
||||
Reference in New Issue
Block a user