2424from genericpath import *
2525
2626
27- __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"split" ,"splitext" ,
27+ __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"splitroot" , " split" ,"splitext" ,
2828 "basename" ,"dirname" ,"commonprefix" ,"getsize" ,"getmtime" ,
2929 "getatime" ,"getctime" , "islink" ,"exists" ,"lexists" ,"isdir" ,"isfile" ,
3030 "ismount" , "expanduser" ,"expandvars" ,"normpath" ,"abspath" ,
3131 "curdir" ,"pardir" ,"sep" ,"pathsep" ,"defpath" ,"altsep" ,
3232 "extsep" ,"devnull" ,"realpath" ,"supports_unicode_filenames" ,"relpath" ,
33- "samefile" , "sameopenfile" , "samestat" , "commonpath" ]
33+ "samefile" , "sameopenfile" , "samestat" , "commonpath" , "isjunction" ]
3434
3535def _get_bothseps (path ):
3636 if isinstance (path , bytes ):
@@ -117,19 +117,21 @@ def join(path, *paths):
117117 try :
118118 if not paths :
119119 path [:0 ] + sep #23780: Ensure compatible data type even if p is null.
120- result_drive , result_path = splitdrive (path )
120+ result_drive , result_root , result_path = splitroot (path )
121121 for p in map (os .fspath , paths ):
122- p_drive , p_path = splitdrive (p )
123- if p_path and p_path [ 0 ] in seps :
122+ p_drive , p_root , p_path = splitroot (p )
123+ if p_root :
124124 # Second path is absolute
125125 if p_drive or not result_drive :
126126 result_drive = p_drive
127+ result_root = p_root
127128 result_path = p_path
128129 continue
129130 elif p_drive and p_drive != result_drive :
130131 if p_drive .lower () != result_drive .lower ():
131132 # Different drives => ignore the first path entirely
132133 result_drive = p_drive
134+ result_root = p_root
133135 result_path = p_path
134136 continue
135137 # Same drive in different case
@@ -139,10 +141,10 @@ def join(path, *paths):
139141 result_path = result_path + sep
140142 result_path = result_path + p_path
141143 ## add separator between UNC and non-absolute path
142- if (result_path and result_path [ 0 ] not in seps and
143- result_drive and result_drive [- 1 :] != colon ):
144+ if (result_path and not result_root and
145+ result_drive and result_drive [- 1 :] not in colon + seps ):
144146 return result_drive + sep + result_path
145- return result_drive + result_path
147+ return result_drive + result_root + result_path
146148 except (TypeError , AttributeError , BytesWarning ):
147149 genericpath._check_arg_types ('join' , path , * paths )
148150 raise
@@ -169,35 +171,61 @@ def splitdrive(p):
169171
170172 Paths cannot contain both a drive letter and a UNC path.
171173
174+ """
175+ drive , root , tail = splitroot (p )
176+ return drive , root + tail
177+
178+
179+ def splitroot (p ):
180+ """Split a pathname into drive, root and tail. The drive is defined
181+ exactly as in splitdrive(). On Windows, the root may be a single path
182+ separator or an empty string. The tail contains anything after the root.
183+ For example:
184+
185+ splitroot('//server/share/') == ('//server/share', '/', '')
186+ splitroot('C:/Users/Barney') == ('C:', '/', 'Users/Barney')
187+ splitroot('C:///spam///ham') == ('C:', '/', '//spam///ham')
188+ splitroot('Windows/notepad') == ('', '', 'Windows/notepad')
172189 """
173190 p = os .fspath (p )
174- if len (p ) >= 2 :
175- if isinstance (p , bytes ):
176- sep = b'\\ '
177- altsep = b'/'
178- colon = b':'
179- unc_prefix = b'\\ \\ ?\\ UNC\\ '
180- else :
181- sep = '\\ '
182- altsep = '/'
183- colon = ':'
184- unc_prefix = '\\ \\ ?\\ UNC\\ '
185- normp = p .replace (altsep , sep )
186- if normp [0 :2 ] == sep * 2 :
191+ if isinstance (p , bytes ):
192+ sep = b'\\ '
193+ altsep = b'/'
194+ colon = b':'
195+ unc_prefix = b'\\ \\ ?\\ UNC\\ '
196+ empty = b''
197+ else :
198+ sep = '\\ '
199+ altsep = '/'
200+ colon = ':'
201+ unc_prefix = '\\ \\ ?\\ UNC\\ '
202+ empty = ''
203+ normp = p .replace (altsep , sep )
204+ if normp [:1 ] == sep :
205+ if normp [1 :2 ] == sep :
187206 # UNC drives, e.g. \\server\share or \\?\UNC\server\share
188207 # Device drives, e.g. \\.\device or \\?\device
189208 start = 8 if normp [:8 ].upper () == unc_prefix else 2
190209 index = normp .find (sep , start )
191210 if index == - 1 :
192- return p , p [: 0 ]
211+ return p , empty , empty
193212 index2 = normp .find (sep , index + 1 )
194213 if index2 == - 1 :
195- return p , p [:0 ]
196- return p [:index2 ], p [index2 :]
197- if normp [1 :2 ] == colon :
198- # Drive-letter drives, e.g. X:
199- return p [:2 ], p [2 :]
200- return p [:0 ], p
214+ return p , empty , empty
215+ return p [:index2 ], p [index2 :index2 + 1 ], p [index2 + 1 :]
216+ else :
217+ # Relative path with root, e.g. \Windows
218+ return empty , p [:1 ], p [1 :]
219+ elif normp [1 :2 ] == colon :
220+ if normp [2 :3 ] == sep :
221+ # Absolute drive-letter path, e.g. X:\Windows
222+ return p [:2 ], p [2 :3 ], p [3 :]
223+ else :
224+ # Relative path with drive, e.g. X:Windows
225+ return p [:2 ], empty , p [2 :]
226+ else :
227+ # Relative path, e.g. Windows
228+ return empty , empty , p
201229
202230
203231# Split a path in head (everything up to the last '/') and tail (the
@@ -212,15 +240,13 @@ def split(p):
212240 Either part may be empty."""
213241 p = os .fspath (p )
214242 seps = _get_bothseps (p )
215- d , p = splitdrive (p )
243+ d , r , p = splitroot (p )
216244 # set i to index beyond p's last slash
217245 i = len (p )
218246 while i and p [i - 1 ] not in seps :
219 247 i -= 1
220248 head , tail = p [:i ], p [i :] # now tail has no slashes
221- # remove trailing slashes from head, unless it's all slashes
222- head = head .rstrip (seps ) or head
223- return d + head , tail
249+ return d + r + head .rstrip (seps ), tail
224250
225251
226252# Split a path in root and extension.
@@ -250,18 +276,23 @@ def dirname(p):
250276 """Returns the directory component of a pathname"""
251277 return split (p )[0 ]
252278
253- # Is a path a symbolic link?
254- # This will always return false on systems where os.lstat doesn't exist.
255279
256- def islink (path ):
257- """Test whether a path is a symbolic link.
258- This will always return false for Windows prior to 6.0.
259- """
260- try :
261- st = os .lstat (path )
262- except (OSError , ValueError , AttributeError ):
280+ # Is a path a junction?
281+
282+ if hasattr (os .stat_result , 'st_reparse_tag' ):
283+ def isjunction (path ):
284+ """Test whether a path is a junction"""
285+ try :
286+ st = os .lstat (path )
287+ except (OSError , ValueError , AttributeError ):
288+ return False
289+ return bool (st .st_reparse_tag == stat .IO_REPARSE_TAG_MOUNT_POINT )
290+ else :
291+ def isjunction (path ):
292+ """Test whether a path is a junction"""
293+ os .fspath (path )
263294 return False
264- return stat . S_ISLNK ( st . st_mode )
295+
265296
266297# Being true for dangling symbolic links is also useful.
267298
@@ -293,10 +324,10 @@ def ismount(path):
293324 path = os .fspath (path )
294325 seps = _get_bothseps (path )
295326 path = abspath (path )
296- root , rest = splitdrive (path )
297- if root and root [0 ] in seps :
298- return ( not rest ) or ( rest in seps )
299- if rest and rest in seps :
327+ drive , root , rest = splitroot (path )
328+ if drive and drive [0 ] in seps :
329+ return not rest
330+ if root and not rest :
300331 return True
301332
302333 if _getvolumepathname :
@@ -507,13 +538,8 @@ def normpath(path):
507538 curdir = '.'
508539 pardir = '..'
509540 path = path .replace (altsep , sep )
510- prefix , path = splitdrive (path )
511-
512- # collapse initial backslashes
513- if path .startswith (sep ):
514- prefix += sep
515- path = path .lstrip (sep )
516-
541+ drive , root , path = splitroot (path )
542+ prefix = drive + root
517543 comps = path .split (sep )
518544 i = 0
519545 while i < len (comps ):
@@ -523,7 +549,7 @@ def normpath(path):
523549 if i > 0 and comps [i - 1 ] != pardir :
524550 del comps [i - 1 :i + 1 ]
525551 i -= 1
526- elif i == 0 and prefix . endswith ( sep ) :
552+ elif i == 0 and root :
527553 del comps [i ]
528554 else :
529555 i += 1
@@ -695,6 +721,14 @@ def realpath(path, *, strict=False):
695721 try :
696722 path = _getfinalpathname (path )
697723 initial_winerror = 0
724+ except ValueError as ex :
725+ # gh-106242: Raised for embedded null characters
726+ # In strict mode, we convert into an OSError.
727+ # Non-strict mode returns the path as-is, since we've already
728+ # made it absolute.
729+ if strict :
730+ raise OSError (str (ex )) from None
731+ path = normpath (path )
698732 except OSError as ex :
699733 if strict :
700734 raise
@@ -714,6 +748,10 @@ def realpath(path, *, strict=False):
714748 try :
715749 if _getfinalpathname (spath ) == path :
716750 path = spath
751+ except ValueError as ex :
752+ # Unexpected, as an invalid path should not have gained a prefix
753+ # at any point, but we ignore this error just in case.
754+ pass
717755 except OSError as ex :
718756 # If the path does not exist and originally did not exist, then
719757 # strip the prefix anyway.
@@ -722,9 +760,8 @@ def realpath(path, *, strict=False):
722760 return path
723761
724762
725- # Win9x family and earlier have no Unicode filename support.
726- supports_unicode_filenames = (hasattr (sys , "getwindowsversion" ) and
727- sys .getwindowsversion ()[3 ] >= 2 )
763+ # All supported version have Unicode filename support.
764+ supports_unicode_filenames = True
728765
729766def relpath (path , start = None ):
730767 """Return a relative version of a path"""
@@ -748,8 +785,8 @@ def relpath(path, start=None):
748785 try :
749786 start_abs = abspath (normpath (start ))
750787 path_abs = abspath (normpath (path ))
751- start_drive , start_rest = splitdrive (start_abs )
752- path_drive , path_rest = splitdrive (path_abs )
788+ start_drive , _ , start_rest = splitroot (start_abs )
789+ path_drive , _ , path_rest = splitroot (path_abs )
753790 if normcase (start_drive ) != normcase (path_drive ):
754791 raise ValueError ("path is on mount %r, start on mount %r" % (
755792 path_drive , start_drive ))
@@ -799,21 +836,19 @@ def commonpath(paths):
799836 curdir = '.'
800837
801838 try :
802- drivesplits = [splitdrive (p .replace (altsep , sep ).lower ()) for p in paths ]
803- split_paths = [p .split (sep ) for d , p in drivesplits ]
839+ drivesplits = [splitroot (p .replace (altsep , sep ).lower ()) for p in paths ]
840+ split_paths = [p .split (sep ) for d , r , p in drivesplits ]
804841
805- try :
806- isabs , = set (p [:1 ] == sep for d , p in drivesplits )
807- except ValueError :
808- raise ValueError ("Can't mix absolute and relative paths" ) from None
842+ if len ({r for d , r , p in drivesplits }) != 1 :
843+ raise ValueError ("Can't mix absolute and relative paths" )
809844
810845 # Check that all drive letters or UNC paths match. The check is made only
811846 # now otherwise type errors for mixing strings and bytes would not be
812847 # caught.
813- if len (set ( d for d , p in drivesplits ) ) != 1 :
848+ if len ({ d for d , r , p in drivesplits } ) != 1 :
814849 raise ValueError ("Paths don't have the same drive" )
815850
816- drive , path = splitdrive (paths [0 ].replace (altsep , sep ))
851+ drive , root , path = splitroot (paths [0 ].replace (altsep , sep ))
817852 common = path .split (sep )
818853 common = [c for c in common if c and c != curdir ]
819854
@@ -827,19 +862,36 @@ def commonpath(paths):
827862 else :
828863 common = common [:len (s1 )]
829864
830- prefix = drive + sep if isabs else drive
831- return prefix + sep .join (common )
865+ return drive + root + sep .join (common )
832866 except (TypeError , AttributeError ):
833867 genericpath ._check_arg_types ('commonpath' , * paths )
834868 raise
835869
836870
837871try :
838- # The genericpath.isdir implementation uses os.stat and checks the mode
839- # attribute to tell whether or not the path is a directory.
840- # This is overkill on Windows - just pass the path to GetFileAttributes
841- # and check the attribute from there.
842- from nt import _isdir as isdir
872+ # The isdir(), isfile(), islink() and exists() implementations in
873+ # genericpath use os.stat(). This is overkill on Windows. Use simpler
874+ # builtin functions if they are available.
875+ from nt import _path_isdir as isdir
876+ from nt import _path_isfile as isfile
877+ from nt import _path_islink as islink
878+ from nt import _path_exists as exists
843879except ImportError :
844- # Use genericpath.isdir as imported above.
880+ # Use genericpath.* as imported above
845881 pass
882+
883+
884+ try :
885+ from nt import _path_isdevdrive
886+ except ImportError :
887+ def isdevdrive (path ):
888+ """Determines whether the specified path is on a Windows Dev Drive."""
889+ # Never a Dev Drive
890+ return False
891+ else :
892+ def isdevdrive (path ):
893+ """Determines whether the specified path is on a Windows Dev Drive."""
894+ try :
895+ return _path_isdevdrive (abspath (path ))
896+ except OSError :
897+ return False
0 commit comments