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