Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,28 @@ def test_prefixes(self):
self.assertEqual(pathlib.Path(out.strip().decode()),
pathlib.Path(expected), prefix)

# gh-157917: the "\\?\" extended-length prefix must not leak into the prefix
@requireVenvCreate
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
def test_prefixes_extended_length_path(self):
"""
Test that invoking a venv through a "\\?\" extended-length path does
not leave that prefix behind in the prefixes. Windows does not
resolve relative components (such as ".." or "/") in such paths, so
they break tools that join them onto sysconfig-derived paths.
"""
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
envpy = os.path.join('\\\\?\\' + self.env_dir, self.bindir, self.exe)
for prefix in ('prefix', 'exec_prefix'):
with self.subTest(prefix):
out, err = check_output(
[envpy, '-c', 'import sys; print(sys.%s)' % prefix],
encoding='utf-8')
self.assertFalse(out.strip().startswith('\\\\?\\'), out)
self.assertEqual(pathlib.Path(out.strip()),
pathlib.Path(self.env_dir), prefix)

@requireVenvCreate
def test_sysconfig(self):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Fix :attr:`sys.prefix`, :attr:`sys.exec_prefix` and :attr:`sys.executable`
retaining the ``\\?\`` extended-length path prefix in a virtual environment
when the interpreter is invoked through such a path on Windows. The venv
launcher reports its own path through ``GetModuleFileNameW``, which preserves
the prefix, and that value was used verbatim. Windows does not resolve
relative path components in extended-length paths, so the leaked prefix made
tools that join ``..`` components onto :mod:`sysconfig` paths fail with
``OSError: [Errno 22]``. The prefix is now stripped again, except for paths
that are still too long to be used without it.
29 changes: 29 additions & 0 deletions Modules/getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,28 @@ def search_up(prefix, *landmarks, test=isfile):
prefix = dirname(prefix)


# The traditional Windows path length limit, not counting the terminating NUL.
MAX_PATH = 260


def strip_extended_length_prefix(path):
"""
Remove the leading "\\\\?\\" of a Windows extended-length path, and
rewrite the "\\\\?\\UNC\\" form of a UNC path back to a plain "\\\\" one.
Windows does not resolve relative components (such as ".." or "/") in an
extended-length path, so keeping the prefix in a path that we later join
onto is liable to break consumers. A path that is still too long to be
used unprefixed keeps its prefix, as it has no other usable form.
"""
if path.startswith('\\\\?\\UNC\\'):
stripped = '\\\\' + path[8:]
elif path.startswith('\\\\?\\'):
stripped = path[4:]
else:
return path
return stripped if len(stripped) < MAX_PATH else path


# ******************************************************************************
# READ VARIABLES FROM config
# ******************************************************************************
Expand Down Expand Up @@ -319,6 +341,13 @@ def search_up(prefix, *landmarks, test=isfile):
real_executable = base_executable
#real_executable_dir = dirname(real_executable)
executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__
if os_name == 'nt':
# QUIRK: On Windows the venv launcher reports its own path through
# GetModuleFileNameW, which hands back a "\\?\" extended-length path
# whenever that is how the interpreter was invoked. Drop that prefix
# again, so the venv prefix and sys.executable stay usable by tools
# that join relative components onto them (gh-157917).
executable = strip_extended_length_prefix(executable)
executable_dir = dirname(executable)


Expand Down
Loading