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