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