24
24
from genericpath import *
25
25
26
26
27
- __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"split" ,"splitext" ,
27
+ __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"splitroot" , " split" ,"splitext" ,
28
28
"basename" ,"dirname" ,"commonprefix" ,"getsize" ,"getmtime" ,
29
29
"getatime" ,"getctime" , "islink" ,"exists" ,"lexists" ,"isdir" ,"isfile" ,
30
30
"ismount" , "expanduser" ,"expandvars" ,"normpath" ,"abspath" ,
31
31
"curdir" ,"pardir" ,"sep" ,"pathsep" ,"defpath" ,"altsep" ,
32
32
"extsep" ,"devnull" ,"realpath" ,"supports_unicode_filenames" ,"relpath" ,
33
- "samefile" , "sameopenfile" , "samestat" , "commonpath" ]
33
+ "samefile" , "sameopenfile" , "samestat" , "commonpath" , "isjunction" ]
34
34
35
35
def _get_bothseps (path ):
36
36
if isinstance (path , bytes ):
@@ -117,19 +117,21 @@ def join(path, *paths):
117
117
try :
118
118
if not paths :
119
119
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 )
121
121
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 :
124
124
# Second path is absolute
125
125
if p_drive or not result_drive :
126
126
result_drive = p_drive
127
+ result_root = p_root
127
128
result_path = p_path
128
129
continue
129
130
elif p_drive and p_drive != result_drive :
130
131
if p_drive .lower () != result_drive .lower ():
131
132
# Different drives => ignore the first path entirely
132
133
result_drive = p_drive
134
+ result_root = p_root
133
135
result_path = p_path
134
136
continue
135
137
# Same drive in different case
@@ -139,10 +141,10 @@ def join(path, *paths):
139
141
result_path = result_path + sep
140
142
result_path = result_path + p_path
141
143
## 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 ):
144
146
return result_drive + sep + result_path
145
- return result_drive + result_path
147
+ return result_drive + result_root + result_path
146
148
except (TypeError , AttributeError , BytesWarning ):
147
149
genericpath ._check_arg_types ('join' , path , * paths )
148
150
raise
@@ -169,35 +171,61 @@ def splitdrive(p):
169
171
170
172
Paths cannot contain both a drive letter and a UNC path.
171
173
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')
172
189
"""
173
190
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 :
187
206
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
188
207
# Device drives, e.g. \\.\device or \\?\device
189
208
start = 8 if normp [:8 ].upper () == unc_prefix else 2
190
209
index = normp .find (sep , start )
191
210
if index == - 1 :
192
- return p , p [: 0 ]
211
+ return p , empty , empty
193
212
index2 = normp .find (sep , index + 1 )
194
213
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
201
229
202
230
203
231
# Split a path in head (everything up to the last '/') and tail (the
@@ -212,15 +240,13 @@ def split(p):
212
240
Either part may be empty."""
213
241
p = os .fspath (p )
214
242
seps = _get_bothseps (p )
215
- d , p = splitdrive (p )
243
+ d , r , p = splitroot (p )
216
244
# set i to index beyond p's last slash
217
245
i = len (p )
218
246
while i and p [i - 1 ] not in seps :
219
247
i -= 1
220
248
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
224
250
225
251
226
252
# Split a path in root and extension.
@@ -250,18 +276,23 @@ def dirname(p):
250
276
"""Returns the directory component of a pathname"""
251
277
return split (p )[0 ]
252
278
253
- # Is a path a symbolic link?
254
- # This will always return false on systems where os.lstat doesn't exist.
255
279
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 )
263
294
return False
264
- return stat . S_ISLNK ( st . st_mode )
295
+
265
296
266
297
# Being true for dangling symbolic links is also useful.
267
298
@@ -293,10 +324,10 @@ def ismount(path):
293
324
path = os .fspath (path )
294
325
seps = _get_bothseps (path )
295
326
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 :
300
331
return True
301
332
302
333
if _getvolumepathname :
@@ -507,13 +538,8 @@ def normpath(path):
507
538
curdir = '.'
508
539
pardir = '..'
509
540
path = path .replace (altsep , sep )
510
- prefix , path = splitdrive (path )
511
-
512
-
10000
# 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
517
543
comps = path .split (sep )
518
544
i = 0
519
545
while i < len (comps ):
@@ -523,7 +549,7 @@ def normpath(path):
523
549
if i > 0 and comps [i - 1 ] != pardir :
524
550
del comps [i - 1 :i + 1 ]
525
551
i -= 1
526
- elif i == 0 and prefix . endswith ( sep ) :
552
+ elif i == 0 and root :
527
553
del comps [i ]
528
554
else :
529
555
i += 1
@@ -695,6 +721,14 @@ def realpath(path, *, strict=False):
695
721
try :
696
722
path = _getfinalpathname (path )
697
723
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 )
698
732
except OSError as ex :
699
733
if strict :
700
734
raise
@@ -714,6 +748,10 @@ def realpath(path, *, strict=False):
714
748
try :
715
749
if _getfinalpathname (spath ) == path :
716
750
path
10000
= 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
717
755
except OSError as ex :
718
756
# If the path does not exist and originally did not exist, then
719
757
# strip the prefix anyway.
@@ -722,9 +760,8 @@ def realpath(path, *, strict=False):
722
760
return path
723
761
724
762
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
728
765
729
766
def relpath (path , start = None ):
730
767
"""Return a relative version of a path"""
@@ -748,8 +785,8 @@ def relpath(path, start=None):
748
785
try :
749
786
start_abs = abspath (normpath (start ))
750
787
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 )
753
790
if normcase (start_drive ) != normcase (path_drive ):
754
791
raise ValueError ("path is on mount %r, start on mount %r" % (
755
792
path_drive , start_drive ))
@@ -799,21 +836,19 @@ def commonpath(paths):
799
836
curdir = '.'
800
837
801
838
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 ]
804
841
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" )
809
844
810
845
# Check that all drive letters or UNC paths match. The check is made only
811
846
# now otherwise type errors for mixing strings and bytes would not be
812
847
# caught.
813
- if len (set ( d for d , p in drivesplits ) ) != 1 :
848
+ if len ({ d for d , r , p in drivesplits } ) != 1 :
814
849
raise ValueError ("Paths don't have the same drive" )
815
850
816
- drive , path = splitdrive (paths [0 ].replace (altsep , sep ))
851
+ drive , root , path = splitroot (paths [0 ].replace (altsep , sep ))
817
852
common = path .split (sep )
818
853
common = [c for c in common if c and c != curdir ]
819
854
@@ -827,19 +862,36 @@ def commonpath(paths):
827
862
else :
828
863
common = common [:len (s1 )]
829
864
830
- prefix = drive + sep if isabs else drive
831
- return prefix + sep .join (common )
865
+ return drive + root + sep .join (common )
832
866
except (TypeError , AttributeError ):
833
867
genericpath ._check_arg_types ('commonpath' , * paths )
834
868
raise
835
869
836
870
837
871
try :
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
843
879
except ImportError :
844
- # Use genericpath.isdir as imported above.
880
+ # Use genericpath.* as imported above
845
881
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