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