• 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
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
27__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
28           "basename","dirname","commonprefix","getsize","getmtime",
29           "getatime","getctime", "islink","exists","lexists","isdir","isfile",
30           "ismount", "expanduser","expandvars","normpath","abspath",
31           "curdir","pardir","sep","pathsep","defpath","altsep",
32           "extsep","devnull","realpath","supports_unicode_filenames","relpath",
33           "samefile", "sameopenfile", "samestat", "commonpath",
34           "ALLOW_MISSING"]
35
36def _get_bothseps(path):
37    if isinstance(path, bytes):
38        return b'\\/'
39    else:
40        return '\\/'
41
42# Normalize the case of a pathname and map slashes to backslashes.
43# Other normalizations (such as optimizing '../' away) are not done
44# (this is done by normpath).
45
46try:
47    from _winapi import (
48        LCMapStringEx as _LCMapStringEx,
49        LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT,
50        LCMAP_LOWERCASE as _LCMAP_LOWERCASE)
51
52    def normcase(s):
53        """Normalize case of pathname.
54
55        Makes all characters lowercase and all slashes into backslashes.
56        """
57        s = os.fspath(s)
58        if not s:
59            return s
60        if isinstance(s, bytes):
61            encoding = sys.getfilesystemencoding()
62            s = s.decode(encoding, 'surrogateescape').replace('/', '\\')
63            s = _LCMapStringEx(_LOCALE_NAME_INVARIANT,
64                               _LCMAP_LOWERCASE, s)
65            return s.encode(encoding, 'surrogateescape')
66        else:
67            return _LCMapStringEx(_LOCALE_NAME_INVARIANT,
68                                  _LCMAP_LOWERCASE,
69                                  s.replace('/', '\\'))
70except ImportError:
71    def normcase(s):
72        """Normalize case of pathname.
73
74        Makes all characters lowercase and all slashes into backslashes.
75        """
76        s = os.fspath(s)
77        if isinstance(s, bytes):
78            return os.fsencode(os.fsdecode(s).replace('/', '\\').lower())
79        return s.replace('/', '\\').lower()
80
81
82# Return whether a path is absolute.
83# Trivial in Posix, harder on Windows.
84# For Windows it is absolute if it starts with a slash or backslash (current
85# volume), or if a pathname after the volume-letter-and-colon or UNC-resource
86# starts with a slash or backslash.
87
88def isabs(s):
89    """Test whether a path is absolute"""
90    s = os.fspath(s)
91    if isinstance(s, bytes):
92        sep = b'\\'
93        altsep = b'/'
94        colon_sep = b':\\'
95    else:
96        sep = '\\'
97        altsep = '/'
98        colon_sep = ':\\'
99    s = s[:3].replace(altsep, sep)
100    # Absolute: UNC, device, and paths with a drive and root.
101    # LEGACY BUG: isabs("/x") should be false since the path has no drive.
102    if s.startswith(sep) or s.startswith(colon_sep, 1):
103        return True
104    return False
105
106
107# Join two (or more) paths.
108def join(path, *paths):
109    path = os.fspath(path)
110    if isinstance(path, bytes):
111        sep = b'\\'
112        seps = b'\\/'
113        colon = b':'
114    else:
115        sep = '\\'
116        seps = '\\/'
117        colon = ':'
118    try:
119        if not paths:
120            path[:0] + sep  #23780: Ensure compatible data type even if p is null.
121        result_drive, result_path = splitdrive(path)
122        for p in map(os.fspath, paths):
123            p_drive, p_path = splitdrive(p)
124            if p_path and p_path[0] in seps:
125                # Second path is absolute
126                if p_drive or not result_drive:
127                    result_drive = p_drive
128                result_path = p_path
129                continue
130            elif p_drive and p_drive != result_drive:
131                if p_drive.lower() != result_drive.lower():
132                    # Different drives => ignore the first path entirely
133                    result_drive = p_drive
134                    result_path = p_path
135                    continue
136                # Same drive in different case
137                result_drive = p_drive
138            # Second path is relative to the first
139            if result_path and result_path[-1] not in seps:
140                result_path = result_path + sep
141            result_path = result_path + p_path
142        ## add separator between UNC and non-absolute path
143        if (result_path and result_path[0] not in seps and
144            result_drive and result_drive[-1:] != colon):
145            return result_drive + sep + result_path
146        return result_drive + result_path
147    except (TypeError, AttributeError, BytesWarning):
148        genericpath._check_arg_types('join', path, *paths)
149        raise
150
151
152# Split a path in a drive specification (a drive letter followed by a
153# colon) and the path specification.
154# It is always true that drivespec + pathspec == p
155def splitdrive(p):
156    """Split a pathname into drive/UNC sharepoint and relative path specifiers.
157    Returns a 2-tuple (drive_or_unc, path); either part may be empty.
158
159    If you assign
160        result = splitdrive(p)
161    It is always true that:
162        result[0] + result[1] == p
163
164    If the path contained a drive letter, drive_or_unc will contain everything
165    up to and including the colon.  e.g. splitdrive("c:/dir") returns ("c:", "/dir")
166
167    If the path contained a UNC path, the drive_or_unc will contain the host name
168    and share up to but not including the fourth directory separator character.
169    e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
170
171    Paths cannot contain both a drive letter and a UNC path.
172
173    """
174    p = os.fspath(p)
175    if len(p) >= 2:
176        if isinstance(p, bytes):
177            sep = b'\\'
178            altsep = b'/'
179            colon = b':'
180            unc_prefix = b'\\\\?\\UNC\\'
181        else:
182            sep = '\\'
183            altsep = '/'
184            colon = ':'
185            unc_prefix = '\\\\?\\UNC\\'
186        normp = p.replace(altsep, sep)
187        if normp[0:2] == sep * 2:
188            # UNC drives, e.g. \\server\share or \\?\UNC\server\share
189            # Device drives, e.g. \\.\device or \\?\device
190            start = 8 if normp[:8].upper() == unc_prefix else 2
191            index = normp.find(sep, start)
192            if index == -1:
193                return p, p[:0]
194            index2 = normp.find(sep, index + 1)
195            if index2 == -1:
196                return p, p[:0]
197            return p[:index2], p[index2:]
198        if normp[1:2] == colon:
199            # Drive-letter drives, e.g. X:
200            return p[:2], p[2:]
201    return p[:0], p
202
203
204# Split a path in head (everything up to the last '/') and tail (the
205# rest).  After the trailing '/' is stripped, the invariant
206# join(head, tail) == p holds.
207# The resulting head won't end in '/' unless it is the root.
208
209def split(p):
210    """Split a pathname.
211
212    Return tuple (head, tail) where tail is everything after the final slash.
213    Either part may be empty."""
214    p = os.fspath(p)
215    seps = _get_bothseps(p)
216    d, p = splitdrive(p)
217    # set i to index beyond p's last slash
218    i = len(p)
219    while i and p[i-1] not in seps:
220        i -= 1
221    head, tail = p[:i], p[i:]  # now tail has no slashes
222    # remove trailing slashes from head, unless it's all slashes
223    head = head.rstrip(seps) or head
224    return d + head, tail
225
226
227# Split a path in root and extension.
228# The extension is everything starting at the last dot in the last
229# pathname component; the root is everything before that.
230# It is always true that root + ext == p.
231
232def splitext(p):
233    p = os.fspath(p)
234    if isinstance(p, bytes):
235        return genericpath._splitext(p, b'\\', b'/', b'.')
236    else:
237        return genericpath._splitext(p, '\\', '/', '.')
238splitext.__doc__ = genericpath._splitext.__doc__
239
240
241# Return the tail (basename) part of a path.
242
243def basename(p):
244    """Returns the final component of a pathname"""
245    return split(p)[1]
246
247
248# Return the head (dirname) part of a path.
249
250def dirname(p):
251    """Returns the directory component of a pathname"""
252    return split(p)[0]
253
254# Is a path a symbolic link?
255# This will always return false on systems where os.lstat doesn't exist.
256
257def islink(path):
258    """Test whether a path is a symbolic link.
259    This will always return false for Windows prior to 6.0.
260    """
261    try:
262        st = os.lstat(path)
263    except (OSError, ValueError, AttributeError):
264        return False
265    return stat.S_ISLNK(st.st_mode)
266
267# Being true for dangling symbolic links is also useful.
268
269def lexists(path):
270    """Test whether a path exists.  Returns True for broken symbolic links"""
271    try:
272        st = os.lstat(path)
273    except (OSError, ValueError):
274        return False
275    return True
276
277# Is a path a mount point?
278# Any drive letter root (eg c:\)
279# Any share UNC (eg \\server\share)
280# Any volume mounted on a filesystem folder
281#
282# No one method detects all three situations. Historically we've lexically
283# detected drive letter roots and share UNCs. The canonical approach to
284# detecting mounted volumes (querying the reparse tag) fails for the most
285# common case: drive letter roots. The alternative which uses GetVolumePathName
286# fails if the drive letter is the result of a SUBST.
287try:
288    from nt import _getvolumepathname
289except ImportError:
290    _getvolumepathname = None
291def ismount(path):
292    """Test whether a path is a mount point (a drive root, the root of a
293    share, or a mounted volume)"""
294    path = os.fspath(path)
295    seps = _get_bothseps(path)
296    path = abspath(path)
297    root, rest = splitdrive(path)
298    if root and root[0] in seps:
299        return (not rest) or (rest in seps)
300    if rest and rest in seps:
301        return True
302
303    if _getvolumepathname:
304        x = path.rstrip(seps)
305        y =_getvolumepathname(path).rstrip(seps)
306        return x.casefold() == y.casefold()
307    else:
308        return False
309
310
311# Expand paths beginning with '~' or '~user'.
312# '~' means $HOME; '~user' means that user's home directory.
313# If the path doesn't begin with '~', or if the user or $HOME is unknown,
314# the path is returned unchanged (leaving error reporting to whatever
315# function is called with the expanded path as argument).
316# See also module 'glob' for expansion of *, ? and [...] in pathnames.
317# (A function should also be defined to do full *sh-style environment
318# variable expansion.)
319
320def expanduser(path):
321    """Expand ~ and ~user constructs.
322
323    If user or $HOME is unknown, do nothing."""
324    path = os.fspath(path)
325    if isinstance(path, bytes):
326        tilde = b'~'
327    else:
328        tilde = '~'
329    if not path.startswith(tilde):
330        return path
331    i, n = 1, len(path)
332    while i < n and path[i] not in _get_bothseps(path):
333        i += 1
334
335    if 'USERPROFILE' in os.environ:
336        userhome = os.environ['USERPROFILE']
337    elif not 'HOMEPATH' in os.environ:
338        return path
339    else:
340        try:
341            drive = os.environ['HOMEDRIVE']
342        except KeyError:
343            drive = ''
344        userhome = join(drive, os.environ['HOMEPATH'])
345
346    if i != 1: #~user
347        target_user = path[1:i]
348        if isinstance(target_user, bytes):
349            target_user = os.fsdecode(target_user)
350        current_user = os.environ.get('USERNAME')
351
352        if target_user != current_user:
353            # Try to guess user home directory.  By default all user
354            # profile directories are located in the same place and are
355            # named by corresponding usernames.  If userhome isn't a
356            # normal profile directory, this guess is likely wrong,
357            # so we bail out.
358            if current_user != basename(userhome):
359                return path
360            userhome = join(dirname(userhome), target_user)
361
362    if isinstance(path, bytes):
363        userhome = os.fsencode(userhome)
364
365    return userhome + path[i:]
366
367
368# Expand paths containing shell variable substitutions.
369# The following rules apply:
370#       - no expansion within single quotes
371#       - '$$' is translated into '$'
372#       - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
373#       - ${varname} is accepted.
374#       - $varname is accepted.
375#       - %varname% is accepted.
376#       - varnames can be made out of letters, digits and the characters '_-'
377#         (though is not verified in the ${varname} and %varname% cases)
378# XXX With COMMAND.COM you can use any characters in a variable name,
379# XXX except '^|<>='.
380
381def expandvars(path):
382    """Expand shell variables of the forms $var, ${var} and %var%.
383
384    Unknown variables are left unchanged."""
385    path = os.fspath(path)
386    if isinstance(path, bytes):
387        if b'$' not in path and b'%' not in path:
388            return path
389        import string
390        varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
391        quote = b'\''
392        percent = b'%'
393        brace = b'{'
394        rbrace = b'}'
395        dollar = b'$'
396        environ = getattr(os, 'environb', None)
397    else:
398        if '$' not in path and '%' not in path:
399            return path
400        import string
401        varchars = string.ascii_letters + string.digits + '_-'
402        quote = '\''
403        percent = '%'
404        brace = '{'
405        rbrace = '}'
406        dollar = '$'
407        environ = os.environ
408    res = path[:0]
409    index = 0
410    pathlen = len(path)
411    while index < pathlen:
412        c = path[index:index+1]
413        if c == quote:   # no expansion within single quotes
414            path = path[index + 1:]
415            pathlen = len(path)
416            try:
417                index = path.index(c)
418                res += c + path[:index + 1]
419            except ValueError:
420                res += c + path
421                index = pathlen - 1
422        elif c == percent:  # variable or '%'
423            if path[index + 1:index + 2] == percent:
424                res += c
425                index += 1
426            else:
427                path = path[index+1:]
428                pathlen = len(path)
429                try:
430                    index = path.index(percent)
431                except ValueError:
432                    res += percent + path
433                    index = pathlen - 1
434                else:
435                    var = path[:index]
436                    try:
437                        if environ is None:
438                            value = os.fsencode(os.environ[os.fsdecode(var)])
439                        else:
440                            value = environ[var]
441                    except KeyError:
442                        value = percent + var + percent
443                    res += value
444        elif c == dollar:  # variable or '$$'
445            if path[index + 1:index + 2] == dollar:
446                res += c
447                index += 1
448            elif path[index + 1:index + 2] == brace:
449                path = path[index+2:]
450                pathlen = len(path)
451                try:
452                    index = path.index(rbrace)
453                except ValueError:
454                    res += dollar + brace + path
455                    index = pathlen - 1
456                else:
457                    var = path[:index]
458                    try:
459                        if environ is None:
460                            value = os.fsencode(os.environ[os.fsdecode(var)])
461                        else:
462                            value = environ[var]
463                    except KeyError:
464                        value = dollar + brace + var + rbrace
465                    res += value
466            else:
467                var = path[:0]
468                index += 1
469                c = path[index:index + 1]
470                while c and c in varchars:
471                    var += c
472                    index += 1
473                    c = path[index:index + 1]
474                try:
475                    if environ is None:
476                        value = os.fsencode(os.environ[os.fsdecode(var)])
477                    else:
478                        value = environ[var]
479                except KeyError:
480                    value = dollar + var
481                res += value
482                if c:
483                    index -= 1
484        else:
485            res += c
486        index += 1
487    return res
488
489
490# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
491# Previously, this function also truncated pathnames to 8+3 format,
492# but as this module is called "ntpath", that's obviously wrong!
493try:
494    from nt import _path_normpath
495
496except ImportError:
497    def normpath(path):
498        """Normalize path, eliminating double slashes, etc."""
499        path = os.fspath(path)
500        if isinstance(path, bytes):
501            sep = b'\\'
502            altsep = b'/'
503            curdir = b'.'
504            pardir = b'..'
505        else:
506            sep = '\\'
507            altsep = '/'
508            curdir = '.'
509            pardir = '..'
510        path = path.replace(altsep, sep)
511        prefix, path = splitdrive(path)
512
513        # collapse initial backslashes
514        if path.startswith(sep):
515            prefix += sep
516            path = path.lstrip(sep)
517
518        comps = path.split(sep)
519        i = 0
520        while i < len(comps):
521            if not comps[i] or comps[i] == curdir:
522                del comps[i]
523            elif comps[i] == pardir:
524                if i > 0 and comps[i-1] != pardir:
525                    del comps[i-1:i+1]
526                    i -= 1
527                elif i == 0 and prefix.endswith(sep):
528                    del comps[i]
529                else:
530                    i += 1
531            else:
532                i += 1
533        # If the path is now empty, substitute '.'
534        if not prefix and not comps:
535            comps.append(curdir)
536        return prefix + sep.join(comps)
537
538else:
539    def normpath(path):
540        """Normalize path, eliminating double slashes, etc."""
541        path = os.fspath(path)
542        if isinstance(path, bytes):
543            return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
544        return _path_normpath(path) or "."
545
546
547def _abspath_fallback(path):
548    """Return the absolute version of a path as a fallback function in case
549    `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
550    more.
551
552    """
553
554    path = os.fspath(path)
555    if not isabs(path):
556        if isinstance(path, bytes):
557            cwd = os.getcwdb()
558        else:
559            cwd = os.getcwd()
560        path = join(cwd, path)
561    return normpath(path)
562
563# Return an absolute path.
564try:
565    from nt import _getfullpathname
566
567except ImportError: # not running on Windows - mock up something sensible
568    abspath = _abspath_fallback
569
570else:  # use native Windows method on Windows
571    def abspath(path):
572        """Return the absolute version of a path."""
573        try:
574            return _getfullpathname(normpath(path))
575        except (OSError, ValueError):
576            return _abspath_fallback(path)
577
578try:
579    from nt import _getfinalpathname, readlink as _nt_readlink
580except ImportError:
581    # realpath is a no-op on systems without _getfinalpathname support.
582    def realpath(path, *, strict=False):
583        return abspath(path)
584else:
585    def _readlink_deep(path, ignored_error=OSError):
586        # These error codes indicate that we should stop reading links and
587        # return the path we currently have.
588        # 1: ERROR_INVALID_FUNCTION
589        # 2: ERROR_FILE_NOT_FOUND
590        # 3: ERROR_DIRECTORY_NOT_FOUND
591        # 5: ERROR_ACCESS_DENIED
592        # 21: ERROR_NOT_READY (implies drive with no media)
593        # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
594        # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
595        # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
596        # 87: ERROR_INVALID_PARAMETER
597        # 4390: ERROR_NOT_A_REPARSE_POINT
598        # 4392: ERROR_INVALID_REPARSE_DATA
599        # 4393: ERROR_REPARSE_TAG_INVALID
600        allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
601
602        seen = set()
603        while normcase(path) not in seen:
604            seen.add(normcase(path))
605            try:
606                old_path = path
607                path = _nt_readlink(path)
608                # Links may be relative, so resolve them against their
609                # own location
610                if not isabs(path):
611                    # If it's something other than a symlink, we don't know
612                    # what it's actually going to be resolved against, so
613                    # just return the old path.
614                    if not islink(old_path):
615                        path = old_path
616                        break
617                    path = normpath(join(dirname(old_path), path))
618            except ignored_error as ex:
619                if ex.winerror in allowed_winerror:
620                    break
621                raise
622            except ValueError:
623                # Stop on reparse points that are not symlinks
624                break
625        return path
626
627    def _getfinalpathname_nonstrict(path, ignored_error=OSError):
628        # These error codes indicate that we should stop resolving the path
629        # and return the value we currently have.
630        # 1: ERROR_INVALID_FUNCTION
631        # 2: ERROR_FILE_NOT_FOUND
632        # 3: ERROR_DIRECTORY_NOT_FOUND
633        # 5: ERROR_ACCESS_DENIED
634        # 21: ERROR_NOT_READY (implies drive with no media)
635        # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
636        # 50: ERROR_NOT_SUPPORTED
637        # 53: ERROR_BAD_NETPATH
638        # 65: ERROR_NETWORK_ACCESS_DENIED
639        # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
640        # 87: ERROR_INVALID_PARAMETER
641        # 123: ERROR_INVALID_NAME
642        # 161: ERROR_BAD_PATHNAME
643        # 1920: ERROR_CANT_ACCESS_FILE
644        # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
645        allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1920, 1921
646
647        # Non-strict algorithm is to find as much of the target directory
648        # as we can and join the rest.
649        tail = path[:0]
650        while path:
651            try:
652                path = _getfinalpathname(path)
653                return join(path, tail) if tail else path
654            except ignored_error as ex:
655                if ex.winerror not in allowed_winerror:
656                    raise
657                try:
658                    # The OS could not resolve this path fully, so we attempt
659                    # to follow the link ourselves. If we succeed, join the tail
660                    # and return.
661                    new_path = _readlink_deep(path,
662                                              ignored_error=ignored_error)
663                    if new_path != path:
664                        return join(new_path, tail) if tail else new_path
665                except ignored_error:
666                    # If we fail to readlink(), let's keep traversing
667                    pass
668                path, name = split(path)
669                # TODO (bpo-38186): Request the real file name from the directory
670                # entry using FindFirstFileW. For now, we will return the path
671                # as best we have it
672                if path and not name:
673                    return path + tail
674                tail = join(name, tail) if tail else name
675        return tail
676
677    def realpath(path, *, strict=False):
678        path = normpath(path)
679        if isinstance(path, bytes):
680            prefix = b'\\\\?\\'
681            unc_prefix = b'\\\\?\\UNC\\'
682            new_unc_prefix = b'\\\\'
683            cwd = os.getcwdb()
684            # bpo-38081: Special case for realpath(b'nul')
685            if normcase(path) == normcase(os.fsencode(devnull)):
686                return b'\\\\.\\NUL'
687        else:
688            prefix = '\\\\?\\'
689            unc_prefix = '\\\\?\\UNC\\'
690            new_unc_prefix = '\\\\'
691            cwd = os.getcwd()
692            # bpo-38081: Special case for realpath('nul')
693            if normcase(path) == normcase(devnull):
694                return '\\\\.\\NUL'
695        had_prefix = path.startswith(prefix)
696
697        if strict is ALLOW_MISSING:
698            ignored_error = FileNotFoundError
699            strict = True
700        elif strict:
701            ignored_error = ()
702        else:
703            ignored_error = OSError
704
705        if not had_prefix and not isabs(path):
706            path = join(cwd, path)
707        try:
708            path = _getfinalpathname(path)
709            initial_winerror = 0
710        except ValueError as ex:
711            # gh-106242: Raised for embedded null characters
712            # In strict modes, we convert into an OSError.
713            # Non-strict mode returns the path as-is, since we've already
714            # made it absolute.
715            if strict:
716                raise OSError(str(ex)) from None
717            path = normpath(path)
718        except ignored_error as ex:
719            initial_winerror = ex.winerror
720            path = _getfinalpathname_nonstrict(path,
721                                               ignored_error=ignored_error)
722        # The path returned by _getfinalpathname will always start with \\?\ -
723        # strip off that prefix unless it was already provided on the original
724        # path.
725        if not had_prefix and path.startswith(prefix):
726            # For UNC paths, the prefix will actually be \\?\UNC\
727            # Handle that case as well.
728            if path.startswith(unc_prefix):
729                spath = new_unc_prefix + path[len(unc_prefix):]
730            else:
731                spath = path[len(prefix):]
732            # Ensure that the non-prefixed path resolves to the same path
733            try:
734                if _getfinalpathname(spath) == path:
735                    path = spath
736            except ValueError as ex:
737                # Unexpected, as an invalid path should not have gained a prefix
738                # at any point, but we ignore this error just in case.
739                pass
740            except OSError as ex:
741                # If the path does not exist and originally did not exist, then
742                # strip the prefix anyway.
743                if ex.winerror == initial_winerror:
744                    path = spath
745        return path
746
747
748# Win9x family and earlier have no Unicode filename support.
749supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
750                              sys.getwindowsversion()[3] >= 2)
751
752def relpath(path, start=None):
753    """Return a relative version of a path"""
754    path = os.fspath(path)
755    if isinstance(path, bytes):
756        sep = b'\\'
757        curdir = b'.'
758        pardir = b'..'
759    else:
760        sep = '\\'
761        curdir = '.'
762        pardir = '..'
763
764    if start is None:
765        start = curdir
766
767    if not path:
768        raise ValueError("no path specified")
769
770    start = os.fspath(start)
771    try:
772        start_abs = abspath(normpath(start))
773        path_abs = abspath(normpath(path))
774        start_drive, start_rest = splitdrive(start_abs)
775        path_drive, path_rest = splitdrive(path_abs)
776        if normcase(start_drive) != normcase(path_drive):
777            raise ValueError("path is on mount %r, start on mount %r" % (
778                path_drive, start_drive))
779
780        start_list = [x for x in start_rest.split(sep) if x]
781        path_list = [x for x in path_rest.split(sep) if x]
782        # Work out how much of the filepath is shared by start and path.
783        i = 0
784        for e1, e2 in zip(start_list, path_list):
785            if normcase(e1) != normcase(e2):
786                break
787            i += 1
788
789        rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
790        if not rel_list:
791            return curdir
792        return join(*rel_list)
793    except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
794        genericpath._check_arg_types('relpath', path, start)
795        raise
796
797
798# Return the longest common sub-path of the sequence of paths given as input.
799# The function is case-insensitive and 'separator-insensitive', i.e. if the
800# only difference between two paths is the use of '\' versus '/' as separator,
801# they are deemed to be equal.
802#
803# However, the returned path will have the standard '\' separator (even if the
804# given paths had the alternative '/' separator) and will have the case of the
805# first path given in the sequence. Additionally, any trailing separator is
806# stripped from the returned path.
807
808def commonpath(paths):
809    """Given a sequence of path names, returns the longest common sub-path."""
810
811    if not paths:
812        raise ValueError('commonpath() arg is an empty sequence')
813
814    paths = tuple(map(os.fspath, paths))
815    if isinstance(paths[0], bytes):
816        sep = b'\\'
817        altsep = b'/'
818        curdir = b'.'
819    else:
820        sep = '\\'
821        altsep = '/'
822        curdir = '.'
823
824    try:
825        drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
826        split_paths = [p.split(sep) for d, p in drivesplits]
827
828        try:
829            isabs, = set(p[:1] == sep for d, p in drivesplits)
830        except ValueError:
831            raise ValueError("Can't mix absolute and relative paths") from None
832
833        # Check that all drive letters or UNC paths match. The check is made only
834        # now otherwise type errors for mixing strings and bytes would not be
835        # caught.
836        if len(set(d for d, p in drivesplits)) != 1:
837            raise ValueError("Paths don't have the same drive")
838
839        drive, path = splitdrive(paths[0].replace(altsep, sep))
840        common = path.split(sep)
841        common = [c for c in common if c and c != curdir]
842
843        split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
844        s1 = min(split_paths)
845        s2 = max(split_paths)
846        for i, c in enumerate(s1):
847            if c != s2[i]:
848                common = common[:i]
849                break
850        else:
851            common = common[:len(s1)]
852
853        prefix = drive + sep if isabs else drive
854        return prefix + sep.join(common)
855    except (TypeError, AttributeError):
856        genericpath._check_arg_types('commonpath', *paths)
857        raise
858
859
860try:
861    # The genericpath.isdir implementation uses os.stat and checks the mode
862    # attribute to tell whether or not the path is a directory.
863    # This is overkill on Windows - just pass the path to GetFileAttributes
864    # and check the attribute from there.
865    from nt import _isdir as isdir
866except ImportError:
867    # Use genericpath.isdir as imported above.
868    pass
869