diff --git a/pm/filesystem.py b/pm/filesystem.py index 37d3e04de5..eb829818e3 100644 --- a/pm/filesystem.py +++ b/pm/filesystem.py @@ -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 diff --git a/pm/store.py b/pm/store.py index b6754651e6..ac0d63ce52 100644 --- a/pm/store.py +++ b/pm/store.py @@ -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