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:
alt-glitch
2026-10-01 16:47:57 +05:30
committed by Siddharth Balyan
parent cbffbeecf4
commit 855997b7d7
2 changed files with 34 additions and 2 deletions

View File

@@ -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

View File

@@ -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