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