blob: 527c7ae1938fbbef34e0aabc204206eaa8ccbb4f [file] [log] [blame]
Guido van Rossum15e22e11997-12-05 19:03:01 +00001# Module 'ntpath' -- common operations on WinNT/Win95 pathnames
Tim Peters2344fae2001-01-15 00:50:52 +00002"""Common pathname manipulations, WindowsNT/95 version.
Guido van Rossum534972b1999-02-03 17:20:50 +00003
4Instead of importing this module directly, import os and refer to this
5module as os.path.
Guido van Rossum15e22e11997-12-05 19:03:01 +00006"""
Guido van Rossum555915a1994-02-24 11:32:59 +00007
Serhiy Storchaka34601982018-01-07 17:54:31 +02008# 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
Guido van Rossum555915a1994-02-24 11:32:59 +000020import os
Mark Hammond8696ebc2002-10-08 02:44:31 +000021import sys
Christian Heimes05e8be12008-02-23 18:30:17 +000022import stat
Guido van Rossumd8faa362007-04-27 19:54:29 +000023import genericpath
Thomas Wouters89f507f2006-12-13 04:49:30 +000024from genericpath import *
Skip Montanaro4d5d5bf2000-07-13 01:01:03 +000025
Skip Montanaro269b83b2001-02-06 01:07:02 +000026__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
27 "basename","dirname","commonprefix","getsize","getmtime",
Georg Brandlf0de6a12005-08-22 18:02:59 +000028 "getatime","getctime", "islink","exists","lexists","isdir","isfile",
Benjamin Petersond71ca412008-05-08 23:44:58 +000029 "ismount", "expanduser","expandvars","normpath","abspath",
Serhiy Storchaka9ed707e2017-01-13 20:55:05 +020030 "curdir","pardir","sep","pathsep","defpath","altsep",
Brian Curtind40e6f72010-07-08 21:39:08 +000031 "extsep","devnull","realpath","supports_unicode_filenames","relpath",
Serhiy Storchaka38220932015-03-31 15:31:53 +030032 "samefile", "sameopenfile", "samestat", "commonpath"]
Guido van Rossum555915a1994-02-24 11:32:59 +000033
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +000034def _get_bothseps(path):
35 if isinstance(path, bytes):
36 return b'\\/'
37 else:
38 return '\\/'
39
Guido van Rossume2ad88c1997-08-12 14:46:58 +000040# Normalize the case of a pathname and map slashes to backslashes.
41# Other normalizations (such as optimizing '../' away) are not done
Guido van Rossum555915a1994-02-24 11:32:59 +000042# (this is done by normpath).
Guido van Rossume2ad88c1997-08-12 14:46:58 +000043
Guido van Rossum555915a1994-02-24 11:32:59 +000044def normcase(s):
Guido van Rossum16a0bc21998-02-18 13:48:31 +000045 """Normalize case of pathname.
46
Guido van Rossum534972b1999-02-03 17:20:50 +000047 Makes all characters lowercase and all slashes into backslashes."""
Brett Cannon3f9183b2016-08-26 14:44:48 -070048 s = os.fspath(s)
Wolfgang Maier74510e22019-03-28 22:47:18 +010049 if isinstance(s, bytes):
50 return s.replace(b'/', b'\\').lower()
51 else:
52 return s.replace('/', '\\').lower()
Guido van Rossum555915a1994-02-24 11:32:59 +000053
Guido van Rossum77e1db31997-06-02 23:11:57 +000054
Fred Drakeef0b5dd2000-02-17 17:30:40 +000055# Return whether a path is absolute.
Mark Hammond5a607a32009-05-06 08:04:54 +000056# 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
Guido van Rossum534972b1999-02-03 17:20:50 +000059# starts with a slash or backslash.
Guido van Rossum555915a1994-02-24 11:32:59 +000060
61def isabs(s):
Guido van Rossum15e22e11997-12-05 19:03:01 +000062 """Test whether a path is absolute"""
Brett Cannon3f9183b2016-08-26 14:44:48 -070063 s = os.fspath(s)
Steve Dowerabde52c2019-11-15 09:49:21 -080064 # 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
Guido van Rossum15e22e11997-12-05 19:03:01 +000072 s = splitdrive(s)[1]
Serhiy Storchaka8518b792014-07-23 20:43:13 +030073 return len(s) > 0 and s[0] in _get_bothseps(s)
Guido van Rossum555915a1994-02-24 11:32:59 +000074
75
Guido van Rossum77e1db31997-06-02 23:11:57 +000076# Join two (or more) paths.
Serhiy Storchakac369c2c2014-01-27 23:15:14 +020077def join(path, *paths):
Brett Cannon3f9183b2016-08-26 14:44:48 -070078 path = os.fspath(path)
Serhiy Storchaka8518b792014-07-23 20:43:13 +030079 if isinstance(path, bytes):
80 sep = b'\\'
81 seps = b'\\/'
82 colon = b':'
83 else:
84 sep = '\\'
85 seps = '\\/'
86 colon = ':'
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +030087 try:
Serhiy Storchaka5bfc03f2015-05-19 11:00:07 +030088 if not paths:
89 path[:0] + sep #23780: Ensure compatible data type even if p is null.
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +030090 result_drive, result_path = splitdrive(path)
Brett Cannon3f9183b2016-08-26 14:44:48 -070091 for p in map(os.fspath, paths):
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +030092 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
Serhiy Storchakac369c2c2014-01-27 23:15:14 +020097 result_path = p_path
98 continue
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +030099 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
Guido van Rossum555915a1994-02-24 11:32:59 +0000119
120
121# Split a path in a drive specification (a drive letter followed by a
Guido van Rossumf3c695c1999-04-06 19:32:19 +0000122# colon) and the path specification.
Guido van Rossum555915a1994-02-24 11:32:59 +0000123# It is always true that drivespec + pathspec == p
124def splitdrive(p):
Mark Hammond5a607a32009-05-06 08:04:54 +0000125 """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 """
Brett Cannon3f9183b2016-08-26 14:44:48 -0700143 p = os.fspath(p)
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300144 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)
Mark Hammond5a607a32009-05-06 08:04:54 +0000154 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:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300161 return p[:0], p
Mark Hammond5a607a32009-05-06 08:04:54 +0000162 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:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300166 return p[:0], p
Mark Hammond5a607a32009-05-06 08:04:54 +0000167 if index2 == -1:
168 index2 = len(p)
169 return p[:index2], p[index2:]
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300170 if normp[1:2] == colon:
Mark Hammond5a607a32009-05-06 08:04:54 +0000171 return p[:2], p[2:]
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300172 return p[:0], p
Guido van Rossumf3c695c1999-04-06 19:32:19 +0000173
174
Guido van Rossum555915a1994-02-24 11:32:59 +0000175# Split a path in head (everything up to the last '/') and tail (the
Guido van Rossum8f0fa9e1999-03-19 21:05:12 +0000176# rest). After the trailing '/' is stripped, the invariant
Guido van Rossum555915a1994-02-24 11:32:59 +0000177# join(head, tail) == p holds.
178# The resulting head won't end in '/' unless it is the root.
179
180def split(p):
Guido van Rossum534972b1999-02-03 17:20:50 +0000181 """Split a pathname.
182
183 Return tuple (head, tail) where tail is everything after the final slash.
184 Either part may be empty."""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700185 p = os.fspath(p)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000186 seps = _get_bothseps(p)
Guido van Rossum15e22e11997-12-05 19:03:01 +0000187 d, p = splitdrive(p)
Guido van Rossum8f0fa9e1999-03-19 21:05:12 +0000188 # set i to index beyond p's last slash
189 i = len(p)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000190 while i and p[i-1] not in seps:
Georg Brandl599b65d2010-07-23 08:46:35 +0000191 i -= 1
Guido van Rossum8f0fa9e1999-03-19 21:05:12 +0000192 head, tail = p[:i], p[i:] # now tail has no slashes
193 # remove trailing slashes from head, unless it's all slashes
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300194 head = head.rstrip(seps) or head
Guido van Rossum15e22e11997-12-05 19:03:01 +0000195 return d + head, tail
Guido van Rossum555915a1994-02-24 11:32:59 +0000196
197
198# Split a path in root and extension.
Guido van Rossum73e122f1997-01-22 00:17:26 +0000199# The extension is everything starting at the last dot in the last
Guido van Rossum555915a1994-02-24 11:32:59 +0000200# pathname component; the root is everything before that.
201# It is always true that root + ext == p.
202
203def splitext(p):
Brett Cannon3f9183b2016-08-26 14:44:48 -0700204 p = os.fspath(p)
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300205 if isinstance(p, bytes):
206 return genericpath._splitext(p, b'\\', b'/', b'.')
207 else:
208 return genericpath._splitext(p, '\\', '/', '.')
Guido van Rossumd8faa362007-04-27 19:54:29 +0000209splitext.__doc__ = genericpath._splitext.__doc__
Guido van Rossum555915a1994-02-24 11:32:59 +0000210
211
212# Return the tail (basename) part of a path.
213
214def basename(p):
Guido van Rossum15e22e11997-12-05 19:03:01 +0000215 """Returns the final component of a pathname"""
216 return split(p)[1]
Guido van Rossum555915a1994-02-24 11:32:59 +0000217
218
219# Return the head (dirname) part of a path.
220
221def dirname(p):
Guido van Rossum15e22e11997-12-05 19:03:01 +0000222 """Returns the directory component of a pathname"""
223 return split(p)[0]
Guido van Rossum555915a1994-02-24 11:32:59 +0000224
Guido van Rossum555915a1994-02-24 11:32:59 +0000225# Is a path a symbolic link?
Brian Curtind40e6f72010-07-08 21:39:08 +0000226# This will always return false on systems where os.lstat doesn't exist.
Guido van Rossum555915a1994-02-24 11:32:59 +0000227
228def islink(path):
Brian Curtind40e6f72010-07-08 21:39:08 +0000229 """Test whether a path is a symbolic link.
Jesus Ceaf1af7052012-10-05 02:48:46 +0200230 This will always return false for Windows prior to 6.0.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000231 """
Brian Curtind40e6f72010-07-08 21:39:08 +0000232 try:
233 st = os.lstat(path)
Serhiy Storchaka0185f342018-09-18 11:28:51 +0300234 except (OSError, ValueError, AttributeError):
Brian Curtind40e6f72010-07-08 21:39:08 +0000235 return False
236 return stat.S_ISLNK(st.st_mode)
Guido van Rossum555915a1994-02-24 11:32:59 +0000237
Brian Curtind40e6f72010-07-08 21:39:08 +0000238# 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)
Serhiy Storchaka0185f342018-09-18 11:28:51 +0300244 except (OSError, ValueError):
Brian Curtind40e6f72010-07-08 21:39:08 +0000245 return False
246 return True
Johannes Gijsbersae882f72004-08-30 10:19:56 +0000247
Tim Golden6b528062013-08-01 12:44:00 +0100248# 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
Guido van Rossum555915a1994-02-24 11:32:59 +0000262def ismount(path):
Tim Golden6b528062013-08-01 12:44:00 +0100263 """Test whether a path is a mount point (a drive root, the root of a
264 share, or a mounted volume)"""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700265 path = os.fspath(path)
Benjamin Peterson48e24782009-03-29 13:02:52 +0000266 seps = _get_bothseps(path)
Tim Golden6b528062013-08-01 12:44:00 +0100267 path = abspath(path)
Mark Hammond5a607a32009-05-06 08:04:54 +0000268 root, rest = splitdrive(path)
269 if root and root[0] in seps:
270 return (not rest) or (rest in seps)
Tim Golden6b528062013-08-01 12:44:00 +0100271 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
Guido van Rossum555915a1994-02-24 11:32:59 +0000278
279
Guido van Rossum555915a1994-02-24 11:32:59 +0000280# 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):
Guido van Rossum534972b1999-02-03 17:20:50 +0000290 """Expand ~ and ~user constructs.
291
292 If user or $HOME is unknown, do nothing."""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700293 path = os.fspath(path)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000294 if isinstance(path, bytes):
295 tilde = b'~'
296 else:
297 tilde = '~'
298 if not path.startswith(tilde):
Guido van Rossum15e22e11997-12-05 19:03:01 +0000299 return path
300 i, n = 1, len(path)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000301 while i < n and path[i] not in _get_bothseps(path):
Georg Brandl599b65d2010-07-23 08:46:35 +0000302 i += 1
Guido van Rossumd8faa362007-04-27 19:54:29 +0000303
Anthony Sottile25ec4a42019-03-12 08:39:57 -0700304 if 'USERPROFILE' in os.environ:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000305 userhome = os.environ['USERPROFILE']
306 elif not 'HOMEPATH' in os.environ:
Guido van Rossum15e22e11997-12-05 19:03:01 +0000307 return path
Guido van Rossumd8faa362007-04-27 19:54:29 +0000308 else:
309 try:
310 drive = os.environ['HOMEDRIVE']
311 except KeyError:
312 drive = ''
313 userhome = join(drive, os.environ['HOMEPATH'])
314
Barney Gale3f3d82b2021-04-07 23:50:13 +0100315 if i != 1: #~user
Barney Gale3f3d82b2021-04-07 23:50:13 +0100316 target_user = path[1:i]
317 if isinstance(target_user, bytes):
318 target_user = os.fsdecode(target_user)
Barney Galeba1db572021-04-09 22:28:15 +0100319 current_user = os.environ.get('USERNAME')
320
Barney Gale3f3d82b2021-04-07 23:50:13 +0100321 if target_user != current_user:
Barney Galeba1db572021-04-09 22:28:15 +0100322 # Try to guess user home directory. By default all user
323 # profile directories are located in the same place and are
324 # named by corresponding usernames. If userhome isn't a
325 # normal profile directory, this guess is likely wrong,
326 # so we bail out.
327 if current_user != basename(userhome):
328 return path
Barney Gale3f3d82b2021-04-07 23:50:13 +0100329 userhome = join(dirname(userhome), target_user)
330
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000331 if isinstance(path, bytes):
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300332 userhome = os.fsencode(userhome)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000333
Guido van Rossum15e22e11997-12-05 19:03:01 +0000334 return userhome + path[i:]
Guido van Rossum555915a1994-02-24 11:32:59 +0000335
336
337# Expand paths containing shell variable substitutions.
338# The following rules apply:
Guido van Rossum15e22e11997-12-05 19:03:01 +0000339# - no expansion within single quotes
Guido van Rossumd8faa362007-04-27 19:54:29 +0000340# - '$$' is translated into '$'
341# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
Guido van Rossum15e22e11997-12-05 19:03:01 +0000342# - ${varname} is accepted.
Guido van Rossumd8faa362007-04-27 19:54:29 +0000343# - $varname is accepted.
344# - %varname% is accepted.
345# - varnames can be made out of letters, digits and the characters '_-'
Ezio Melotti13925002011-03-16 11:05:33 +0200346# (though is not verified in the ${varname} and %varname% cases)
Guido van Rossum555915a1994-02-24 11:32:59 +0000347# XXX With COMMAND.COM you can use any characters in a variable name,
348# XXX except '^|<>='.
349
Tim Peters2344fae2001-01-15 00:50:52 +0000350def expandvars(path):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000351 """Expand shell variables of the forms $var, ${var} and %var%.
Guido van Rossum534972b1999-02-03 17:20:50 +0000352
353 Unknown variables are left unchanged."""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700354 path = os.fspath(path)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000355 if isinstance(path, bytes):
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300356 if b'$' not in path and b'%' not in path:
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000357 return path
358 import string
359 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000360 quote = b'\''
361 percent = b'%'
362 brace = b'{'
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300363 rbrace = b'}'
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000364 dollar = b'$'
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200365 environ = getattr(os, 'environb', None)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000366 else:
367 if '$' not in path and '%' not in path:
368 return path
369 import string
370 varchars = string.ascii_letters + string.digits + '_-'
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000371 quote = '\''
372 percent = '%'
373 brace = '{'
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300374 rbrace = '}'
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000375 dollar = '$'
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200376 environ = os.environ
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000377 res = path[:0]
Guido van Rossum15e22e11997-12-05 19:03:01 +0000378 index = 0
379 pathlen = len(path)
380 while index < pathlen:
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000381 c = path[index:index+1]
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000382 if c == quote: # no expansion within single quotes
Guido van Rossum15e22e11997-12-05 19:03:01 +0000383 path = path[index + 1:]
384 pathlen = len(path)
385 try:
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000386 index = path.index(c)
Georg Brandl599b65d2010-07-23 08:46:35 +0000387 res += c + path[:index + 1]
Fred Drakeb4e460a2000-09-28 16:25:20 +0000388 except ValueError:
Serhiy Storchaka1b87ae02015-03-25 16:40:15 +0200389 res += c + path
Fred Drakeb4e460a2000-09-28 16:25:20 +0000390 index = pathlen - 1
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000391 elif c == percent: # variable or '%'
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000392 if path[index + 1:index + 2] == percent:
Georg Brandl599b65d2010-07-23 08:46:35 +0000393 res += c
394 index += 1
Guido van Rossumd8faa362007-04-27 19:54:29 +0000395 else:
396 path = path[index+1:]
397 pathlen = len(path)
398 try:
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000399 index = path.index(percent)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000400 except ValueError:
Georg Brandl599b65d2010-07-23 08:46:35 +0000401 res += percent + path
Guido van Rossumd8faa362007-04-27 19:54:29 +0000402 index = pathlen - 1
403 else:
404 var = path[:index]
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200405 try:
406 if environ is None:
407 value = os.fsencode(os.environ[os.fsdecode(var)])
408 else:
409 value = environ[var]
410 except KeyError:
411 value = percent + var + percent
Georg Brandl599b65d2010-07-23 08:46:35 +0000412 res += value
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000413 elif c == dollar: # variable or '$$'
414 if path[index + 1:index + 2] == dollar:
Georg Brandl599b65d2010-07-23 08:46:35 +0000415 res += c
416 index += 1
Amaury Forgeot d'Arc3b44e612008-10-03 20:32:33 +0000417 elif path[index + 1:index + 2] == brace:
Guido van Rossum15e22e11997-12-05 19:03:01 +0000418 path = path[index+2:]
419 pathlen = len(path)
420 try:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300421 index = path.index(rbrace)
Fred Drakeb4e460a2000-09-28 16:25:20 +0000422 except ValueError:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300423 res += dollar + brace + path
Guido van Rossum15e22e11997-12-05 19:03:01 +0000424 index = pathlen - 1
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200425 else:
426 var = path[:index]
427 try:
428 if environ is None:
429 value = os.fsencode(os.environ[os.fsdecode(var)])
430 else:
431 value = environ[var]
432 except KeyError:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300433 value = dollar + brace + var + rbrace
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200434 res += value
Guido van Rossum15e22e11997-12-05 19:03:01 +0000435 else:
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200436 var = path[:0]
Georg Brandl599b65d2010-07-23 08:46:35 +0000437 index += 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000438 c = path[index:index + 1]
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000439 while c and c in varchars:
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200440 var += c
Georg Brandl599b65d2010-07-23 08:46:35 +0000441 index += 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000442 c = path[index:index + 1]
Serhiy Storchakadbb10192014-02-13 10:13:53 +0200443 try:
444 if environ is None:
445 value = os.fsencode(os.environ[os.fsdecode(var)])
446 else:
447 value = environ[var]
448 except KeyError:
449 value = dollar + var
Georg Brandl599b65d2010-07-23 08:46:35 +0000450 res += value
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000451 if c:
Georg Brandl599b65d2010-07-23 08:46:35 +0000452 index -= 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000453 else:
Georg Brandl599b65d2010-07-23 08:46:35 +0000454 res += c
455 index += 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000456 return res
Guido van Rossum555915a1994-02-24 11:32:59 +0000457
458
Tim Peters54a14a32001-08-30 22:05:26 +0000459# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
Guido van Rossum3df7b5a1996-08-26 16:35:26 +0000460# Previously, this function also truncated pathnames to 8+3 format,
461# but as this module is called "ntpath", that's obviously wrong!
Guido van Rossum555915a1994-02-24 11:32:59 +0000462
463def normpath(path):
Guido van Rossum15e22e11997-12-05 19:03:01 +0000464 """Normalize path, eliminating double slashes, etc."""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700465 path = os.fspath(path)
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300466 if isinstance(path, bytes):
467 sep = b'\\'
468 altsep = b'/'
469 curdir = b'.'
470 pardir = b'..'
471 special_prefixes = (b'\\\\.\\', b'\\\\?\\')
472 else:
473 sep = '\\'
474 altsep = '/'
475 curdir = '.'
476 pardir = '..'
477 special_prefixes = ('\\\\.\\', '\\\\?\\')
Georg Brandlcfb68212010-07-31 21:40:15 +0000478 if path.startswith(special_prefixes):
479 # in the case of paths with these prefixes:
480 # \\.\ -> device names
481 # \\?\ -> literal paths
Steve Dower06be2c72019-08-21 16:45:02 -0700482 # do not do any normalization, but return the path
483 # unchanged apart from the call to os.fspath()
Georg Brandlcfb68212010-07-31 21:40:15 +0000484 return path
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300485 path = path.replace(altsep, sep)
Guido van Rossum15e22e11997-12-05 19:03:01 +0000486 prefix, path = splitdrive(path)
Mark Hammond5a607a32009-05-06 08:04:54 +0000487
488 # collapse initial backslashes
489 if path.startswith(sep):
Georg Brandl599b65d2010-07-23 08:46:35 +0000490 prefix += sep
Mark Hammond5a607a32009-05-06 08:04:54 +0000491 path = path.lstrip(sep)
492
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000493 comps = path.split(sep)
Guido van Rossum15e22e11997-12-05 19:03:01 +0000494 i = 0
495 while i < len(comps):
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300496 if not comps[i] or comps[i] == curdir:
Guido van Rossum15e22e11997-12-05 19:03:01 +0000497 del comps[i]
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300498 elif comps[i] == pardir:
499 if i > 0 and comps[i-1] != pardir:
Tim Peters54a14a32001-08-30 22:05:26 +0000500 del comps[i-1:i+1]
501 i -= 1
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300502 elif i == 0 and prefix.endswith(sep):
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000503 del comps[i]
504 else:
505 i += 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000506 else:
Tim Peters54a14a32001-08-30 22:05:26 +0000507 i += 1
Guido van Rossum15e22e11997-12-05 19:03:01 +0000508 # If the path is now empty, substitute '.'
509 if not prefix and not comps:
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300510 comps.append(curdir)
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000511 return prefix + sep.join(comps)
Guido van Rossume294cf61999-01-29 18:05:18 +0000512
Franz Wöllertd2e902e2018-07-29 14:47:09 +0200513def _abspath_fallback(path):
514 """Return the absolute version of a path as a fallback function in case
515 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
516 more.
517
518 """
519
520 path = os.fspath(path)
521 if not isabs(path):
522 if isinstance(path, bytes):
523 cwd = os.getcwdb()
524 else:
525 cwd = os.getcwd()
526 path = join(cwd, path)
527 return normpath(path)
Guido van Rossume294cf61999-01-29 18:05:18 +0000528
529# Return an absolute path.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000530try:
531 from nt import _getfullpathname
Mark Hammondf717f052002-01-17 00:44:26 +0000532
Brett Cannoncd171c82013-07-04 17:43:24 -0400533except ImportError: # not running on Windows - mock up something sensible
Franz Wöllertd2e902e2018-07-29 14:47:09 +0200534 abspath = _abspath_fallback
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535
536else: # use native Windows method on Windows
537 def abspath(path):
538 """Return the absolute version of a path."""
Franz Wöllertd2e902e2018-07-29 14:47:09 +0200539 try:
Tim Grahamd03b7752018-10-25 11:26:38 -0400540 return normpath(_getfullpathname(path))
Serhiy Storchaka0185f342018-09-18 11:28:51 +0300541 except (OSError, ValueError):
Franz Wöllertd2e902e2018-07-29 14:47:09 +0200542 return _abspath_fallback(path)
Guido van Rossum83eeef42001-09-17 15:16:09 +0000543
Steve Dower75e06492019-08-21 13:43:06 -0700544try:
545 from nt import _getfinalpathname, readlink as _nt_readlink
546except ImportError:
547 # realpath is a no-op on systems without _getfinalpathname support.
548 realpath = abspath
549else:
Steve Dowerabde52c2019-11-15 09:49:21 -0800550 def _readlink_deep(path):
Steve Dower89b89332019-09-16 15:25:11 +0100551 # These error codes indicate that we should stop reading links and
552 # return the path we currently have.
553 # 1: ERROR_INVALID_FUNCTION
554 # 2: ERROR_FILE_NOT_FOUND
555 # 3: ERROR_DIRECTORY_NOT_FOUND
556 # 5: ERROR_ACCESS_DENIED
557 # 21: ERROR_NOT_READY (implies drive with no media)
558 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
559 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
560 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
561 # 87: ERROR_INVALID_PARAMETER
562 # 4390: ERROR_NOT_A_REPARSE_POINT
563 # 4392: ERROR_INVALID_REPARSE_DATA
564 # 4393: ERROR_REPARSE_TAG_INVALID
565 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
566
Steve Dowerabde52c2019-11-15 09:49:21 -0800567 seen = set()
Steve Dower75e06492019-08-21 13:43:06 -0700568 while normcase(path) not in seen:
569 seen.add(normcase(path))
570 try:
Steve Dowerabde52c2019-11-15 09:49:21 -0800571 old_path = path
Steve Dower75e06492019-08-21 13:43:06 -0700572 path = _nt_readlink(path)
Steve Dowerabde52c2019-11-15 09:49:21 -0800573 # Links may be relative, so resolve them against their
574 # own location
575 if not isabs(path):
576 # If it's something other than a symlink, we don't know
577 # what it's actually going to be resolved against, so
578 # just return the old path.
579 if not islink(old_path):
580 path = old_path
581 break
582 path = normpath(join(dirname(old_path), path))
Steve Dower75e06492019-08-21 13:43:06 -0700583 except OSError as ex:
Steve Dower89b89332019-09-16 15:25:11 +0100584 if ex.winerror in allowed_winerror:
Steve Dower75e06492019-08-21 13:43:06 -0700585 break
586 raise
587 except ValueError:
588 # Stop on reparse points that are not symlinks
589 break
590 return path
591
592 def _getfinalpathname_nonstrict(path):
Steve Dower89b89332019-09-16 15:25:11 +0100593 # These error codes indicate that we should stop resolving the path
594 # and return the value we currently have.
595 # 1: ERROR_INVALID_FUNCTION
596 # 2: ERROR_FILE_NOT_FOUND
597 # 3: ERROR_DIRECTORY_NOT_FOUND
598 # 5: ERROR_ACCESS_DENIED
599 # 21: ERROR_NOT_READY (implies drive with no media)
600 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
601 # 50: ERROR_NOT_SUPPORTED
602 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
603 # 87: ERROR_INVALID_PARAMETER
604 # 123: ERROR_INVALID_NAME
Steve Dowera0e3d272019-10-03 08:31:03 -0700605 # 1920: ERROR_CANT_ACCESS_FILE
Steve Dower89b89332019-09-16 15:25:11 +0100606 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
Steve Dowera0e3d272019-10-03 08:31:03 -0700607 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 123, 1920, 1921
Steve Dower75e06492019-08-21 13:43:06 -0700608
609 # Non-strict algorithm is to find as much of the target directory
610 # as we can and join the rest.
611 tail = ''
Steve Dower75e06492019-08-21 13:43:06 -0700612 while path:
613 try:
Steve Dower75e06492019-08-21 13:43:06 -0700614 path = _getfinalpathname(path)
615 return join(path, tail) if tail else path
616 except OSError as ex:
617 if ex.winerror not in allowed_winerror:
618 raise
Steve Dowerabde52c2019-11-15 09:49:21 -0800619 try:
620 # The OS could not resolve this path fully, so we attempt
621 # to follow the link ourselves. If we succeed, join the tail
622 # and return.
623 new_path = _readlink_deep(path)
624 if new_path != path:
625 return join(new_path, tail) if tail else new_path
626 except OSError:
627 # If we fail to readlink(), let's keep traversing
628 pass
Steve Dower75e06492019-08-21 13:43:06 -0700629 path, name = split(path)
Steve Dower89b89332019-09-16 15:25:11 +0100630 # TODO (bpo-38186): Request the real file name from the directory
631 # entry using FindFirstFileW. For now, we will return the path
632 # as best we have it
Steve Dower75e06492019-08-21 13:43:06 -0700633 if path and not name:
Steve Dowerabde52c2019-11-15 09:49:21 -0800634 return path + tail
Steve Dower75e06492019-08-21 13:43:06 -0700635 tail = join(name, tail) if tail else name
Steve Dowerabde52c2019-11-15 09:49:21 -0800636 return tail
Steve Dower75e06492019-08-21 13:43:06 -0700637
Barney Galebaecfbd2021-04-28 16:50:17 +0100638 def realpath(path, *, strict=False):
Steve Dower06be2c72019-08-21 16:45:02 -0700639 path = normpath(path)
Steve Dower75e06492019-08-21 13:43:06 -0700640 if isinstance(path, bytes):
641 prefix = b'\\\\?\\'
642 unc_prefix = b'\\\\?\\UNC\\'
643 new_unc_prefix = b'\\\\'
644 cwd = os.getcwdb()
Steve Dowerabde52c2019-11-15 09:49:21 -0800645 # bpo-38081: Special case for realpath(b'nul')
646 if normcase(path) == normcase(os.fsencode(devnull)):
647 return b'\\\\.\\NUL'
Steve Dower75e06492019-08-21 13:43:06 -0700648 else:
649 prefix = '\\\\?\\'
650 unc_prefix = '\\\\?\\UNC\\'
651 new_unc_prefix = '\\\\'
652 cwd = os.getcwd()
Steve Dowerabde52c2019-11-15 09:49:21 -0800653 # bpo-38081: Special case for realpath('nul')
654 if normcase(path) == normcase(devnull):
655 return '\\\\.\\NUL'
Steve Dower75e06492019-08-21 13:43:06 -0700656 had_prefix = path.startswith(prefix)
Steve Dowerabde52c2019-11-15 09:49:21 -0800657 if not had_prefix and not isabs(path):
658 path = join(cwd, path)
Steve Dowera0e3d272019-10-03 08:31:03 -0700659 try:
660 path = _getfinalpathname(path)
661 initial_winerror = 0
662 except OSError as ex:
Barney Galebaecfbd2021-04-28 16:50:17 +0100663 if strict:
664 raise
Steve Dowera0e3d272019-10-03 08:31:03 -0700665 initial_winerror = ex.winerror
666 path = _getfinalpathname_nonstrict(path)
Steve Dower75e06492019-08-21 13:43:06 -0700667 # The path returned by _getfinalpathname will always start with \\?\ -
668 # strip off that prefix unless it was already provided on the original
669 # path.
670 if not had_prefix and path.startswith(prefix):
671 # For UNC paths, the prefix will actually be \\?\UNC\
672 # Handle that case as well.
673 if path.startswith(unc_prefix):
674 spath = new_unc_prefix + path[len(unc_prefix):]
675 else:
676 spath = path[len(prefix):]
677 # Ensure that the non-prefixed path resolves to the same path
678 try:
679 if _getfinalpathname(spath) == path:
680 path = spath
681 except OSError as ex:
Steve Dower06be2c72019-08-21 16:45:02 -0700682 # If the path does not exist and originally did not exist, then
683 # strip the prefix anyway.
Steve Dowera0e3d272019-10-03 08:31:03 -0700684 if ex.winerror == initial_winerror:
Steve Dower06be2c72019-08-21 16:45:02 -0700685 path = spath
Steve Dower75e06492019-08-21 13:43:06 -0700686 return path
687
688
Mark Hammond8696ebc2002-10-08 02:44:31 +0000689# Win9x family and earlier have no Unicode filename support.
Tim Peters26bc25a2002-10-09 07:56:04 +0000690supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
691 sys.getwindowsversion()[3] >= 2)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000692
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300693def relpath(path, start=None):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000694 """Return a relative version of a path"""
Brett Cannon3f9183b2016-08-26 14:44:48 -0700695 path = os.fspath(path)
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300696 if isinstance(path, bytes):
697 sep = b'\\'
698 curdir = b'.'
699 pardir = b'..'
700 else:
701 sep = '\\'
702 curdir = '.'
703 pardir = '..'
Amaury Forgeot d'Arcc72ef8b2008-10-03 18:38:26 +0000704
Serhiy Storchaka8518b792014-07-23 20:43:13 +0300705 if start is None:
706 start = curdir
Guido van Rossumd8faa362007-04-27 19:54:29 +0000707
708 if not path:
709 raise ValueError("no path specified")
Mark Hammond5a607a32009-05-06 08:04:54 +0000710
Brett Cannon3f9183b2016-08-26 14:44:48 -0700711 start = os.fspath(start)
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +0300712 try:
713 start_abs = abspath(normpath(start))
714 path_abs = abspath(normpath(path))
715 start_drive, start_rest = splitdrive(start_abs)
716 path_drive, path_rest = splitdrive(path_abs)
717 if normcase(start_drive) != normcase(path_drive):
718 raise ValueError("path is on mount %r, start on mount %r" % (
719 path_drive, start_drive))
Mark Hammond5a607a32009-05-06 08:04:54 +0000720
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +0300721 start_list = [x for x in start_rest.split(sep) if x]
722 path_list = [x for x in path_rest.split(sep) if x]
723 # Work out how much of the filepath is shared by start and path.
724 i = 0
725 for e1, e2 in zip(start_list, path_list):
726 if normcase(e1) != normcase(e2):
727 break
728 i += 1
Guido van Rossumd8faa362007-04-27 19:54:29 +0000729
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +0300730 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
731 if not rel_list:
732 return curdir
733 return join(*rel_list)
Serhiy Storchakae4f47082014-10-04 16:09:02 +0300734 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
Serhiy Storchaka3deeeb02014-10-04 14:58:43 +0300735 genericpath._check_arg_types('relpath', path, start)
736 raise
Brian Curtind40e6f72010-07-08 21:39:08 +0000737
738
Serhiy Storchaka38220932015-03-31 15:31:53 +0300739# Return the longest common sub-path of the sequence of paths given as input.
740# The function is case-insensitive and 'separator-insensitive', i.e. if the
741# only difference between two paths is the use of '\' versus '/' as separator,
742# they are deemed to be equal.
743#
744# However, the returned path will have the standard '\' separator (even if the
745# given paths had the alternative '/' separator) and will have the case of the
746# first path given in the sequence. Additionally, any trailing separator is
747# stripped from the returned path.
748
749def commonpath(paths):
750 """Given a sequence of path names, returns the longest common sub-path."""
751
752 if not paths:
753 raise ValueError('commonpath() arg is an empty sequence')
754
Brett Cannon3f9183b2016-08-26 14:44:48 -0700755 paths = tuple(map(os.fspath, paths))
Serhiy Storchaka38220932015-03-31 15:31:53 +0300756 if isinstance(paths[0], bytes):
757 sep = b'\\'
758 altsep = b'/'
759 curdir = b'.'
760 else:
761 sep = '\\'
762 altsep = '/'
763 curdir = '.'
764
765 try:
766 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
767 split_paths = [p.split(sep) for d, p in drivesplits]
768
769 try:
770 isabs, = set(p[:1] == sep for d, p in drivesplits)
771 except ValueError:
772 raise ValueError("Can't mix absolute and relative paths") from None
773
774 # Check that all drive letters or UNC paths match. The check is made only
775 # now otherwise type errors for mixing strings and bytes would not be
776 # caught.
777 if len(set(d for d, p in drivesplits)) != 1:
778 raise ValueError("Paths don't have the same drive")
779
780 drive, path = splitdrive(paths[0].replace(altsep, sep))
781 common = path.split(sep)
782 common = [c for c in common if c and c != curdir]
783
784 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
785 s1 = min(split_paths)
786 s2 = max(split_paths)
787 for i, c in enumerate(s1):
788 if c != s2[i]:
789 common = common[:i]
790 break
791 else:
792 common = common[:len(s1)]
793
794 prefix = drive + sep if isabs else drive
795 return prefix + sep.join(common)
796 except (TypeError, AttributeError):
797 genericpath._check_arg_types('commonpath', *paths)
798 raise
799
800
Brian Curtin9c669cc2011-06-08 18:17:18 -0500801try:
802 # The genericpath.isdir implementation uses os.stat and checks the mode
803 # attribute to tell whether or not the path is a directory.
804 # This is overkill on Windows - just pass the path to GetFileAttributes
805 # and check the attribute from there.
Brian Curtin95d028f2011-06-09 09:10:38 -0500806 from nt import _isdir as isdir
Brett Cannoncd171c82013-07-04 17:43:24 -0400807except ImportError:
Brian Curtin95d028f2011-06-09 09:10:38 -0500808 # Use genericpath.isdir as imported above.
809 pass