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 stat 23import genericpath 24from genericpath import * 25 26__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 27 "basename","dirname","commonprefix","getsize","getmtime", 28 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 29 "ismount", "expanduser","expandvars","normpath","abspath", 30 "curdir","pardir","sep","pathsep","defpath","altsep", 31 "extsep","devnull","realpath","supports_unicode_filenames","relpath", 32 "samefile", "sameopenfile", "samestat", "commonpath"] 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 44def normcase(s): 45 """Normalize case of pathname. 46 47 Makes all characters lowercase and all slashes into backslashes.""" 48 s = os.fspath(s) 49 try: 50 if isinstance(s, bytes): 51 return s.replace(b'/', b'\\').lower() 52 else: 53 return s.replace('/', '\\').lower() 54 except (TypeError, AttributeError): 55 if not isinstance(s, (bytes, str)): 56 raise TypeError("normcase() argument must be str or bytes, " 57 "not %r" % s.__class__.__name__) from None 58 raise 59 60 61# Return whether a path is absolute. 62# Trivial in Posix, harder on Windows. 63# For Windows it is absolute if it starts with a slash or backslash (current 64# volume), or if a pathname after the volume-letter-and-colon or UNC-resource 65# starts with a slash or backslash. 66 67def isabs(s): 68 """Test whether a path is absolute""" 69 s = os.fspath(s) 70 s = splitdrive(s)[1] 71 return len(s) > 0 and s[0] in _get_bothseps(s) 72 73 74# Join two (or more) paths. 75def join(path, *paths): 76 path = os.fspath(path) 77 if isinstance(path, bytes): 78 sep = b'\\' 79 seps = b'\\/' 80 colon = b':' 81 else: 82 sep = '\\' 83 seps = '\\/' 84 colon = ':' 85 try: 86 if not paths: 87 path[:0] + sep #23780: Ensure compatible data type even if p is null. 88 result_drive, result_path = splitdrive(path) 89 for p in map(os.fspath, paths): 90 p_drive, p_path = splitdrive(p) 91 if p_path and p_path[0] in seps: 92 # Second path is absolute 93 if p_drive or not result_drive: 94 result_drive = p_drive 95 result_path = p_path 96 continue 97 elif p_drive and p_drive != result_drive: 98 if p_drive.lower() != result_drive.lower(): 99 # Different drives => ignore the first path entirely 100 result_drive = p_drive 101 result_path = p_path 102 continue 103 # Same drive in different case 104 result_drive = p_drive 105 # Second path is relative to the first 106 if result_path and result_path[-1] not in seps: 107 result_path = result_path + sep 108 result_path = result_path + p_path 109 ## add separator between UNC and non-absolute path 110 if (result_path and result_path[0] not in seps and 111 result_drive and result_drive[-1:] != colon): 112 return result_drive + sep + result_path 113 return result_drive + result_path 114 except (TypeError, AttributeError, BytesWarning): 115 genericpath._check_arg_types('join', path, *paths) 116 raise 117 118 119# Split a path in a drive specification (a drive letter followed by a 120# colon) and the path specification. 121# It is always true that drivespec + pathspec == p 122def splitdrive(p): 123 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 124 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 125 126 If you assign 127 result = splitdrive(p) 128 It is always true that: 129 result[0] + result[1] == p 130 131 If the path contained a drive letter, drive_or_unc will contain everything 132 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 133 134 If the path contained a UNC path, the drive_or_unc will contain the host name 135 and share up to but not including the fourth directory separator character. 136 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 137 138 Paths cannot contain both a drive letter and a UNC path. 139 140 """ 141 p = os.fspath(p) 142 if len(p) >= 2: 143 if isinstance(p, bytes): 144 sep = b'\\' 145 altsep = b'/' 146 colon = b':' 147 else: 148 sep = '\\' 149 altsep = '/' 150 colon = ':' 151 normp = p.replace(altsep, sep) 152 if (normp[0:2] == sep*2) and (normp[2:3] != sep): 153 # is a UNC path: 154 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path 155 # \\machine\mountpoint\directory\etc\... 156 # directory ^^^^^^^^^^^^^^^ 157 index = normp.find(sep, 2) 158 if index == -1: 159 return p[:0], p 160 index2 = normp.find(sep, index + 1) 161 # a UNC path can't have two slashes in a row 162 # (after the initial two) 163 if index2 == index + 1: 164 return p[:0], p 165 if index2 == -1: 166 index2 = len(p) 167 return p[:index2], p[index2:] 168 if normp[1:2] == colon: 169 return p[:2], p[2:] 170 return p[:0], p 171 172 173# Split a path in head (everything up to the last '/') and tail (the 174# rest). After the trailing '/' is stripped, the invariant 175# join(head, tail) == p holds. 176# The resulting head won't end in '/' unless it is the root. 177 178def split(p): 179 """Split a pathname. 180 181 Return tuple (head, tail) where tail is everything after the final slash. 182 Either part may be empty.""" 183 p = os.fspath(p) 184 seps = _get_bothseps(p) 185 d, p = splitdrive(p) 186 # set i to index beyond p's last slash 187 i = len(p) 188 while i and p[i-1] not in seps: 189 i -= 1 190 head, tail = p[:i], p[i:] # now tail has no slashes 191 # remove trailing slashes from head, unless it's all slashes 192 head = head.rstrip(seps) or head 193 return d + head, tail 194 195 196# Split a path in root and extension. 197# The extension is everything starting at the last dot in the last 198# pathname component; the root is everything before that. 199# It is always true that root + ext == p. 200 201def splitext(p): 202 p = os.fspath(p) 203 if isinstance(p, bytes): 204 return genericpath._splitext(p, b'\\', b'/', b'.') 205 else: 206 return genericpath._splitext(p, '\\', '/', '.') 207splitext.__doc__ = genericpath._splitext.__doc__ 208 209 210# Return the tail (basename) part of a path. 211 212def basename(p): 213 """Returns the final component of a pathname""" 214 return split(p)[1] 215 216 217# Return the head (dirname) part of a path. 218 219def dirname(p): 220 """Returns the directory component of a pathname""" 221 return split(p)[0] 222 223# Is a path a symbolic link? 224# This will always return false on systems where os.lstat doesn't exist. 225 226def islink(path): 227 """Test whether a path is a symbolic link. 228 This will always return false for Windows prior to 6.0. 229 """ 230 try: 231 st = os.lstat(path) 232 except (OSError, AttributeError): 233 return False 234 return stat.S_ISLNK(st.st_mode) 235 236# Being true for dangling symbolic links is also useful. 237 238def lexists(path): 239 """Test whether a path exists. Returns True for broken symbolic links""" 240 try: 241 st = os.lstat(path) 242 except OSError: 243 return False 244 return True 245 246# Is a path a mount point? 247# Any drive letter root (eg c:\) 248# Any share UNC (eg \\server\share) 249# Any volume mounted on a filesystem folder 250# 251# No one method detects all three situations. Historically we've lexically 252# detected drive letter roots and share UNCs. The canonical approach to 253# detecting mounted volumes (querying the reparse tag) fails for the most 254# common case: drive letter roots. The alternative which uses GetVolumePathName 255# fails if the drive letter is the result of a SUBST. 256try: 257 from nt import _getvolumepathname 258except ImportError: 259 _getvolumepathname = None 260def ismount(path): 261 """Test whether a path is a mount point (a drive root, the root of a 262 share, or a mounted volume)""" 263 path = os.fspath(path) 264 seps = _get_bothseps(path) 265 path = abspath(path) 266 root, rest = splitdrive(path) 267 if root and root[0] in seps: 268 return (not rest) or (rest in seps) 269 if rest in seps: 270 return True 271 272 if _getvolumepathname: 273 return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps) 274 else: 275 return False 276 277 278# Expand paths beginning with '~' or '~user'. 279# '~' means $HOME; '~user' means that user's home directory. 280# If the path doesn't begin with '~', or if the user or $HOME is unknown, 281# the path is returned unchanged (leaving error reporting to whatever 282# function is called with the expanded path as argument). 283# See also module 'glob' for expansion of *, ? and [...] in pathnames. 284# (A function should also be defined to do full *sh-style environment 285# variable expansion.) 286 287def expanduser(path): 288 """Expand ~ and ~user constructs. 289 290 If user or $HOME is unknown, do nothing.""" 291 path = os.fspath(path) 292 if isinstance(path, bytes): 293 tilde = b'~' 294 else: 295 tilde = '~' 296 if not path.startswith(tilde): 297 return path 298 i, n = 1, len(path) 299 while i < n and path[i] not in _get_bothseps(path): 300 i += 1 301 302 if 'HOME' in os.environ: 303 userhome = os.environ['HOME'] 304 elif 'USERPROFILE' in os.environ: 305 userhome = os.environ['USERPROFILE'] 306 elif not 'HOMEPATH' in os.environ: 307 return path 308 else: 309 try: 310 drive = os.environ['HOMEDRIVE'] 311 except KeyError: 312 drive = '' 313 userhome = join(drive, os.environ['HOMEPATH']) 314 315 if isinstance(path, bytes): 316 userhome = os.fsencode(userhome) 317 318 if i != 1: #~user 319 userhome = join(dirname(userhome), path[1:i]) 320 321 return userhome + path[i:] 322 323 324# Expand paths containing shell variable substitutions. 325# The following rules apply: 326# - no expansion within single quotes 327# - '$$' is translated into '$' 328# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 329# - ${varname} is accepted. 330# - $varname is accepted. 331# - %varname% is accepted. 332# - varnames can be made out of letters, digits and the characters '_-' 333# (though is not verified in the ${varname} and %varname% cases) 334# XXX With COMMAND.COM you can use any characters in a variable name, 335# XXX except '^|<>='. 336 337def expandvars(path): 338 """Expand shell variables of the forms $var, ${var} and %var%. 339 340 Unknown variables are left unchanged.""" 341 path = os.fspath(path) 342 if isinstance(path, bytes): 343 if b'$' not in path and b'%' not in path: 344 return path 345 import string 346 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') 347 quote = b'\'' 348 percent = b'%' 349 brace = b'{' 350 rbrace = b'}' 351 dollar = b'$' 352 environ = getattr(os, 'environb', None) 353 else: 354 if '$' not in path and '%' not in path: 355 return path 356 import string 357 varchars = string.ascii_letters + string.digits + '_-' 358 quote = '\'' 359 percent = '%' 360 brace = '{' 361 rbrace = '}' 362 dollar = '$' 363 environ = os.environ 364 res = path[:0] 365 index = 0 366 pathlen = len(path) 367 while index < pathlen: 368 c = path[index:index+1] 369 if c == quote: # no expansion within single quotes 370 path = path[index + 1:] 371 pathlen = len(path) 372 try: 373 index = path.index(c) 374 res += c + path[:index + 1] 375 except ValueError: 376 res += c + path 377 index = pathlen - 1 378 elif c == percent: # variable or '%' 379 if path[index + 1:index + 2] == percent: 380 res += c 381 index += 1 382 else: 383 path = path[index+1:] 384 pathlen = len(path) 385 try: 386 index = path.index(percent) 387 except ValueError: 388 res += percent + path 389 index = pathlen - 1 390 else: 391 var = path[:index] 392 try: 393 if environ is None: 394 value = os.fsencode(os.environ[os.fsdecode(var)]) 395 else: 396 value = environ[var] 397 except KeyError: 398 value = percent + var + percent 399 res += value 400 elif c == dollar: # variable or '$$' 401 if path[index + 1:index + 2] == dollar: 402 res += c 403 index += 1 404 elif path[index + 1:index + 2] == brace: 405 path = path[index+2:] 406 pathlen = len(path) 407 try: 408 index = path.index(rbrace) 409 except ValueError: 410 res += dollar + brace + path 411 index = pathlen - 1 412 else: 413 var = path[:index] 414 try: 415 if environ is None: 416 value = os.fsencode(os.environ[os.fsdecode(var)]) 417 else: 418 value = environ[var] 419 except KeyError: 420 value = dollar + brace + var + rbrace 421 res += value 422 else: 423 var = path[:0] 424 index += 1 425 c = path[index:index + 1] 426 while c and c in varchars: 427 var += c 428 index += 1 429 c = path[index:index + 1] 430 try: 431 if environ is None: 432 value = os.fsencode(os.environ[os.fsdecode(var)]) 433 else: 434 value = environ[var] 435 except KeyError: 436 value = dollar + var 437 res += value 438 if c: 439 index -= 1 440 else: 441 res += c 442 index += 1 443 return res 444 445 446# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 447# Previously, this function also truncated pathnames to 8+3 format, 448# but as this module is called "ntpath", that's obviously wrong! 449 450def normpath(path): 451 """Normalize path, eliminating double slashes, etc.""" 452 path = os.fspath(path) 453 if isinstance(path, bytes): 454 sep = b'\\' 455 altsep = b'/' 456 curdir = b'.' 457 pardir = b'..' 458 special_prefixes = (b'\\\\.\\', b'\\\\?\\') 459 else: 460 sep = '\\' 461 altsep = '/' 462 curdir = '.' 463 pardir = '..' 464 special_prefixes = ('\\\\.\\', '\\\\?\\') 465 if path.startswith(special_prefixes): 466 # in the case of paths with these prefixes: 467 # \\.\ -> device names 468 # \\?\ -> literal paths 469 # do not do any normalization, but return the path unchanged 470 return path 471 path = path.replace(altsep, sep) 472 prefix, path = splitdrive(path) 473 474 # collapse initial backslashes 475 if path.startswith(sep): 476 prefix += sep 477 path = path.lstrip(sep) 478 479 comps = path.split(sep) 480 i = 0 481 while i < len(comps): 482 if not comps[i] or comps[i] == curdir: 483 del comps[i] 484 elif comps[i] == pardir: 485 if i > 0 and comps[i-1] != pardir: 486 del comps[i-1:i+1] 487 i -= 1 488 elif i == 0 and prefix.endswith(sep): 489 del comps[i] 490 else: 491 i += 1 492 else: 493 i += 1 494 # If the path is now empty, substitute '.' 495 if not prefix and not comps: 496 comps.append(curdir) 497 return prefix + sep.join(comps) 498 499def _abspath_fallback(path): 500 """Return the absolute version of a path as a fallback function in case 501 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for 502 more. 503 504 """ 505 506 path = os.fspath(path) 507 if not isabs(path): 508 if isinstance(path, bytes): 509 cwd = os.getcwdb() 510 else: 511 cwd = os.getcwd() 512 path = join(cwd, path) 513 return normpath(path) 514 515# Return an absolute path. 516try: 517 from nt import _getfullpathname 518 519except ImportError: # not running on Windows - mock up something sensible 520 abspath = _abspath_fallback 521 522else: # use native Windows method on Windows 523 def abspath(path): 524 """Return the absolute version of a path.""" 525 try: 526 return normpath(_getfullpathname(path)) 527 except (OSError, ValueError): 528 return _abspath_fallback(path) 529 530# realpath is a no-op on systems without islink support 531realpath = abspath 532# Win9x family and earlier have no Unicode filename support. 533supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 534 sys.getwindowsversion()[3] >= 2) 535 536def relpath(path, start=None): 537 """Return a relative version of a path""" 538 path = os.fspath(path) 539 if isinstance(path, bytes): 540 sep = b'\\' 541 curdir = b'.' 542 pardir = b'..' 543 else: 544 sep = '\\' 545 curdir = '.' 546 pardir = '..' 547 548 if start is None: 549 start = curdir 550 551 if not path: 552 raise ValueError("no path specified") 553 554 start = os.fspath(start) 555 try: 556 start_abs = abspath(normpath(start)) 557 path_abs = abspath(normpath(path)) 558 start_drive, start_rest = splitdrive(start_abs) 559 path_drive, path_rest = splitdrive(path_abs) 560 if normcase(start_drive) != normcase(path_drive): 561 raise ValueError("path is on mount %r, start on mount %r" % ( 562 path_drive, start_drive)) 563 564 start_list = [x for x in start_rest.split(sep) if x] 565 path_list = [x for x in path_rest.split(sep) if x] 566 # Work out how much of the filepath is shared by start and path. 567 i = 0 568 for e1, e2 in zip(start_list, path_list): 569 if normcase(e1) != normcase(e2): 570 break 571 i += 1 572 573 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 574 if not rel_list: 575 return curdir 576 return join(*rel_list) 577 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): 578 genericpath._check_arg_types('relpath', path, start) 579 raise 580 581 582# Return the longest common sub-path of the sequence of paths given as input. 583# The function is case-insensitive and 'separator-insensitive', i.e. if the 584# only difference between two paths is the use of '\' versus '/' as separator, 585# they are deemed to be equal. 586# 587# However, the returned path will have the standard '\' separator (even if the 588# given paths had the alternative '/' separator) and will have the case of the 589# first path given in the sequence. Additionally, any trailing separator is 590# stripped from the returned path. 591 592def commonpath(paths): 593 """Given a sequence of path names, returns the longest common sub-path.""" 594 595 if not paths: 596 raise ValueError('commonpath() arg is an empty sequence') 597 598 paths = tuple(map(os.fspath, paths)) 599 if isinstance(paths[0], bytes): 600 sep = b'\\' 601 altsep = b'/' 602 curdir = b'.' 603 else: 604 sep = '\\' 605 altsep = '/' 606 curdir = '.' 607 608 try: 609 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] 610 split_paths = [p.split(sep) for d, p in drivesplits] 611 612 try: 613 isabs, = set(p[:1] == sep for d, p in drivesplits) 614 except ValueError: 615 raise ValueError("Can't mix absolute and relative paths") from None 616 617 # Check that all drive letters or UNC paths match. The check is made only 618 # now otherwise type errors for mixing strings and bytes would not be 619 # caught. 620 if len(set(d for d, p in drivesplits)) != 1: 621 raise ValueError("Paths don't have the same drive") 622 623 drive, path = splitdrive(paths[0].replace(altsep, sep)) 624 common = path.split(sep) 625 common = [c for c in common if c and c != curdir] 626 627 split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 628 s1 = min(split_paths) 629 s2 = max(split_paths) 630 for i, c in enumerate(s1): 631 if c != s2[i]: 632 common = common[:i] 633 break 634 else: 635 common = common[:len(s1)] 636 637 prefix = drive + sep if isabs else drive 638 return prefix + sep.join(common) 639 except (TypeError, AttributeError): 640 genericpath._check_arg_types('commonpath', *paths) 641 raise 642 643 644# determine if two files are in fact the same file 645try: 646 # GetFinalPathNameByHandle is available starting with Windows 6.0. 647 # Windows XP and non-Windows OS'es will mock _getfinalpathname. 648 if sys.getwindowsversion()[:2] >= (6, 0): 649 from nt import _getfinalpathname 650 else: 651 raise ImportError 652except (AttributeError, ImportError): 653 # On Windows XP and earlier, two files are the same if their absolute 654 # pathnames are the same. 655 # Non-Windows operating systems fake this method with an XP 656 # approximation. 657 def _getfinalpathname(f): 658 return normcase(abspath(f)) 659 660 661try: 662 # The genericpath.isdir implementation uses os.stat and checks the mode 663 # attribute to tell whether or not the path is a directory. 664 # This is overkill on Windows - just pass the path to GetFileAttributes 665 # and check the attribute from there. 666 from nt import _isdir as isdir 667except ImportError: 668 # Use genericpath.isdir as imported above. 669 pass 670