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