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