Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-100809: Fix handling of drive-relative paths in pathlib.Path.absolute() #100812

Merged
merged 11 commits into from
Feb 17, 2023
11 changes: 10 additions & 1 deletion Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
from operator import attrgetter
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from urllib.parse import quote_from_bytes as urlquote_from_bytes
try:
from nt import _getfullpathname
except ImportError:
_getfullpathname = None


__all__ = [
Expand Down Expand Up @@ -827,7 +831,12 @@ def absolute(self):
"""
if self.is_absolute():
return self
return self._from_parts([os.getcwd()] + self._parts)
elif self._drv and _getfullpathname:
# There is a CWD on each drive-letter drive.
cwd = _getfullpathname(self._drv)
else:
cwd = os.getcwd()
return self._from_parts([cwd] + self._parts)

def resolve(self, strict=False):
"""
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/support/os_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import stat
import string
import sys
import time
import unittest
Expand Down Expand Up @@ -716,3 +717,37 @@ def __exit__(self, *ignore_exc):
else:
self._environ[k] = v
os.environ = self._environ


try:
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
except (ImportError, AttributeError):
def subst_drive(path):
raise NotImplementedError('ctypes or kernel32 is not available')
barneygale marked this conversation as resolved.
Show resolved Hide resolved
else:
ERROR_FILE_NOT_FOUND = 2
DDD_REMOVE_DEFINITION = 2
DDD_EXACT_MATCH_ON_REMOVE = 4
DDD_NO_BROADCAST_SYSTEM = 8
barneygale marked this conversation as resolved.
Show resolved Hide resolved

@contextlib.contextmanager
def subst_drive(path):
"""Temporarily yield a substitute drive for a given path."""
for c in reversed(string.ascii_uppercase):
drive = f'{c}:'
if (not kernel32.QueryDosDeviceW(drive, None, 0) and
ctypes.get_last_error() == ERROR_FILE_NOT_FOUND):
break
else:
raise FileExistsError('no available logical drive')
barneygale marked this conversation as resolved.
Show resolved Hide resolved
if not kernel32.DefineDosDeviceW(
DDD_NO_BROADCAST_SYSTEM, drive, path):
raise ctypes.WinError(ctypes.get_last_error())
try:
yield drive
finally:
if not kernel32.DefineDosDeviceW(
DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
barneygale marked this conversation as resolved.
Show resolved Hide resolved
drive, path):
raise ctypes.WinError(ctypes.get_last_error())
19 changes: 19 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3002,6 +3002,25 @@ def test_absolute(self):
self.assertEqual(str(P('a', 'b', 'c').absolute()),
os.path.join(share, 'a', 'b', 'c'))

drive = os.path.splitdrive(BASE)[0]
with os_helper.change_cwd(BASE):
# Relative path with root
self.assertEqual(str(P('\\').absolute()), drive + '\\')
self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo')

# Relative path on current drive
self.assertEqual(str(P(drive).absolute()), BASE)
self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(BASE, 'foo'))

with os_helper.subst_drive(BASE) as other_drive:
other_cwd = f'{other_drive}\\dirA'
with os_helper.change_cwd(other_cwd):
barneygale marked this conversation as resolved.
Show resolved Hide resolved
pass
barneygale marked this conversation as resolved.
Show resolved Hide resolved

# Relative path on another drive
self.assertEqual(str(P(other_drive).absolute()), other_cwd)
self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo')


def test_glob(self):
P = self.cls
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix handling of drive-relative paths (like 'C:' and 'C:foo') in
:meth:`pathlib.Path.absolute`. This method now calls
``nt._getfullpathname()`` on Windows.
barneygale marked this conversation as resolved.
Show resolved Hide resolved