logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

ntpath.py (28406B)


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