ntpath.py 26 KB

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