1 2# Module 'ntpath' -- common operations on WinNT/Win95 and UEFI pathnames. 3# 4# Copyright (c) 2015, Daryl McDaniel. All rights reserved.<BR> 5# Copyright (c) 2011 - 2012, Intel Corporation. All rights reserved.<BR> 6# This program and the accompanying materials are licensed and made available under 7# the terms and conditions of the BSD License that accompanies this distribution. 8# The full text of the license may be found at 9# http://opensource.org/licenses/bsd-license. 10# 11# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 12# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 14 15"""Common pathname manipulations, WindowsNT/95 and UEFI version. 16 17Instead of importing this module directly, import os and refer to this 18module as os.path. 19""" 20 21import os 22import sys 23import stat 24import genericpath 25import warnings 26 27from genericpath import * 28from genericpath import _unicode 29 30__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 31 "basename","dirname","commonprefix","getsize","getmtime", 32 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 33 "ismount","walk","expanduser","expandvars","normpath","abspath", 34 "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", 35 "extsep","devnull","realpath","supports_unicode_filenames","relpath"] 36 37# strings representing various path-related bits and pieces 38curdir = '.' 39pardir = '..' 40extsep = '.' 41sep = '\\' 42pathsep = ';' 43altsep = '/' 44defpath = '.;C:\\bin' 45if 'ce' in sys.builtin_module_names: 46 defpath = '\\Windows' 47elif 'os2' in sys.builtin_module_names: 48 # OS/2 w/ VACPP 49 altsep = '/' 50devnull = 'nul' 51 52# Normalize the case of a pathname and map slashes to backslashes. 53# Other normalizations (such as optimizing '../' away) are not done 54# (this is done by normpath). 55 56def normcase(s): 57 """Normalize case of pathname. 58 59 Makes all characters lowercase and all slashes into backslashes.""" 60 return s.replace("/", "\\").lower() 61 62 63# Return whether a path is absolute. 64# Trivial in Posix, harder on the Mac or MS-DOS. 65# For DOS it is absolute if it starts with a slash or backslash (current 66# volume), or if a pathname after the volume letter and colon / UNC resource 67# starts with a slash or backslash. 68 69def isabs(s): 70 """Test whether a path is absolute""" 71 s = splitdrive(s)[1] 72 return s != '' and s[:1] in '/\\' 73 74 75# Join two (or more) paths. 76def join(path, *paths): 77 """Join two or more pathname components, inserting "\\" as needed.""" 78 result_drive, result_path = splitdrive(path) 79 for p in paths: 80 p_drive, p_path = splitdrive(p) 81 if p_path and p_path[0] in '\\/': 82 # Second path is absolute 83 if p_drive or not result_drive: 84 result_drive = p_drive 85 result_path = p_path 86 continue 87 elif p_drive and p_drive != result_drive: 88 if p_drive.lower() != result_drive.lower(): 89 # Different drives => ignore the first path entirely 90 result_drive = p_drive 91 result_path = p_path 92 continue 93 # Same drive in different case 94 result_drive = p_drive 95 # Second path is relative to the first 96 if result_path and result_path[-1] not in '\\/': 97 result_path = result_path + '\\' 98 result_path = result_path + p_path 99 ## add separator between UNC and non-absolute path 100 if (result_path and result_path[0] not in '\\/' and 101 result_drive and result_drive[-1:] != ':'): 102 return result_drive + sep + result_path 103 return result_drive + result_path 104 105 106# Split a path in a drive specification (a drive letter followed by a 107# colon) and the path specification. 108# It is always true that drivespec + pathspec == p 109# NOTE: for UEFI (and even Windows) you can have multiple characters to the left 110# of the ':' for the device or drive spec. This is reflected in the modifications 111# to splitdrive() and splitunc(). 112def splitdrive(p): 113 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 114 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 115 116 If you assign 117 result = splitdrive(p) 118 It is always true that: 119 result[0] + result[1] == p 120 121 If the path contained a drive letter, drive_or_unc will contain everything 122 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 123 124 If the path contained a UNC path, the drive_or_unc will contain the host name 125 and share up to but not including the fourth directory separator character. 126 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 127 128 Paths cannot contain both a drive letter and a UNC path. 129 130 """ 131 if len(p) > 1: 132 normp = p.replace(altsep, sep) 133 if (normp[0:2] == sep*2) and (normp[2:3] != sep): 134 # is a UNC path: 135 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path 136 # \\machine\mountpoint\directory\etc\... 137 # directory ^^^^^^^^^^^^^^^ 138 index = normp.find(sep, 2) 139 if index == -1: 140 return '', p 141 index2 = normp.find(sep, index + 1) 142 # a UNC path can't have two slashes in a row 143 # (after the initial two) 144 if index2 == index + 1: 145 return '', p 146 if index2 == -1: 147 index2 = len(p) 148 return p[:index2], p[index2:] 149 index = p.find(':') 150 if index != -1: 151 index = index + 1 152 return p[:index], p[index:] 153 return '', p 154 155# Parse UNC paths 156def splitunc(p): 157 """Split a pathname into UNC mount point and relative path specifiers. 158 159 Return a 2-tuple (unc, rest); either part may be empty. 160 If unc is not empty, it has the form '//host/mount' (or similar 161 using backslashes). unc+rest is always the input path. 162 Paths containing drive letters never have an UNC part. 163 """ 164 if ':' in p: 165 return '', p # Drive letter or device name present 166 firstTwo = p[0:2] 167 if firstTwo == '//' or firstTwo == '\\\\': 168 # is a UNC path: 169 # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter 170 # \\machine\mountpoint\directories... 171 # directory ^^^^^^^^^^^^^^^ 172 normp = p.replace('\\', '/') 173 index = normp.find('/', 2) 174 if index <= 2: 175 return '', p 176 index2 = normp.find('/', index + 1) 177 # a UNC path can't have two slashes in a row 178 # (after the initial two) 179 if index2 == index + 1: 180 return '', p 181 if index2 == -1: 182 index2 = len(p) 183 return p[:index2], p[index2:] 184 return '', p 185 186 187# Split a path in head (everything up to the last '/') and tail (the 188# rest). After the trailing '/' is stripped, the invariant 189# join(head, tail) == p holds. 190# The resulting head won't end in '/' unless it is the root. 191 192def split(p): 193 """Split a pathname. 194 195 Return tuple (head, tail) where tail is everything after the final slash. 196 Either part may be empty.""" 197 198 d, p = splitdrive(p) 199 # set i to index beyond p's last slash 200 i = len(p) 201 while i and p[i-1] not in '/\\': 202 i = i - 1 203 head, tail = p[:i], p[i:] # now tail has no slashes 204 # remove trailing slashes from head, unless it's all slashes 205 head2 = head 206 while head2 and head2[-1] in '/\\': 207 head2 = head2[:-1] 208 head = head2 or head 209 return d + head, tail 210 211 212# Split a path in root and extension. 213# The extension is everything starting at the last dot in the last 214# pathname component; the root is everything before that. 215# It is always true that root + ext == p. 216 217def splitext(p): 218 return genericpath._splitext(p, sep, altsep, extsep) 219splitext.__doc__ = genericpath._splitext.__doc__ 220 221 222# Return the tail (basename) part of a path. 223 224def basename(p): 225 """Returns the final component of a pathname""" 226 return split(p)[1] 227 228 229# Return the head (dirname) part of a path. 230 231def dirname(p): 232 """Returns the directory component of a pathname""" 233 return split(p)[0] 234 235# Is a path a symbolic link? 236# This will always return false on systems where posix.lstat doesn't exist. 237 238def islink(path): 239 """Test for symbolic link. 240 On WindowsNT/95 and OS/2 always returns false 241 """ 242 return False 243 244# alias exists to lexists 245lexists = exists 246 247# Is a path a mount point? Either a root (with or without drive letter) 248# or an UNC path with at most a / or \ after the mount point. 249 250def ismount(path): 251 """Test whether a path is a mount point (defined as root of drive)""" 252 unc, rest = splitunc(path) 253 if unc: 254 return rest in ("", "/", "\\") 255 p = splitdrive(path)[1] 256 return len(p) == 1 and p[0] in '/\\' 257 258 259# Directory tree walk. 260# For each directory under top (including top itself, but excluding 261# '.' and '..'), func(arg, dirname, filenames) is called, where 262# dirname is the name of the directory and filenames is the list 263# of files (and subdirectories etc.) in the directory. 264# The func may modify the filenames list, to implement a filter, 265# or to impose a different order of visiting. 266 267def walk(top, func, arg): 268 """Directory tree walk with callback function. 269 270 For each directory in the directory tree rooted at top (including top 271 itself, but excluding '.' and '..'), call func(arg, dirname, fnames). 272 dirname is the name of the directory, and fnames a list of the names of 273 the files and subdirectories in dirname (excluding '.' and '..'). func 274 may modify the fnames list in-place (e.g. via del or slice assignment), 275 and walk will only recurse into the subdirectories whose names remain in 276 fnames; this can be used to implement a filter, or to impose a specific 277 order of visiting. No semantics are defined for, or required of, arg, 278 beyond that arg is always passed to func. It can be used, e.g., to pass 279 a filename pattern, or a mutable object designed to accumulate 280 statistics. Passing None for arg is common.""" 281 warnings.warnpy3k("In 3.x, os.path.walk is removed in favor of os.walk.", 282 stacklevel=2) 283 try: 284 names = os.listdir(top) 285 except os.error: 286 return 287 func(arg, top, names) 288 for name in names: 289 name = join(top, name) 290 if isdir(name): 291 walk(name, func, arg) 292 293 294# Expand paths beginning with '~' or '~user'. 295# '~' means $HOME; '~user' means that user's home directory. 296# If the path doesn't begin with '~', or if the user or $HOME is unknown, 297# the path is returned unchanged (leaving error reporting to whatever 298# function is called with the expanded path as argument). 299# See also module 'glob' for expansion of *, ? and [...] in pathnames. 300# (A function should also be defined to do full *sh-style environment 301# variable expansion.) 302 303def expanduser(path): 304 """Expand ~ and ~user constructs. 305 306 If user or $HOME is unknown, do nothing.""" 307 if path[:1] != '~': 308 return path 309 i, n = 1, len(path) 310 while i < n and path[i] not in '/\\': 311 i = i + 1 312 313 if 'HOME' in os.environ: 314 userhome = os.environ['HOME'] 315 elif 'USERPROFILE' in os.environ: 316 userhome = os.environ['USERPROFILE'] 317 elif not 'HOMEPATH' in os.environ: 318 return path 319 else: 320 try: 321 drive = os.environ['HOMEDRIVE'] 322 except KeyError: 323 drive = '' 324 userhome = join(drive, os.environ['HOMEPATH']) 325 326 if i != 1: #~user 327 userhome = join(dirname(userhome), path[1:i]) 328 329 return userhome + path[i:] 330 331 332# Expand paths containing shell variable substitutions. 333# The following rules apply: 334# - no expansion within single quotes 335# - '$$' is translated into '$' 336# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 337# - ${varname} is accepted. 338# - $varname is accepted. 339# - %varname% is accepted. 340# - varnames can be made out of letters, digits and the characters '_-' 341# (though is not verified in the ${varname} and %varname% cases) 342# XXX With COMMAND.COM you can use any characters in a variable name, 343# XXX except '^|<>='. 344 345def expandvars(path): 346 """Expand shell variables of the forms $var, ${var} and %var%. 347 348 Unknown variables are left unchanged.""" 349 if '$' not in path and '%' not in path: 350 return path 351 import string 352 varchars = string.ascii_letters + string.digits + '_-' 353 if isinstance(path, _unicode): 354 encoding = sys.getfilesystemencoding() 355 def getenv(var): 356 return os.environ[var.encode(encoding)].decode(encoding) 357 else: 358 def getenv(var): 359 return os.environ[var] 360 res = '' 361 index = 0 362 pathlen = len(path) 363 while index < pathlen: 364 c = path[index] 365 if c == '\'': # no expansion within single quotes 366 path = path[index + 1:] 367 pathlen = len(path) 368 try: 369 index = path.index('\'') 370 res = res + '\'' + path[:index + 1] 371 except ValueError: 372 res = res + c + path 373 index = pathlen - 1 374 elif c == '%': # variable or '%' 375 if path[index + 1:index + 2] == '%': 376 res = res + c 377 index = index + 1 378 else: 379 path = path[index+1:] 380 pathlen = len(path) 381 try: 382 index = path.index('%') 383 except ValueError: 384 res = res + '%' + path 385 index = pathlen - 1 386 else: 387 var = path[:index] 388 try: 389 res = res + getenv(var) 390 except KeyError: 391 res = res + '%' + var + '%' 392 elif c == '$': # variable or '$$' 393 if path[index + 1:index + 2] == '$': 394 res = res + c 395 index = index + 1 396 elif path[index + 1:index + 2] == '{': 397 path = path[index+2:] 398 pathlen = len(path) 399 try: 400 index = path.index('}') 401 var = path[:index] 402 try: 403 res = res + getenv(var) 404 except KeyError: 405 res = res + '${' + var + '}' 406 except ValueError: 407 res = res + '${' + path 408 index = pathlen - 1 409 else: 410 var = '' 411 index = index + 1 412 c = path[index:index + 1] 413 while c != '' and c in varchars: 414 var = var + c 415 index = index + 1 416 c = path[index:index + 1] 417 try: 418 res = res + getenv(var) 419 except KeyError: 420 res = res + '$' + var 421 if c != '': 422 index = index - 1 423 else: 424 res = res + c 425 index = index + 1 426 return res 427 428 429# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 430# Previously, this function also truncated pathnames to 8+3 format, 431# but as this module is called "ntpath", that's obviously wrong! 432 433def normpath(path): 434 """Normalize path, eliminating double slashes, etc.""" 435 # Preserve unicode (if path is unicode) 436 backslash, dot = (u'\\', u'.') if isinstance(path, _unicode) else ('\\', '.') 437 if path.startswith(('\\\\.\\', '\\\\?\\')): 438 # in the case of paths with these prefixes: 439 # \\.\ -> device names 440 # \\?\ -> literal paths 441 # do not do any normalization, but return the path unchanged 442 return path 443 path = path.replace("/", "\\") 444 prefix, path = splitdrive(path) 445 # We need to be careful here. If the prefix is empty, and the path starts 446 # with a backslash, it could either be an absolute path on the current 447 # drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It 448 # is therefore imperative NOT to collapse multiple backslashes blindly in 449 # that case. 450 # The code below preserves multiple backslashes when there is no drive 451 # letter. This means that the invalid filename \\\a\b is preserved 452 # unchanged, where a\\\b is normalised to a\b. It's not clear that there 453 # is any better behaviour for such edge cases. 454 if prefix == '': 455 # No drive letter - preserve initial backslashes 456 while path[:1] == "\\": 457 prefix = prefix + backslash 458 path = path[1:] 459 else: 460 # We have a drive letter - collapse initial backslashes 461 if path.startswith("\\"): 462 prefix = prefix + backslash 463 path = path.lstrip("\\") 464 comps = path.split("\\") 465 i = 0 466 while i < len(comps): 467 if comps[i] in ('.', ''): 468 del comps[i] 469 elif comps[i] == '..': 470 if i > 0 and comps[i-1] != '..': 471 del comps[i-1:i+1] 472 i -= 1 473 elif i == 0 and prefix.endswith("\\"): 474 del comps[i] 475 else: 476 i += 1 477 else: 478 i += 1 479 # If the path is now empty, substitute '.' 480 if not prefix and not comps: 481 comps.append(dot) 482 return prefix + backslash.join(comps) 483 484 485# Return an absolute path. 486try: 487 from nt import _getfullpathname 488 489except ImportError: # not running on Windows - mock up something sensible 490 def abspath(path): 491 """Return the absolute version of a path.""" 492 if not isabs(path): 493 if isinstance(path, _unicode): 494 cwd = os.getcwdu() 495 else: 496 cwd = os.getcwd() 497 path = join(cwd, path) 498 return normpath(path) 499 500else: # use native Windows method on Windows 501 def abspath(path): 502 """Return the absolute version of a path.""" 503 504 if path: # Empty path must return current working directory. 505 try: 506 path = _getfullpathname(path) 507 except WindowsError: 508 pass # Bad path - return unchanged. 509 elif isinstance(path, _unicode): 510 path = os.getcwdu() 511 else: 512 path = os.getcwd() 513 return normpath(path) 514 515# realpath is a no-op on systems without islink support 516realpath = abspath 517# Win9x family and earlier have no Unicode filename support. 518supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 519 sys.getwindowsversion()[3] >= 2) 520 521def _abspath_split(path): 522 abs = abspath(normpath(path)) 523 prefix, rest = splitunc(abs) 524 is_unc = bool(prefix) 525 if not is_unc: 526 prefix, rest = splitdrive(abs) 527 return is_unc, prefix, [x for x in rest.split(sep) if x] 528 529def relpath(path, start=curdir): 530 """Return a relative version of a path""" 531 532 if not path: 533 raise ValueError("no path specified") 534 535 start_is_unc, start_prefix, start_list = _abspath_split(start) 536 path_is_unc, path_prefix, path_list = _abspath_split(path) 537 538 if path_is_unc ^ start_is_unc: 539 raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" 540 % (path, start)) 541 if path_prefix.lower() != start_prefix.lower(): 542 if path_is_unc: 543 raise ValueError("path is on UNC root %s, start on UNC root %s" 544 % (path_prefix, start_prefix)) 545 else: 546 raise ValueError("path is on drive %s, start on drive %s" 547 % (path_prefix, start_prefix)) 548 # Work out how much of the filepath is shared by start and path. 549 i = 0 550 for e1, e2 in zip(start_list, path_list): 551 if e1.lower() != e2.lower(): 552 break 553 i += 1 554 555 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 556 if not rel_list: 557 return curdir 558 return join(*rel_list) 559 560try: 561 # The genericpath.isdir implementation uses os.stat and checks the mode 562 # attribute to tell whether or not the path is a directory. 563 # This is overkill on Windows - just pass the path to GetFileAttributes 564 # and check the attribute from there. 565 from nt import _isdir as isdir 566except ImportError: 567 # Use genericpath.isdir as imported above. 568 pass 569