Skip to content
11 changes: 10 additions & 1 deletion Lib/genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdir', 'isfile', 'islink', 'samefile', 'sameopenfile',
'samestat']
'samestat','lexists']


# Does a path exist?
Expand All @@ -22,6 +22,15 @@ def exists(path):
return True


# Being true for dangling symbolic links is also useful.
def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
os.lstat(path)
except (OSError, ValueError):
return False
return True

# This follows symbolic links, so both islink() and isdir() can be true
# for the same path on systems that support symlinks
def isfile(path):
Expand Down
79 changes: 35 additions & 44 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"ismount","isreserved","expanduser","expandvars","normpath",
"abspath","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction",
"isdevdrive"]

def _get_bothseps(path):
if isinstance(path, bytes):
Expand Down Expand Up @@ -164,8 +165,34 @@ def splitdrive(p):
Paths cannot contain both a drive letter and a UNC path.

"""
drive, root, tail = splitroot(p)
return drive, root + tail
p = os.fspath(p)
if isinstance(p, bytes):
sep = b'\\'
altsep = b'/'
colon = b':'
unc_prefix = b'\\\\?\\UNC\\'
else:
sep = '\\'
altsep = '/'
colon = ':'
unc_prefix = '\\\\?\\UNC\\'
normp = p.replace(altsep, sep)
if normp[:1] != sep:
if normp[1:2] == colon:
# Drive-letter drives, e.g. X:
return p[:2], p[2:]
elif normp[1:2] == sep:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, p[:0]
index2 = normp.find(sep, index + 1)
if index2 == -1:
return p, p[:0]
return p[:index2], p[index2:]
return p[:0], p


def splitroot(p):
Expand All @@ -179,45 +206,19 @@ def splitroot(p):
splitroot('C:///spam///ham') == ('C:', '/', '//spam///ham')
splitroot('Windows/notepad') == ('', '', 'Windows/notepad')
"""
p = os.fspath(p)
drive, p = splitdrive(p)
if isinstance(p, bytes):
sep = b'\\'
altsep = b'/'
colon = b':'
unc_prefix = b'\\\\?\\UNC\\'
empty = b''
else:
sep = '\\'
altsep = '/'
colon = ':'
unc_prefix = '\\\\?\\UNC\\'
empty = ''
normp = p.replace(altsep, sep)
if normp[:1] == sep:
if normp[1:2] == sep:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, empty, empty
index2 = normp.find(sep, index + 1)
if index2 == -1:
return p, empty, empty
return p[:index2], p[index2:index2 + 1], p[index2 + 1:]
else:
# Relative path with root, e.g. \Windows
return empty, p[:1], p[1:]
elif normp[1:2] == colon:
if normp[2:3] == sep:
# Absolute drive-letter path, e.g. X:\Windows
return p[:2], p[2:3], p[3:]
else:
# Relative path with drive, e.g. X:Windows
return p[:2], empty, p[2:]
else:
# Relative path, e.g. Windows
return empty, empty, p
# Absolute path, e.g. X:\Windows
return drive, p[:1], p[1:]
# Relative path, e.g. X:Windows
return drive, p[:0], p


# Split a path in head (everything up to the last '/') and tail (the
Expand Down Expand Up @@ -286,16 +287,6 @@ def isjunction(path):
return False


# Being true for dangling symbolic links is also useful.

def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
st = os.lstat(path)
except (OSError, ValueError):
return False
return True

# Is a path a mount point?
# Any drive letter root (eg c:\)
# Any share UNC (eg \\server\share)
Expand Down
31 changes: 11 additions & 20 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"samefile","sameopenfile","samestat",
"curdir","pardir","sep","pathsep","defpath","altsep","extsep",
"devnull","realpath","supports_unicode_filenames","relpath",
"commonpath", "isjunction"]
"commonpath", "isjunction","isreserved","isdevdrive"]


def _get_sep(path):
Expand Down Expand Up @@ -168,23 +168,14 @@ def splitroot(p):

def basename(p):
"""Returns the final component of a pathname"""
p = os.fspath(p)
sep = _get_sep(p)
i = p.rfind(sep) + 1
return p[i:]
return split(p)[1]


# Return the head (dirname) part of a path, same as split(path)[0].

def dirname(p):
"""Returns the directory component of a pathname"""
p = os.fspath(p)
sep = _get_sep(p)
i = p.rfind(sep) + 1
head = p[:i]
if head and head != sep*len(head):
head = head.rstrip(sep)
return head
return split(p)[0]


# Is a path a junction?
Expand All @@ -196,16 +187,16 @@ def isjunction(path):
return False


# Being true for dangling symbolic links is also useful.
def isreserved(path):
"""Return true if the pathname is reserved by the system.
Always returns False on posix"""
return False

def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
os.lstat(path)
except (OSError, ValueError):
return False
return True

def isdevdrive(path):
"""Determines whether the specified path is on a Dev Drive.
Dev Drives are not a part of posix semantics"""
return False

# Is a path a mount point?
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
Expand Down
9 changes: 5 additions & 4 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ def test_splitdrive(self):
tester('ntpath.splitdrive("//?/UNC/server/share/dir")',
("//?/UNC/server/share", "/dir"))

# gh-101363: match GetFullPathNameW() drive letter parsing behaviour
tester('ntpath.splitdrive(" :/foo")', (" :", "/foo"))
tester('ntpath.splitdrive("/:/foo")', ("", "/:/foo"))


def test_splitroot(self):
tester("ntpath.splitroot('')", ('', '', ''))
tester("ntpath.splitroot('foo')", ('', '', 'foo'))
Expand Down Expand Up @@ -210,10 +215,6 @@ def test_splitroot(self):
tester('ntpath.splitroot("//x")', ("//x", "", "")) # non-empty server & missing share
tester('ntpath.splitroot("//x/")', ("//x/", "", "")) # non-empty server & empty share

# gh-101363: match GetFullPathNameW() drive letter parsing behaviour
tester('ntpath.splitroot(" :/foo")', (" :", "/", "foo"))
tester('ntpath.splitroot("/:/foo")', ("", "/", ":/foo"))

def test_split(self):
tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added posix implementation for isreserved & isdevdrive.