Home | History | Annotate | Download | only in Lib
      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 
     17 Instead of importing this module directly, import os and refer to this
     18 module as os.path.
     19 """
     20 
     21 import os
     22 import sys
     23 import stat
     24 import genericpath
     25 import warnings
     26 
     27 from genericpath import *
     28 from 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

     38 curdir = '.'
     39 pardir = '..'
     40 extsep = '.'
     41 sep = '\\'
     42 pathsep = ';'
     43 altsep = '/'
     44 defpath = '.;C:\\bin'
     45 if 'ce' in sys.builtin_module_names:
     46     defpath = '\\Windows'
     47 elif 'os2' in sys.builtin_module_names:
     48     # OS/2 w/ VACPP
     49     altsep = '/'
     50 devnull = '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 
     56 def 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 
     69 def 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.
     76 def 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().
    112 def 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

    156 def 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 
    192 def 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 
    217 def splitext(p):
    218     return genericpath._splitext(p, sep, altsep, extsep)
    219 splitext.__doc__ = genericpath._splitext.__doc__
    220 
    221 
    222 # Return the tail (basename) part of a path.
    223 
    224 def 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 
    231 def 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 
    238 def 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

    245 lexists = 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 
    250 def 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 
    267 def 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 
    303 def 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 
    345 def 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 
    433 def 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.

    486 try:
    487     from nt import _getfullpathname
    488 
    489 except 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 
    500 else:  # 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

    516 realpath = abspath
    517 # Win9x family and earlier have no Unicode filename support.

    518 supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
    519                               sys.getwindowsversion()[3] >= 2)
    520 
    521 def _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 
    529 def 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 
    560 try:
    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
    566 except ImportError:
    567     # Use genericpath.isdir as imported above.

    568     pass
    569