ntpath.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  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","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"]
  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. def normcase(s):
  38. """Normalize case of pathname.
  39. Makes all characters lowercase and all slashes into backslashes."""
  40. s = os.fspath(s)
  41. try:
  42. if isinstance(s, bytes):
  43. return s.replace(b'/', b'\\').lower()
  44. else:
  45. return s.replace('/', '\\').lower()
  46. except (TypeError, AttributeError):
  47. if not isinstance(s, (bytes, str)):
  48. raise TypeError("normcase() argument must be str or bytes, "
  49. "not %r" % s.__class__.__name__) from None
  50. raise
  51. # Return whether a path is absolute.
  52. # Trivial in Posix, harder on Windows.
  53. # For Windows it is absolute if it starts with a slash or backslash (current
  54. # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
  55. # starts with a slash or backslash.
  56. def isabs(s):
  57. """Test whether a path is absolute"""
  58. s = os.fspath(s)
  59. s = splitdrive(s)[1]
  60. return len(s) > 0 and s[0] in _get_bothseps(s)
  61. # Join two (or more) paths.
  62. def join(path, *paths):
  63. path = os.fspath(path)
  64. if isinstance(path, bytes):
  65. sep = b'\\'
  66. seps = b'\\/'
  67. colon = b':'
  68. else:
  69. sep = '\\'
  70. seps = '\\/'
  71. colon = ':'
  72. try:
  73. if not paths:
  74. path[:0] + sep #23780: Ensure compatible data type even if p is null.
  75. result_drive, result_path = splitdrive(path)
  76. for p in map(os.fspath, paths):
  77. p_drive, p_path = splitdrive(p)
  78. if p_path and p_path[0] in seps:
  79. # Second path is absolute
  80. if p_drive or not result_drive:
  81. result_drive = p_drive
  82. result_path = p_path
  83. continue
  84. elif p_drive and p_drive != result_drive:
  85. if p_drive.lower() != result_drive.lower():
  86. # Different drives => ignore the first path entirely
  87. result_drive = p_drive
  88. result_path = p_path
  89. continue
  90. # Same drive in different case
  91. result_drive = p_drive
  92. # Second path is relative to the first
  93. if result_path and result_path[-1] not in seps:
  94. result_path = result_path + sep
  95. result_path = result_path + p_path
  96. ## add separator between UNC and non-absolute path
  97. if (result_path and result_path[0] not in seps and
  98. result_drive and result_drive[-1:] != colon):
  99. return result_drive + sep + result_path
  100. return result_drive + result_path
  101. except (TypeError, AttributeError, BytesWarning):
  102. genericpath._check_arg_types('join', path, *paths)
  103. raise
  104. # Split a path in a drive specification (a drive letter followed by a
  105. # colon) and the path specification.
  106. # It is always true that drivespec + pathspec == p
  107. def splitdrive(p):
  108. """Split a pathname into drive/UNC sharepoint and relative path specifiers.
  109. Returns a 2-tuple (drive_or_unc, path); either part may be empty.
  110. If you assign
  111. result = splitdrive(p)
  112. It is always true that:
  113. result[0] + result[1] == p
  114. If the path contained a drive letter, drive_or_unc will contain everything
  115. up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
  116. If the path contained a UNC path, the drive_or_unc will contain the host name
  117. and share up to but not including the fourth directory separator character.
  118. e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
  119. Paths cannot contain both a drive letter and a UNC path.
  120. """
  121. p = os.fspath(p)
  122. if len(p) >= 2:
  123. if isinstance(p, bytes):
  124. sep = b'\\'
  125. altsep = b'/'
  126. colon = b':'
  127. else:
  128. sep = '\\'
  129. altsep = '/'
  130. colon = ':'
  131. normp = p.replace(altsep, sep)
  132. if (normp[0:2] == sep*2) and (normp[2:3] != sep):
  133. # is a UNC path:
  134. # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
  135. # \\machine\mountpoint\directory\etc\...
  136. # directory ^^^^^^^^^^^^^^^
  137. index = normp.find(sep, 2)
  138. if index == -1:
  139. return p[:0], p
  140. index2 = normp.find(sep, index + 1)
  141. # a UNC path can't have two slashes in a row
  142. # (after the initial two)
  143. if index2 == index + 1:
  144. return p[:0], p
  145. if index2 == -1:
  146. index2 = len(p)
  147. return p[:index2], p[index2:]
  148. if normp[1:2] == colon:
  149. return p[:2], p[2:]
  150. return p[:0], p
  151. # Split a path in head (everything up to the last '/') and tail (the
  152. # rest). After the trailing '/' is stripped, the invariant
  153. # join(head, tail) == p holds.
  154. # The resulting head won't end in '/' unless it is the root.
  155. def split(p):
  156. """Split a pathname.
  157. Return tuple (head, tail) where tail is everything after the final slash.
  158. Either part may be empty."""
  159. p = os.fspath(p)
  160. seps = _get_bothseps(p)
  161. d, p = splitdrive(p)
  162. # set i to index beyond p's last slash
  163. i = len(p)
  164. while i and p[i-1] not in seps:
  165. i -= 1
  166. head, tail = p[:i], p[i:] # now tail has no slashes
  167. # remove trailing slashes from head, unless it's all slashes
  168. head = head.rstrip(seps) or head
  169. return d + head, tail
  170. # Split a path in root and extension.
  171. # The extension is everything starting at the last dot in the last
  172. # pathname component; the root is everything before that.
  173. # It is always true that root + ext == p.
  174. def splitext(p):
  175. p = os.fspath(p)
  176. if isinstance(p, bytes):
  177. return genericpath._splitext(p, b'\\', b'/', b'.')
  178. else:
  179. return genericpath._splitext(p, '\\', '/', '.')
  180. splitext.__doc__ = genericpath._splitext.__doc__
  181. # Return the tail (basename) part of a path.
  182. def basename(p):
  183. """Returns the final component of a pathname"""
  184. return split(p)[1]
  185. # Return the head (dirname) part of a path.
  186. def dirname(p):
  187. """Returns the directory component of a pathname"""
  188. return split(p)[0]
  189. # Is a path a symbolic link?
  190. # This will always return false on systems where os.lstat doesn't exist.
  191. def islink(path):
  192. """Test whether a path is a symbolic link.
  193. This will always return false for Windows prior to 6.0.
  194. """
  195. try:
  196. st = os.lstat(path)
  197. except (OSError, AttributeError):
  198. return False
  199. return stat.S_ISLNK(st.st_mode)
  200. # Being true for dangling symbolic links is also useful.
  201. def lexists(path):
  202. """Test whether a path exists. Returns True for broken symbolic links"""
  203. try:
  204. st = os.lstat(path)
  205. except OSError:
  206. return False
  207. return True
  208. # Is a path a mount point?
  209. # Any drive letter root (eg c:\)
  210. # Any share UNC (eg \\server\share)
  211. # Any volume mounted on a filesystem folder
  212. #
  213. # No one method detects all three situations. Historically we've lexically
  214. # detected drive letter roots and share UNCs. The canonical approach to
  215. # detecting mounted volumes (querying the reparse tag) fails for the most
  216. # common case: drive letter roots. The alternative which uses GetVolumePathName
  217. # fails if the drive letter is the result of a SUBST.
  218. try:
  219. from nt import _getvolumepathname
  220. except ImportError:
  221. _getvolumepathname = None
  222. def ismount(path):
  223. """Test whether a path is a mount point (a drive root, the root of a
  224. share, or a mounted volume)"""
  225. path = os.fspath(path)
  226. seps = _get_bothseps(path)
  227. path = abspath(path)
  228. root, rest = splitdrive(path)
  229. if root and root[0] in seps:
  230. return (not rest) or (rest in seps)
  231. if rest in seps:
  232. return True
  233. if _getvolumepathname:
  234. return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
  235. else:
  236. return False
  237. # Expand paths beginning with '~' or '~user'.
  238. # '~' means $HOME; '~user' means that user's home directory.
  239. # If the path doesn't begin with '~', or if the user or $HOME is unknown,
  240. # the path is returned unchanged (leaving error reporting to whatever
  241. # function is called with the expanded path as argument).
  242. # See also module 'glob' for expansion of *, ? and [...] in pathnames.
  243. # (A function should also be defined to do full *sh-style environment
  244. # variable expansion.)
  245. def expanduser(path):
  246. """Expand ~ and ~user constructs.
  247. If user or $HOME is unknown, do nothing."""
  248. path = os.fspath(path)
  249. if isinstance(path, bytes):
  250. tilde = b'~'
  251. else:
  252. tilde = '~'
  253. if not path.startswith(tilde):
  254. return path
  255. i, n = 1, len(path)
  256. while i < n and path[i] not in _get_bothseps(path):
  257. i += 1
  258. if 'HOME' in os.environ:
  259. userhome = os.environ['HOME']
  260. elif 'USERPROFILE' in os.environ:
  261. userhome = os.environ['USERPROFILE']
  262. elif not 'HOMEPATH' in os.environ:
  263. return path
  264. else:
  265. try:
  266. drive = os.environ['HOMEDRIVE']
  267. except KeyError:
  268. drive = ''
  269. userhome = join(drive, os.environ['HOMEPATH'])
  270. if isinstance(path, bytes):
  271. userhome = os.fsencode(userhome)
  272. if i != 1: #~user
  273. userhome = join(dirname(userhome), path[1:i])
  274. return userhome + path[i:]
  275. # Expand paths containing shell variable substitutions.
  276. # The following rules apply:
  277. # - no expansion within single quotes
  278. # - '$$' is translated into '$'
  279. # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
  280. # - ${varname} is accepted.
  281. # - $varname is accepted.
  282. # - %varname% is accepted.
  283. # - varnames can be made out of letters, digits and the characters '_-'
  284. # (though is not verified in the ${varname} and %varname% cases)
  285. # XXX With COMMAND.COM you can use any characters in a variable name,
  286. # XXX except '^|<>='.
  287. def expandvars(path):
  288. """Expand shell variables of the forms $var, ${var} and %var%.
  289. Unknown variables are left unchanged."""
  290. path = os.fspath(path)
  291. if isinstance(path, bytes):
  292. if b'$' not in path and b'%' not in path:
  293. return path
  294. import string
  295. varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
  296. quote = b'\''
  297. percent = b'%'
  298. brace = b'{'
  299. rbrace = b'}'
  300. dollar = b'$'
  301. environ = getattr(os, 'environb', None)
  302. else:
  303. if '$' not in path and '%' not in path:
  304. return path
  305. import string
  306. varchars = string.ascii_letters + string.digits + '_-'
  307. quote = '\''
  308. percent = '%'
  309. brace = '{'
  310. rbrace = '}'
  311. dollar = '$'
  312. environ = os.environ
  313. res = path[:0]
  314. index = 0
  315. pathlen = len(path)
  316. while index < pathlen:
  317. c = path[index:index+1]
  318. if c == quote: # no expansion within single quotes
  319. path = path[index + 1:]
  320. pathlen = len(path)
  321. try:
  322. index = path.index(c)
  323. res += c + path[:index + 1]
  324. except ValueError:
  325. res += c + path
  326. index = pathlen - 1
  327. elif c == percent: # variable or '%'
  328. if path[index + 1:index + 2] == percent:
  329. res += c
  330. index += 1
  331. else:
  332. path = path[index+1:]
  333. pathlen = len(path)
  334. try:
  335. index = path.index(percent)
  336. except ValueError:
  337. res += percent + path
  338. index = pathlen - 1
  339. else:
  340. var = path[:index]
  341. try:
  342. if environ is None:
  343. value = os.fsencode(os.environ[os.fsdecode(var)])
  344. else:
  345. value = environ[var]
  346. except KeyError:
  347. value = percent + var + percent
  348. res += value
  349. elif c == dollar: # variable or '$$'
  350. if path[index + 1:index + 2] == dollar:
  351. res += c
  352. index += 1
  353. elif path[index + 1:index + 2] == brace:
  354. path = path[index+2:]
  355. pathlen = len(path)
  356. try:
  357. index = path.index(rbrace)
  358. except ValueError:
  359. res += dollar + brace + path
  360. index = pathlen - 1
  361. else:
  362. var = path[:index]
  363. try:
  364. if environ is None:
  365. value = os.fsencode(os.environ[os.fsdecode(var)])
  366. else:
  367. value = environ[var]
  368. except KeyError:
  369. value = dollar + brace + var + rbrace
  370. res += value
  371. else:
  372. var = path[:0]
  373. index += 1
  374. c = path[index:index + 1]
  375. while c and c in varchars:
  376. var += c
  377. index += 1
  378. c = path[index:index + 1]
  379. try:
  380. if environ is None:
  381. value = os.fsencode(os.environ[os.fsdecode(var)])
  382. else:
  383. value = environ[var]
  384. except KeyError:
  385. value = dollar + var
  386. res += value
  387. if c:
  388. index -= 1
  389. else:
  390. res += c
  391. index += 1
  392. return res
  393. # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
  394. # Previously, this function also truncated pathnames to 8+3 format,
  395. # but as this module is called "ntpath", that's obviously wrong!
  396. def normpath(path):
  397. """Normalize path, eliminating double slashes, etc."""
  398. path = os.fspath(path)
  399. if isinstance(path, bytes):
  400. sep = b'\\'
  401. altsep = b'/'
  402. curdir = b'.'
  403. pardir = b'..'
  404. special_prefixes = (b'\\\\.\\', b'\\\\?\\')
  405. else:
  406. sep = '\\'
  407. altsep = '/'
  408. curdir = '.'
  409. pardir = '..'
  410. special_prefixes = ('\\\\.\\', '\\\\?\\')
  411. if path.startswith(special_prefixes):
  412. # in the case of paths with these prefixes:
  413. # \\.\ -> device names
  414. # \\?\ -> literal paths
  415. # do not do any normalization, but return the path unchanged
  416. return path
  417. path = path.replace(altsep, sep)
  418. prefix, path = splitdrive(path)
  419. # collapse initial backslashes
  420. if path.startswith(sep):
  421. prefix += sep
  422. path = path.lstrip(sep)
  423. comps = path.split(sep)
  424. i = 0
  425. while i < len(comps):
  426. if not comps[i] or comps[i] == curdir:
  427. del comps[i]
  428. elif comps[i] == pardir:
  429. if i > 0 and comps[i-1] != pardir:
  430. del comps[i-1:i+1]
  431. i -= 1
  432. elif i == 0 and prefix.endswith(sep):
  433. del comps[i]
  434. else:
  435. i += 1
  436. else:
  437. i += 1
  438. # If the path is now empty, substitute '.'
  439. if not prefix and not comps:
  440. comps.append(curdir)
  441. return prefix + sep.join(comps)
  442. def _abspath_fallback(path):
  443. """Return the absolute version of a path as a fallback function in case
  444. `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
  445. more.
  446. """
  447. path = os.fspath(path)
  448. if not isabs(path):
  449. if isinstance(path, bytes):
  450. cwd = os.getcwdb()
  451. else:
  452. cwd = os.getcwd()
  453. path = join(cwd, path)
  454. return normpath(path)
  455. # Return an absolute path.
  456. try:
  457. from nt import _getfullpathname
  458. except ImportError: # not running on Windows - mock up something sensible
  459. abspath = _abspath_fallback
  460. else: # use native Windows method on Windows
  461. def abspath(path):
  462. """Return the absolute version of a path."""
  463. try:
  464. return normpath(_getfullpathname(path))
  465. except (OSError, ValueError):
  466. return _abspath_fallback(path)
  467. # realpath is a no-op on systems without islink support
  468. realpath = abspath
  469. # Win9x family and earlier have no Unicode filename support.
  470. supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
  471. sys.getwindowsversion()[3] >= 2)
  472. def relpath(path, start=None):
  473. """Return a relative version of a path"""
  474. path = os.fspath(path)
  475. if isinstance(path, bytes):
  476. sep = b'\\'
  477. curdir = b'.'
  478. pardir = b'..'
  479. else:
  480. sep = '\\'
  481. curdir = '.'
  482. pardir = '..'
  483. if start is None:
  484. start = curdir
  485. if not path:
  486. raise ValueError("no path specified")
  487. start = os.fspath(start)
  488. try:
  489. start_abs = abspath(normpath(start))
  490. path_abs = abspath(normpath(path))
  491. start_drive, start_rest = splitdrive(start_abs)
  492. path_drive, path_rest = splitdrive(path_abs)
  493. if normcase(start_drive) != normcase(path_drive):
  494. raise ValueError("path is on mount %r, start on mount %r" % (
  495. path_drive, start_drive))
  496. start_list = [x for x in start_rest.split(sep) if x]
  497. path_list = [x for x in path_rest.split(sep) if x]
  498. # Work out how much of the filepath is shared by start and path.
  499. i = 0
  500. for e1, e2 in zip(start_list, path_list):
  501. if normcase(e1) != normcase(e2):
  502. break
  503. i += 1
  504. rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
  505. if not rel_list:
  506. return curdir
  507. return join(*rel_list)
  508. except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
  509. genericpath._check_arg_types('relpath', path, start)
  510. raise
  511. # Return the longest common sub-path of the sequence of paths given as input.
  512. # The function is case-insensitive and 'separator-insensitive', i.e. if the
  513. # only difference between two paths is the use of '\' versus '/' as separator,
  514. # they are deemed to be equal.
  515. #
  516. # However, the returned path will have the standard '\' separator (even if the
  517. # given paths had the alternative '/' separator) and will have the case of the
  518. # first path given in the sequence. Additionally, any trailing separator is
  519. # stripped from the returned path.
  520. def commonpath(paths):
  521. """Given a sequence of path names, returns the longest common sub-path."""
  522. if not paths:
  523. raise ValueError('commonpath() arg is an empty sequence')
  524. paths = tuple(map(os.fspath, paths))
  525. if isinstance(paths[0], bytes):
  526. sep = b'\\'
  527. altsep = b'/'
  528. curdir = b'.'
  529. else:
  530. sep = '\\'
  531. altsep = '/'
  532. curdir = '.'
  533. try:
  534. drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
  535. split_paths = [p.split(sep) for d, p in drivesplits]
  536. try:
  537. isabs, = set(p[:1] == sep for d, p in drivesplits)
  538. except ValueError:
  539. raise ValueError("Can't mix absolute and relative paths") from None
  540. # Check that all drive letters or UNC paths match. The check is made only
  541. # now otherwise type errors for mixing strings and bytes would not be
  542. # caught.
  543. if len(set(d for d, p in drivesplits)) != 1:
  544. raise ValueError("Paths don't have the same drive")
  545. drive, path = splitdrive(paths[0].replace(altsep, sep))
  546. common = path.split(sep)
  547. common = [c for c in common if c and c != curdir]
  548. split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
  549. s1 = min(split_paths)
  550. s2 = max(split_paths)
  551. for i, c in enumerate(s1):
  552. if c != s2[i]:
  553. common = common[:i]
  554. break
  555. else:
  556. common = common[:len(s1)]
  557. prefix = drive + sep if isabs else drive
  558. return prefix + sep.join(common)
  559. except (TypeError, AttributeError):
  560. genericpath._check_arg_types('commonpath', *paths)
  561. raise
  562. # determine if two files are in fact the same file
  563. try:
  564. # GetFinalPathNameByHandle is available starting with Windows 6.0.
  565. # Windows XP and non-Windows OS'es will mock _getfinalpathname.
  566. if sys.getwindowsversion()[:2] >= (6, 0):
  567. from nt import _getfinalpathname
  568. else:
  569. raise ImportError
  570. except (AttributeError, ImportError):
  571. # On Windows XP and earlier, two files are the same if their absolute
  572. # pathnames are the same.
  573. # Non-Windows operating systems fake this method with an XP
  574. # approximation.
  575. def _getfinalpathname(f):
  576. return normcase(abspath(f))
  577. try:
  578. # The genericpath.isdir implementation uses os.stat and checks the mode
  579. # attribute to tell whether or not the path is a directory.
  580. # This is overkill on Windows - just pass the path to GetFileAttributes
  581. # and check the attribute from there.
  582. from nt import _isdir as isdir
  583. except ImportError:
  584. # Use genericpath.isdir as imported above.
  585. pass