ntpath.py 31 KB

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