1 # Module 'ntpath' -- common operations on WinNT/Win95 pathnames
2 """Common pathname manipulations, WindowsNT/95 version.
3
4 Instead of importing this module directly, import os and refer to this
5 module as os.path.
6 """
7
8 # strings representing various path-related bits and pieces
9 # These are primarily for export; internally, they are hardcoded.
10 # Should be set before imports for resolving cyclic dependency.
11 curdir = '.'
12 pardir = '..'
13 extsep = '.'
14 sep = '\\'
15 pathsep = ';'
16 altsep = '/'
17 defpath = '.;C:\\bin'
18 devnull = 'nul'
19
20 import os
21 import sys
22 import stat
23 import genericpath
24 from genericpath import *
25
26
27 __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
28 "basename","dirname","commonprefix","getsize","getmtime",
29 "getatime","getctime", "islink","exists","lexists","isdir","isfile",
30 "ismount", "expanduser","expandvars","normpath","abspath",
31 "curdir","pardir","sep","pathsep","defpath","altsep",
32 "extsep","devnull","realpath","supports_unicode_filenames","relpath",
33 "samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
34
35 def _get_bothseps(path):
36 if isinstance(path, bytes):
37 return b'\\/'
38 else:
39 return '\\/'
40
41 # Normalize the case of a pathname and map slashes to backslashes.
42 # Other normalizations (such as optimizing '../' away) are not done
43 # (this is done by normpath).
44
45 try:
46 from _winapi import (
47 LCMapStringEx as _LCMapStringEx,
48 LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT,
49 LCMAP_LOWERCASE as _LCMAP_LOWERCASE)
50
51 def normcase(s):
52 """Normalize case of pathname.
53
54 Makes all characters lowercase and all slashes into backslashes.
55 """
56 s = os.fspath(s)
57 if not s:
58 return s
59 if isinstance(s, bytes):
60 encoding = sys.getfilesystemencoding()
61 s = s.decode(encoding, 'surrogateescape').replace('/', '\\')
62 s = _LCMapStringEx(_LOCALE_NAME_INVARIANT,
63 _LCMAP_LOWERCASE, s)
64 return s.encode(encoding, 'surrogateescape')
65 else:
66 return _LCMapStringEx(_LOCALE_NAME_INVARIANT,
67 _LCMAP_LOWERCASE,
68 s.replace('/', '\\'))
69 except ImportError:
70 def normcase(s):
71 """Normalize case of pathname.
72
73 Makes all characters lowercase and all slashes into backslashes.
74 """
75 s = os.fspath(s)
76 if isinstance(s, bytes):
77 return os.fsencode(os.fsdecode(s).replace('/', '\\').lower())
78 return s.replace('/', '\\').lower()
79
80
81 # Return whether a path is absolute.
82 # Trivial in Posix, harder on Windows.
83 # For Windows it is absolute if it starts with a slash or backslash (current
84 # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
85 # starts with a slash or backslash.
86
87 def isabs(s):
88 """Test whether a path is absolute"""
89 s = os.fspath(s)
90 if isinstance(s, bytes):
91 sep = b'\\'
92 altsep = b'/'
93 colon_sep = b':\\'
94 else:
95 sep = '\\'
96 altsep = '/'
97 colon_sep = ':\\'
98 s = s[:3].replace(altsep, sep)
99 # Absolute: UNC, device, and paths with a drive and root.
100 # LEGACY BUG: isabs("/x") should be false since the path has no drive.
101 if s.startswith(sep) or s.startswith(colon_sep, 1):
102 return True
103 return False
104
105
106 # Join two (or more) paths.
107 def join(path, *paths):
108 path = os.fspath(path)
109 if isinstance(path, bytes):
110 sep = b'\\'
111 seps = b'\\/'
112 colon = b':'
113 else:
114 sep = '\\'
115 seps = '\\/'
116 colon = ':'
117 try:
118 if not paths:
119 path[:0] + sep #23780: Ensure compatible data type even if p is null.
120 result_drive, result_root, result_path = splitroot(path)
121 for p in map(os.fspath, paths):
122 p_drive, p_root, p_path = splitroot(p)
123 if p_root:
124 # Second path is absolute
125 if p_drive or not result_drive:
126 result_drive = p_drive
127 result_root = p_root
128 result_path = p_path
129 continue
130 elif p_drive and p_drive != result_drive:
131 if p_drive.lower() != result_drive.lower():
132 # Different drives => ignore the first path entirely
133 result_drive = p_drive
134 result_root = p_root
135 result_path = p_path
136 continue
137 # Same drive in different case
138 result_drive = p_drive
139 # Second path is relative to the first
140 if result_path and result_path[-1] not in seps:
141 result_path = result_path + sep
142 result_path = result_path + p_path
143 ## add separator between UNC and non-absolute path
144 if (result_path and not result_root and
145 result_drive and result_drive[-1:] not in colon + seps):
146 return result_drive + sep + result_path
147 return result_drive + result_root + result_path
148 except (TypeError, AttributeError, BytesWarning):
149 genericpath._check_arg_types('join', path, *paths)
150 raise
151
152
153 # Split a path in a drive specification (a drive letter followed by a
154 # colon) and the path specification.
155 # It is always true that drivespec + pathspec == p
156 def splitdrive(p):
157 """Split a pathname into drive/UNC sharepoint and relative path specifiers.
158 Returns a 2-tuple (drive_or_unc, path); either part may be empty.
159
160 If you assign
161 result = splitdrive(p)
162 It is always true that:
163 result[0] + result[1] == p
164
165 If the path contained a drive letter, drive_or_unc will contain everything
166 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
167
168 If the path contained a UNC path, the drive_or_unc will contain the host name
169 and share up to but not including the fourth directory separator character.
170 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
171
172 Paths cannot contain both a drive letter and a UNC path.
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')
189 """
190 p = os.fspath(p)
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:
206 # UNC drives, e.g. \\server\share or \\?\UNC\server\share
207 # Device drives, e.g. \\.\device or \\?\device
208 start = 8 if normp[:8].upper() == unc_prefix else 2
209 index = normp.find(sep, start)
210 if index == -1:
211 return p, empty, empty
212 index2 = normp.find(sep, index + 1)
213 if index2 == -1:
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
229
230
231 # Split a path in head (everything up to the last '/') and tail (the
232 # rest). After the trailing '/' is stripped, the invariant
233 # join(head, tail) == p holds.
234 # The resulting head won't end in '/' unless it is the root.
235
236 def split(p):
237 """Split a pathname.
238
239 Return tuple (head, tail) where tail is everything after the final slash.
240 Either part may be empty."""
241 p = os.fspath(p)
242 seps = _get_bothseps(p)
243 d, r, p = splitroot(p)
244 # set i to index beyond p's last slash
245 i = len(p)
246 while i and p[i-1] not in seps:
247 i -= 1
248 head, tail = p[:i], p[i:] # now tail has no slashes
249 return d + r + head.rstrip(seps), tail
250
251
252 # Split a path in root and extension.
253 # The extension is everything starting at the last dot in the last
254 # pathname component; the root is everything before that.
255 # It is always true that root + ext == p.
256
257 def splitext(p):
258 p = os.fspath(p)
259 if isinstance(p, bytes):
260 return genericpath._splitext(p, b'\\', b'/', b'.')
261 else:
262 return genericpath._splitext(p, '\\', '/', '.')
263 splitext.__doc__ = genericpath._splitext.__doc__
264
265
266 # Return the tail (basename) part of a path.
267
268 def basename(p):
269 """Returns the final component of a pathname"""
270 return split(p)[1]
271
272
273 # Return the head (dirname) part of a path.
274
275 def dirname(p):
276 """Returns the directory component of a pathname"""
277 return split(p)[0]
278
279
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)
294 return False
295
296
297 # Being true for dangling symbolic links is also useful.
298
299 def lexists(path):
300 """Test whether a path exists. Returns True for broken symbolic links"""
301 try:
302 st = os.lstat(path)
303 except (OSError, ValueError):
304 return False
305 return True
306
307 # Is a path a mount point?
308 # Any drive letter root (eg c:\)
309 # Any share UNC (eg \\server\share)
310 # Any volume mounted on a filesystem folder
311 #
312 # No one method detects all three situations. Historically we've lexically
313 # detected drive letter roots and share UNCs. The canonical approach to
314 # detecting mounted volumes (querying the reparse tag) fails for the most
315 # common case: drive letter roots. The alternative which uses GetVolumePathName
316 # fails if the drive letter is the result of a SUBST.
317 try:
318 from nt import _getvolumepathname
319 except ImportError:
320 _getvolumepathname = None
321 def ismount(path):
322 """Test whether a path is a mount point (a drive root, the root of a
323 share, or a mounted volume)"""
324 path = os.fspath(path)
325 seps = _get_bothseps(path)
326 path = abspath(path)
327 drive, root, rest = splitroot(path)
328 if drive and drive[0] in seps:
329 return not rest
330 if root and not rest:
331 return True
332
333 if _getvolumepathname:
334 x = path.rstrip(seps)
335 y =_getvolumepathname(path).rstrip(seps)
336 return x.casefold() == y.casefold()
337 else:
338 return False
339
340
341 # Expand paths beginning with '~' or '~user'.
342 # '~' means $HOME; '~user' means that user's home directory.
343 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
344 # the path is returned unchanged (leaving error reporting to whatever
345 # function is called with the expanded path as argument).
346 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
347 # (A function should also be defined to do full *sh-style environment
348 # variable expansion.)
349
350 def expanduser(path):
351 """Expand ~ and ~user constructs.
352
353 If user or $HOME is unknown, do nothing."""
354 path = os.fspath(path)
355 if isinstance(path, bytes):
356 tilde = b'~'
357 else:
358 tilde = '~'
359 if not path.startswith(tilde):
360 return path
361 i, n = 1, len(path)
362 while i < n and path[i] not in _get_bothseps(path):
363 i += 1
364
365 if 'USERPROFILE' in os.environ:
366 userhome = os.environ['USERPROFILE']
367 elif not 'HOMEPATH' in os.environ:
368 return path
369 else:
370 try:
371 drive = os.environ['HOMEDRIVE']
372 except KeyError:
373 drive = ''
374 userhome = join(drive, os.environ['HOMEPATH'])
375
376 if i != 1: #~user
377 target_user = path[1:i]
378 if isinstance(target_user, bytes):
379 target_user = os.fsdecode(target_user)
380 current_user = os.environ.get('USERNAME')
381
382 if target_user != current_user:
383 # Try to guess user home directory. By default all user
384 # profile directories are located in the same place and are
385 # named by corresponding usernames. If userhome isn't a
386 # normal profile directory, this guess is likely wrong,
387 # so we bail out.
388 if current_user != basename(userhome):
389 return path
390 userhome = join(dirname(userhome), target_user)
391
392 if isinstance(path, bytes):
393 userhome = os.fsencode(userhome)
394
395 return userhome + path[i:]
396
397
398 # Expand paths containing shell variable substitutions.
399 # The following rules apply:
400 # - no expansion within single quotes
401 # - '$$' is translated into '$'
402 # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
403 # - ${varname} is accepted.
404 # - $varname is accepted.
405 # - %varname% is accepted.
406 # - varnames can be made out of letters, digits and the characters '_-'
407 # (though is not verified in the ${varname} and %varname% cases)
408 # XXX With COMMAND.COM you can use any characters in a variable name,
409 # XXX except '^|<>='.
410
411 def expandvars(path):
412 """Expand shell variables of the forms $var, ${var} and %var%.
413
414 Unknown variables are left unchanged."""
415 path = os.fspath(path)
416 if isinstance(path, bytes):
417 if b'$' not in path and b'%' not in path:
418 return path
419 import string
420 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
421 quote = b'\''
422 percent = b'%'
423 brace = b'{'
424 rbrace = b'}'
425 dollar = b'$'
426 environ = getattr(os, 'environb', None)
427 else:
428 if '$' not in path and '%' not in path:
429 return path
430 import string
431 varchars = string.ascii_letters + string.digits + '_-'
432 quote = '\''
433 percent = '%'
434 brace = '{'
435 rbrace = '}'
436 dollar = '$'
437 environ = os.environ
438 res = path[:0]
439 index = 0
440 pathlen = len(path)
441 while index < pathlen:
442 c = path[index:index+1]
443 if c == quote: # no expansion within single quotes
444 path = path[index + 1:]
445 pathlen = len(path)
446 try:
447 index = path.index(c)
448 res += c + path[:index + 1]
449 except ValueError:
450 res += c + path
451 index = pathlen - 1
452 elif c == percent: # variable or '%'
453 if path[index + 1:index + 2] == percent:
454 res += c
455 index += 1
456 else:
457 path = path[index+1:]
458 pathlen = len(path)
459 try:
460 index = path.index(percent)
461 except ValueError:
462 res += percent + path
463 index = pathlen - 1
464 else:
465 var = path[:index]
466 try:
467 if environ is None:
468 value = os.fsencode(os.environ[os.fsdecode(var)])
469 else:
470 value = environ[var]
471 except KeyError:
472 value = percent + var + percent
473 res += value
474 elif c == dollar: # variable or '$$'
475 if path[index + 1:index + 2] == dollar:
476 res += c
477 index += 1
478 elif path[index + 1:index + 2] == brace:
479 path = path[index+2:]
480 pathlen = len(path)
481 try:
482 index = path.index(rbrace)
483 except ValueError:
484 res += dollar + brace + path
485 index = pathlen - 1
486 else:
487 var = path[:index]
488 try:
489 if environ is None:
490 value = os.fsencode(os.environ[os.fsdecode(var)])
491 else:
492 value = environ[var]
493 except KeyError:
494 value = dollar + brace + var + rbrace
495 res += value
496 else:
497 var = path[:0]
498 index += 1
499 c = path[index:index + 1]
500 while c and c in varchars:
501 var += c
502 index += 1
503 c = path[index:index + 1]
504 try:
505 if environ is None:
506 value = os.fsencode(os.environ[os.fsdecode(var)])
507 else:
508 value = environ[var]
509 except KeyError:
510 value = dollar + var
511 res += value
512 if c:
513 index -= 1
514 else:
515 res += c
516 index += 1
517 return res
518
519
520 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
521 # Previously, this function also truncated pathnames to 8+3 format,
522 # but as this module is called "ntpath", that's obviously wrong!
523 try:
524 from nt import _path_normpath
525
526 except ImportError:
527 def normpath(path):
528 """Normalize path, eliminating double slashes, etc."""
529 path = os.fspath(path)
530 if isinstance(path, bytes):
531 sep = b'\\'
532 altsep = b'/'
533 curdir = b'.'
534 pardir = b'..'
535 else:
536 sep = '\\'
537 altsep = '/'
538 curdir = '.'
539 pardir = '..'
540 path = path.replace(altsep, sep)
541 drive, root, path = splitroot(path)
542 prefix = drive + root
543 comps = path.split(sep)
544 i = 0
545 while i < len(comps):
546 if not comps[i] or comps[i] == curdir:
547 del comps[i]
548 elif comps[i] == pardir:
549 if i > 0 and comps[i-1] != pardir:
550 del comps[i-1:i+1]
551 i -= 1
552 elif i == 0 and root:
553 del comps[i]
554 else:
555 i += 1
556 else:
557 i += 1
558 # If the path is now empty, substitute '.'
559 if not prefix and not comps:
560 comps.append(curdir)
561 return prefix + sep.join(comps)
562
563 else:
564 def normpath(path):
565 """Normalize path, eliminating double slashes, etc."""
566 path = os.fspath(path)
567 if isinstance(path, bytes):
568 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
569 return _path_normpath(path) or "."
570
571
572 def _abspath_fallback(path):
573 """Return the absolute version of a path as a fallback function in case
574 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
575 more.
576
577 """
578
579 path = os.fspath(path)
580 if not isabs(path):
581 if isinstance(path, bytes):
582 cwd = os.getcwdb()
583 else:
584 cwd = os.getcwd()
585 path = join(cwd, path)
586 return normpath(path)
587
588 # Return an absolute path.
589 try:
590 from nt import _getfullpathname
591
592 except ImportError: # not running on Windows - mock up something sensible
593 abspath = _abspath_fallback
594
595 else: # use native Windows method on Windows
596 def abspath(path):
597 """Return the absolute version of a path."""
598 try:
599 return _getfullpathname(normpath(path))
600 except (OSError, ValueError):
601 return _abspath_fallback(path)
602
603 try:
604 from nt import _getfinalpathname, readlink as _nt_readlink
605 except ImportError:
606 # realpath is a no-op on systems without _getfinalpathname support.
607 realpath = abspath
608 else:
609 def _readlink_deep(path):
610 # These error codes indicate that we should stop reading links and
611 # return the path we currently have.
612 # 1: ERROR_INVALID_FUNCTION
613 # 2: ERROR_FILE_NOT_FOUND
614 # 3: ERROR_DIRECTORY_NOT_FOUND
615 # 5: ERROR_ACCESS_DENIED
616 # 21: ERROR_NOT_READY (implies drive with no media)
617 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
618 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
619 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
620 # 87: ERROR_INVALID_PARAMETER
621 # 4390: ERROR_NOT_A_REPARSE_POINT
622 # 4392: ERROR_INVALID_REPARSE_DATA
623 # 4393: ERROR_REPARSE_TAG_INVALID
624 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
625
626 seen = set()
627 while normcase(path) not in seen:
628 seen.add(normcase(path))
629 try:
630 old_path = path
631 path = _nt_readlink(path)
632 # Links may be relative, so resolve them against their
633 # own location
634 if not isabs(path):
635 # If it's something other than a symlink, we don't know
636 # what it's actually going to be resolved against, so
637 # just return the old path.
638 if not islink(old_path):
639 path = old_path
640 break
641 path = normpath(join(dirname(old_path), path))
642 except OSError as ex:
643 if ex.winerror in allowed_winerror:
644 break
645 raise
646 except ValueError:
647 # Stop on reparse points that are not symlinks
648 break
649 return path
650
651 def _getfinalpathname_nonstrict(path):
652 # These error codes indicate that we should stop resolving the path
653 # and return the value we currently have.
654 # 1: ERROR_INVALID_FUNCTION
655 # 2: ERROR_FILE_NOT_FOUND
656 # 3: ERROR_DIRECTORY_NOT_FOUND
657 # 5: ERROR_ACCESS_DENIED
658 # 21: ERROR_NOT_READY (implies drive with no media)
659 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
660 # 50: ERROR_NOT_SUPPORTED
661 # 53: ERROR_BAD_NETPATH
662 # 65: ERROR_NETWORK_ACCESS_DENIED
663 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
664 # 87: ERROR_INVALID_PARAMETER
665 # 123: ERROR_INVALID_NAME
666 # 161: ERROR_BAD_PATHNAME
667 # 1920: ERROR_CANT_ACCESS_FILE
668 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
669 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1920, 1921
670
671 # Non-strict algorithm is to find as much of the target directory
672 # as we can and join the rest.
673 tail = path[:0]
674 while path:
675 try:
676 path = _getfinalpathname(path)
677 return join(path, tail) if tail else path
678 except OSError as ex:
679 if ex.winerror not in allowed_winerror:
680 raise
681 try:
682 # The OS could not resolve this path fully, so we attempt
683 # to follow the link ourselves. If we succeed, join the tail
684 # and return.
685 new_path = _readlink_deep(path)
686 if new_path != path:
687 return join(new_path, tail) if tail else new_path
688 except OSError:
689 # If we fail to readlink(), let's keep traversing
690 pass
691 path, name = split(path)
692 # TODO (bpo-38186): Request the real file name from the directory
693 # entry using FindFirstFileW. For now, we will return the path
694 # as best we have it
695 if path and not name:
696 return path + tail
697 tail = join(name, tail) if tail else name
698 return tail
699
700 def realpath(path, *, strict=False):
701 path = normpath(path)
702 if isinstance(path, bytes):
703 prefix = b'\\\\?\\'
704 unc_prefix = b'\\\\?\\UNC\\'
705 new_unc_prefix = b'\\\\'
706 cwd = os.getcwdb()
707 # bpo-38081: Special case for realpath(b'nul')
708 if normcase(path) == normcase(os.fsencode(devnull)):
709 return b'\\\\.\\NUL'
710 else:
711 prefix = '\\\\?\\'
712 unc_prefix = '\\\\?\\UNC\\'
713 new_unc_prefix = '\\\\'
714 cwd = os.getcwd()
715 # bpo-38081: Special case for realpath('nul')
716 if normcase(path) == normcase(devnull):
717 return '\\\\.\\NUL'
718 had_prefix = path.startswith(prefix)
719 if not had_prefix and not isabs(path):
720 path = join(cwd, path)
721 try:
722 path = _getfinalpathname(path)
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)
732 except OSError as ex:
733 if strict:
734 raise
735 initial_winerror = ex.winerror
736 path = _getfinalpathname_nonstrict(path)
737 # The path returned by _getfinalpathname will always start with \\?\ -
738 # strip off that prefix unless it was already provided on the original
739 # path.
740 if not had_prefix and path.startswith(prefix):
741 # For UNC paths, the prefix will actually be \\?\UNC\
742 # Handle that case as well.
743 if path.startswith(unc_prefix):
744 spath = new_unc_prefix + path[len(unc_prefix):]
745 else:
746 spath = path[len(prefix):]
747 # Ensure that the non-prefixed path resolves to the same path
748 try:
749 if _getfinalpathname(spath) == path:
750 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
755 except OSError as ex:
756 # If the path does not exist and originally did not exist, then
757 # strip the prefix anyway.
758 if ex.winerror == initial_winerror:
759 path = spath
760 return path
761
762
763 # All supported version have Unicode filename support.
764 supports_unicode_filenames = True
765
766 def relpath(path, start=None):
767 """Return a relative version of a path"""
768 path = os.fspath(path)
769 if isinstance(path, bytes):
770 sep = b'\\'
771 curdir = b'.'
772 pardir = b'..'
773 else:
774 sep = '\\'
775 curdir = '.'
776 pardir = '..'
777
778 if start is None:
779 start = curdir
780
781 if not path:
782 raise ValueError("no path specified")
783
784 start = os.fspath(start)
785 try:
786 start_abs = abspath(normpath(start))
787 path_abs = abspath(normpath(path))
788 start_drive, _, start_rest = splitroot(start_abs)
789 path_drive, _, path_rest = splitroot(path_abs)
790 if normcase(start_drive) != normcase(path_drive):
791 raise ValueError("path is on mount %r, start on mount %r" % (
792 path_drive, start_drive))
793
794 start_list = [x for x in start_rest.split(sep) if x]
795 path_list = [x for x in path_rest.split(sep) if x]
796 # Work out how much of the filepath is shared by start and path.
797 i = 0
798 for e1, e2 in zip(start_list, path_list):
799 if normcase(e1) != normcase(e2):
800 break
801 i += 1
802
803 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
804 if not rel_list:
805 return curdir
806 return join(*rel_list)
807 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
808 genericpath._check_arg_types('relpath', path, start)
809 raise
810
811
812 # Return the longest common sub-path of the sequence of paths given as input.
813 # The function is case-insensitive and 'separator-insensitive', i.e. if the
814 # only difference between two paths is the use of '\' versus '/' as separator,
815 # they are deemed to be equal.
816 #
817 # However, the returned path will have the standard '\' separator (even if the
818 # given paths had the alternative '/' separator) and will have the case of the
819 # first path given in the sequence. Additionally, any trailing separator is
820 # stripped from the returned path.
821
822 def commonpath(paths):
823 """Given a sequence of path names, returns the longest common sub-path."""
824
825 if not paths:
826 raise ValueError('commonpath() arg is an empty sequence')
827
828 paths = tuple(map(os.fspath, paths))
829 if isinstance(paths[0], bytes):
830 sep = b'\\'
831 altsep = b'/'
832 curdir = b'.'
833 else:
834 sep = '\\'
835 altsep = '/'
836 curdir = '.'
837
838 try:
839 drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths]
840 split_paths = [p.split(sep) for d, r, p in drivesplits]
841
842 if len({r for d, r, p in drivesplits}) != 1:
843 raise ValueError("Can't mix absolute and relative paths")
844
845 # Check that all drive letters or UNC paths match. The check is made only
846 # now otherwise type errors for mixing strings and bytes would not be
847 # caught.
848 if len({d for d, r, p in drivesplits}) != 1:
849 raise ValueError("Paths don't have the same drive")
850
851 drive, root, path = splitroot(paths[0].replace(altsep, sep))
852 common = path.split(sep)
853 common = [c for c in common if c and c != curdir]
854
855 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
856 s1 = min(split_paths)
857 s2 = max(split_paths)
858 for i, c in enumerate(s1):
859 if c != s2[i]:
860 common = common[:i]
861 break
862 else:
863 common = common[:len(s1)]
864
865 return drive + root + sep.join(common)
866 except (TypeError, AttributeError):
867 genericpath._check_arg_types('commonpath', *paths)
868 raise
869
870
871 try:
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
879 except ImportError:
880 # Use genericpath.* as imported above
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