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] 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 in seps:
272        return True
273
274    if _getvolumepathname:
275        return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
276    else:
277        return False
278
279
280# Expand paths beginning with '~' or '~user'.
281# '~' means $HOME; '~user' means that user's home directory.
282# If the path doesn't begin with '~', or if the user or $HOME is unknown,
283# the path is returned unchanged (leaving error reporting to whatever
284# function is called with the expanded path as argument).
285# See also module 'glob' for expansion of *, ? and [...] in pathnames.
286# (A function should also be defined to do full *sh-style environment
287# variable expansion.)
288
289def expanduser(path):
290    """Expand ~ and ~user constructs.
291
292    If user or $HOME is unknown, do nothing."""
293    path = os.fspath(path)
294    if isinstance(path, bytes):
295        tilde = b'~'
296    else:
297        tilde = '~'
298    if not path.startswith(tilde):
299        return path
300    i, n = 1, len(path)
301    while i < n and path[i] not in _get_bothseps(path):
302        i += 1
303
304    if 'USERPROFILE' in os.environ:
305        userhome = os.environ['USERPROFILE']
306    elif not 'HOMEPATH' in os.environ:
307        return path
308    else:
309        try:
310            drive = os.environ['HOMEDRIVE']
311        except KeyError:
312            drive = ''
313        userhome = join(drive, os.environ['HOMEPATH'])
314
315    if i != 1: #~user
316        target_user = path[1:i]
317        if isinstance(target_user, bytes):
318            target_user = os.fsdecode(target_user)
319        current_user = os.environ.get('USERNAME')
320
321        if target_user != current_user:
322            # Try to guess user home directory.  By default all user
323            # profile directories are located in the same place and are
324            # named by corresponding usernames.  If userhome isn't a
325            # normal profile directory, this guess is likely wrong,
326            # so we bail out.
327            if current_user != basename(userhome):
328                return path
329            userhome = join(dirname(userhome), target_user)
330
331    if isinstance(path, bytes):
332        userhome = os.fsencode(userhome)
333
334    return userhome + path[i:]
335
336
337# Expand paths containing shell variable substitutions.
338# The following rules apply:
339#       - no expansion within single quotes
340#       - '$$' is translated into '$'
341#       - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
342#       - ${varname} is accepted.
343#       - $varname is accepted.
344#       - %varname% is accepted.
345#       - varnames can be made out of letters, digits and the characters '_-'
346#         (though is not verified in the ${varname} and %varname% cases)
347# XXX With COMMAND.COM you can use any characters in a variable name,
348# XXX except '^|<>='.
349
350def expandvars(path):
351    """Expand shell variables of the forms $var, ${var} and %var%.
352
353    Unknown variables are left unchanged."""
354    path = os.fspath(path)
355    if isinstance(path, bytes):
356        if b'$' not in path and b'%' not in path:
357            return path
358        import string
359        varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
360        quote = b'\''
361        percent = b'%'
362        brace = b'{'
363        rbrace = b'}'
364        dollar = b'$'
365        environ = getattr(os, 'environb', None)
366    else:
367        if '$' not in path and '%' not in path:
368            return path
369        import string
370        varchars = string.ascii_letters + string.digits + '_-'
371        quote = '\''
372        percent = '%'
373        brace = '{'
374        rbrace = '}'
375        dollar = '$'
376        environ = os.environ
377    res = path[:0]
378    index = 0
379    pathlen = len(path)
380    while index < pathlen:
381        c = path[index:index+1]
382        if c == quote:   # no expansion within single quotes
383            path = path[index + 1:]
384            pathlen = len(path)
385            try:
386                index = path.index(c)
387                res += c + path[:index + 1]
388            except ValueError:
389                res += c + path
390                index = pathlen - 1
391        elif c == percent:  # variable or '%'
392            if path[index + 1:index + 2] == percent:
393                res += c
394                index += 1
395            else:
396                path = path[index+1:]
397                pathlen = len(path)
398                try:
399                    index = path.index(percent)
400                except ValueError:
401                    res += percent + path
402                    index = pathlen - 1
403                else:
404                    var = path[:index]
405                    try:
406                        if environ is None:
407                            value = os.fsencode(os.environ[os.fsdecode(var)])
408                        else:
409                            value = environ[var]
410                    except KeyError:
411                        value = percent + var + percent
412                    res += value
413        elif c == dollar:  # variable or '$$'
414            if path[index + 1:index + 2] == dollar:
415                res += c
416                index += 1
417            elif path[index + 1:index + 2] == brace:
418                path = path[index+2:]
419                pathlen = len(path)
420                try:
421                    index = path.index(rbrace)
422                except ValueError:
423                    res += dollar + brace + path
424                    index = pathlen - 1
425                else:
426                    var = path[:index]
427                    try:
428                        if environ is None:
429                            value = os.fsencode(os.environ[os.fsdecode(var)])
430                        else:
431                            value = environ[var]
432                    except KeyError:
433                        value = dollar + brace + var + rbrace
434                    res += value
435            else:
436                var = path[:0]
437                index += 1
438                c = path[index:index + 1]
439                while c and c in varchars:
440                    var += c
441                    index += 1
442                    c = path[index:index + 1]
443                try:
444                    if environ is None:
445                        value = os.fsencode(os.environ[os.fsdecode(var)])
446                    else:
447                        value = environ[var]
448                except KeyError:
449                    value = dollar + var
450                res += value
451                if c:
452                    index -= 1
453        else:
454            res += c
455        index += 1
456    return res
457
458
459# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
460# Previously, this function also truncated pathnames to 8+3 format,
461# but as this module is called "ntpath", that's obviously wrong!
462
463def normpath(path):
464    """Normalize path, eliminating double slashes, etc."""
465    path = os.fspath(path)
466    if isinstance(path, bytes):
467        sep = b'\\'
468        altsep = b'/'
469        curdir = b'.'
470        pardir = b'..'
471        special_prefixes = (b'\\\\.\\', b'\\\\?\\')
472    else:
473        sep = '\\'
474        altsep = '/'
475        curdir = '.'
476        pardir = '..'
477        special_prefixes = ('\\\\.\\', '\\\\?\\')
478    if path.startswith(special_prefixes):
479        # in the case of paths with these prefixes:
480        # \\.\ -> device names
481        # \\?\ -> literal paths
482        # do not do any normalization, but return the path
483        # unchanged apart from the call to os.fspath()
484        return path
485    path = path.replace(altsep, sep)
486    prefix, path = splitdrive(path)
487
488    # collapse initial backslashes
489    if path.startswith(sep):
490        prefix += sep
491        path = path.lstrip(sep)
492
493    comps = path.split(sep)
494    i = 0
495    while i < len(comps):
496        if not comps[i] or comps[i] == curdir:
497            del comps[i]
498        elif comps[i] == pardir:
499            if i > 0 and comps[i-1] != pardir:
500                del comps[i-1:i+1]
501                i -= 1
502            elif i == 0 and prefix.endswith(sep):
503                del comps[i]
504            else:
505                i += 1
506        else:
507            i += 1
508    # If the path is now empty, substitute '.'
509    if not prefix and not comps:
510        comps.append(curdir)
511    return prefix + sep.join(comps)
512
513def _abspath_fallback(path):
514    """Return the absolute version of a path as a fallback function in case
515    `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
516    more.
517
518    """
519
520    path = os.fspath(path)
521    if not isabs(path):
522        if isinstance(path, bytes):
523            cwd = os.getcwdb()
524        else:
525            cwd = os.getcwd()
526        path = join(cwd, path)
527    return normpath(path)
528
529# Return an absolute path.
530try:
531    from nt import _getfullpathname
532
533except ImportError: # not running on Windows - mock up something sensible
534    abspath = _abspath_fallback
535
536else:  # use native Windows method on Windows
537    def abspath(path):
538        """Return the absolute version of a path."""
539        try:
540            return normpath(_getfullpathname(path))
541        except (OSError, ValueError):
542            return _abspath_fallback(path)
543
544try:
545    from nt import _getfinalpathname, readlink as _nt_readlink
546except ImportError:
547    # realpath is a no-op on systems without _getfinalpathname support.
548    realpath = abspath
549else:
550    def _readlink_deep(path):
551        # These error codes indicate that we should stop reading links and
552        # return the path we currently have.
553        # 1: ERROR_INVALID_FUNCTION
554        # 2: ERROR_FILE_NOT_FOUND
555        # 3: ERROR_DIRECTORY_NOT_FOUND
556        # 5: ERROR_ACCESS_DENIED
557        # 21: ERROR_NOT_READY (implies drive with no media)
558        # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
559        # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
560        # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
561        # 87: ERROR_INVALID_PARAMETER
562        # 4390: ERROR_NOT_A_REPARSE_POINT
563        # 4392: ERROR_INVALID_REPARSE_DATA
564        # 4393: ERROR_REPARSE_TAG_INVALID
565        allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
566
567        seen = set()
568        while normcase(path) not in seen:
569            seen.add(normcase(path))
570            try:
571                old_path = path
572                path = _nt_readlink(path)
573                # Links may be relative, so resolve them against their
574                # own location
575                if not isabs(path):
576                    # If it's something other than a symlink, we don't know
577                    # what it's actually going to be resolved against, so
578                    # just return the old path.
579                    if not islink(old_path):
580                        path = old_path
581                        break
582                    path = normpath(join(dirname(old_path), path))
583            except OSError as ex:
584                if ex.winerror in allowed_winerror:
585                    break
586                raise
587            except ValueError:
588                # Stop on reparse points that are not symlinks
589                break
590        return path
591
592    def _getfinalpathname_nonstrict(path):
593        # These error codes indicate that we should stop resolving the path
594        # and return the value we currently have.
595        # 1: ERROR_INVALID_FUNCTION
596        # 2: ERROR_FILE_NOT_FOUND
597        # 3: ERROR_DIRECTORY_NOT_FOUND
598        # 5: ERROR_ACCESS_DENIED
599        # 21: ERROR_NOT_READY (implies drive with no media)
600        # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
601        # 50: ERROR_NOT_SUPPORTED
602        # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
603        # 87: ERROR_INVALID_PARAMETER
604        # 123: ERROR_INVALID_NAME
605        # 1920: ERROR_CANT_ACCESS_FILE
606        # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
607        allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 123, 1920, 1921
608
609        # Non-strict algorithm is to find as much of the target directory
610        # as we can and join the rest.
611        tail = ''
612        while path:
613            try:
614                path = _getfinalpathname(path)
615                return join(path, tail) if tail else path
616            except OSError as ex:
617                if ex.winerror not in allowed_winerror:
618                    raise
619                try:
620                    # The OS could not resolve this path fully, so we attempt
621                    # to follow the link ourselves. If we succeed, join the tail
622                    # and return.
623                    new_path = _readlink_deep(path)
624                    if new_path != path:
625                        return join(new_path, tail) if tail else new_path
626                except OSError:
627                    # If we fail to readlink(), let's keep traversing
628                    pass
629                path, name = split(path)
630                # TODO (bpo-38186): Request the real file name from the directory
631                # entry using FindFirstFileW. For now, we will return the path
632                # as best we have it
633                if path and not name:
634                    return path + tail
635                tail = join(name, tail) if tail else name
636        return tail
637
638    def realpath(path, *, strict=False):
639        path = normpath(path)
640        if isinstance(path, bytes):
641            prefix = b'\\\\?\\'
642            unc_prefix = b'\\\\?\\UNC\\'
643            new_unc_prefix = b'\\\\'
644            cwd = os.getcwdb()
645            # bpo-38081: Special case for realpath(b'nul')
646            if normcase(path) == normcase(os.fsencode(devnull)):
647                return b'\\\\.\\NUL'
648        else:
649            prefix = '\\\\?\\'
650            unc_prefix = '\\\\?\\UNC\\'
651            new_unc_prefix = '\\\\'
652            cwd = os.getcwd()
653            # bpo-38081: Special case for realpath('nul')
654            if normcase(path) == normcase(devnull):
655                return '\\\\.\\NUL'
656        had_prefix = path.startswith(prefix)
657        if not had_prefix and not isabs(path):
658            path = join(cwd, path)
659        try:
660            path = _getfinalpathname(path)
661            initial_winerror = 0
662        except OSError as ex:
663            if strict:
664                raise
665            initial_winerror = ex.winerror
666            path = _getfinalpathname_nonstrict(path)
667        # The path returned by _getfinalpathname will always start with \\?\ -
668        # strip off that prefix unless it was already provided on the original
669        # path.
670        if not had_prefix and path.startswith(prefix):
671            # For UNC paths, the prefix will actually be \\?\UNC\
672            # Handle that case as well.
673            if path.startswith(unc_prefix):
674                spath = new_unc_prefix + path[len(unc_prefix):]
675            else:
676                spath = path[len(prefix):]
677            # Ensure that the non-prefixed path resolves to the same path
678            try:
679                if _getfinalpathname(spath) == path:
680                    path = spath
681            except OSError as ex:
682                # If the path does not exist and originally did not exist, then
683                # strip the prefix anyway.
684                if ex.winerror == initial_winerror:
685                    path = spath
686        return path
687
688
689# Win9x family and earlier have no Unicode filename support.
690supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
691                              sys.getwindowsversion()[3] >= 2)
692
693def relpath(path, start=None):
694    """Return a relative version of a path"""
695    path = os.fspath(path)
696    if isinstance(path, bytes):
697        sep = b'\\'
698        curdir = b'.'
699        pardir = b'..'
700    else:
701        sep = '\\'
702        curdir = '.'
703        pardir = '..'
704
705    if start is None:
706        start = curdir
707
708    if not path:
709        raise ValueError("no path specified")
710
711    start = os.fspath(start)
712    try:
713        start_abs = abspath(normpath(start))
714        path_abs = abspath(normpath(path))
715        start_drive, start_rest = splitdrive(start_abs)
716        path_drive, path_rest = splitdrive(path_abs)
717        if normcase(start_drive) != normcase(path_drive):
718            raise ValueError("path is on mount %r, start on mount %r" % (
719                path_drive, start_drive))
720
721        start_list = [x for x in start_rest.split(sep) if x]
722        path_list = [x for x in path_rest.split(sep) if x]
723        # Work out how much of the filepath is shared by start and path.
724        i = 0
725        for e1, e2 in zip(start_list, path_list):
726            if normcase(e1) != normcase(e2):
727                break
728            i += 1
729
730        rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
731        if not rel_list:
732            return curdir
733        return join(*rel_list)
734    except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
735        genericpath._check_arg_types('relpath', path, start)
736        raise
737
738
739# Return the longest common sub-path of the sequence of paths given as input.
740# The function is case-insensitive and 'separator-insensitive', i.e. if the
741# only difference between two paths is the use of '\' versus '/' as separator,
742# they are deemed to be equal.
743#
744# However, the returned path will have the standard '\' separator (even if the
745# given paths had the alternative '/' separator) and will have the case of the
746# first path given in the sequence. Additionally, any trailing separator is
747# stripped from the returned path.
748
749def commonpath(paths):
750    """Given a sequence of path names, returns the longest common sub-path."""
751
752    if not paths:
753        raise ValueError('commonpath() arg is an empty sequence')
754
755    paths = tuple(map(os.fspath, paths))
756    if isinstance(paths[0], bytes):
757        sep = b'\\'
758        altsep = b'/'
759        curdir = b'.'
760    else:
761        sep = '\\'
762        altsep = '/'
763        curdir = '.'
764
765    try:
766        drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
767        split_paths = [p.split(sep) for d, p in drivesplits]
768
769        try:
770            isabs, = set(p[:1] == sep for d, p in drivesplits)
771        except ValueError:
772            raise ValueError("Can't mix absolute and relative paths") from None
773
774        # Check that all drive letters or UNC paths match. The check is made only
775        # now otherwise type errors for mixing strings and bytes would not be
776        # caught.
777        if len(set(d for d, p in drivesplits)) != 1:
778            raise ValueError("Paths don't have the same drive")
779
780        drive, path = splitdrive(paths[0].replace(altsep, sep))
781        common = path.split(sep)
782        common = [c for c in common if c and c != curdir]
783
784        split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
785        s1 = min(split_paths)
786        s2 = max(split_paths)
787        for i, c in enumerate(s1):
788            if c != s2[i]:
789                common = common[:i]
790                break
791        else:
792            common = common[:len(s1)]
793
794        prefix = drive + sep if isabs else drive
795        return prefix + sep.join(common)
796    except (TypeError, AttributeError):
797        genericpath._check_arg_types('commonpath', *paths)
798        raise
799
800
801try:
802    # The genericpath.isdir implementation uses os.stat and checks the mode
803    # attribute to tell whether or not the path is a directory.
804    # This is overkill on Windows - just pass the path to GetFileAttributes
805    # and check the attribute from there.
806    from nt import _isdir as isdir
807except ImportError:
808    # Use genericpath.isdir as imported above.
809    pass
810