]> git.ipfire.org Git - thirdparty/Python/cpython.git/blob - Lib/posixpath.py
gh-117394: Speed up os.path.ismount() on Posix (GH-117447)
[thirdparty/Python/cpython.git] / Lib / posixpath.py
1 """Common operations on Posix pathnames.
2
3 Instead of importing this module directly, import os and refer to
4 this module as os.path. The "os.path" name is an alias for this
5 module on Posix systems; on other systems (e.g. Windows),
6 os.path provides the same operations in a manner specific to that
7 platform, and is an alias to another module (e.g. ntpath).
8
9 Some of this can actually be useful on non-Posix systems too, e.g.
10 for manipulation of the pathname component of URLs.
11 """
12
13 # Strings representing various path-related bits and pieces.
14 # These are primarily for export; internally, they are hardcoded.
15 # Should be set before imports for resolving cyclic dependency.
16 curdir = '.'
17 pardir = '..'
18 extsep = '.'
19 sep = '/'
20 pathsep = ':'
21 defpath = '/bin:/usr/bin'
22 altsep = None
23 devnull = '/dev/null'
24
25 import os
26 import sys
27 import stat
28 import genericpath
29 from genericpath import *
30
31 __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
32 "basename","dirname","commonprefix","getsize","getmtime",
33 "getatime","getctime","islink","exists","lexists","isdir","isfile",
34 "ismount", "expanduser","expandvars","normpath","abspath",
35 "samefile","sameopenfile","samestat",
36 "curdir","pardir","sep","pathsep","defpath","altsep","extsep",
37 "devnull","realpath","supports_unicode_filenames","relpath",
38 "commonpath", "isjunction","isdevdrive"]
39
40
41 def _get_sep(path):
42 if isinstance(path, bytes):
43 return b'/'
44 else:
45 return '/'
46
47 # Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.
48 # On MS-DOS this may also turn slashes into backslashes; however, other
49 # normalizations (such as optimizing '../' away) are not allowed
50 # (another function should be defined to do that).
51
52 def normcase(s):
53 """Normalize case of pathname. Has no effect under Posix"""
54 return os.fspath(s)
55
56
57 # Return whether a path is absolute.
58 # Trivial in Posix, harder on the Mac or MS-DOS.
59
60 def isabs(s):
61 """Test whether a path is absolute"""
62 s = os.fspath(s)
63 sep = _get_sep(s)
64 return s.startswith(sep)
65
66
67 # Join pathnames.
68 # Ignore the previous parts if a part is absolute.
69 # Insert a '/' unless the first part is empty or already ends in '/'.
70
71 def join(a, *p):
72 """Join two or more pathname components, inserting '/' as needed.
73 If any component is an absolute path, all previous path components
74 will be discarded. An empty last part will result in a path that
75 ends with a separator."""
76 a = os.fspath(a)
77 sep = _get_sep(a)
78 path = a
79 try:
80 for b in p:
81 b = os.fspath(b)
82 if b.startswith(sep) or not path:
83 path = b
84 elif path.endswith(sep):
85 path += b
86 else:
87 path += sep + b
88 except (TypeError, AttributeError, BytesWarning):
89 genericpath._check_arg_types('join', a, *p)
90 raise
91 return path
92
93
94 # Split a path in head (everything up to the last '/') and tail (the
95 # rest). If the path ends in '/', tail will be empty. If there is no
96 # '/' in the path, head will be empty.
97 # Trailing '/'es are stripped from head unless it is the root.
98
99 def split(p):
100 """Split a pathname. Returns tuple "(head, tail)" where "tail" is
101 everything after the final slash. Either part may be empty."""
102 p = os.fspath(p)
103 sep = _get_sep(p)
104 i = p.rfind(sep) + 1
105 head, tail = p[:i], p[i:]
106 if head and head != sep*len(head):
107 head = head.rstrip(sep)
108 return head, tail
109
110
111 # Split a path in root and extension.
112 # The extension is everything starting at the last dot in the last
113 # pathname component; the root is everything before that.
114 # It is always true that root + ext == p.
115
116 def splitext(p):
117 p = os.fspath(p)
118 if isinstance(p, bytes):
119 sep = b'/'
120 extsep = b'.'
121 else:
122 sep = '/'
123 extsep = '.'
124 return genericpath._splitext(p, sep, None, extsep)
125 splitext.__doc__ = genericpath._splitext.__doc__
126
127 # Split a pathname into a drive specification and the rest of the
128 # path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.
129
130 def splitdrive(p):
131 """Split a pathname into drive and path. On Posix, drive is always
132 empty."""
133 p = os.fspath(p)
134 return p[:0], p
135
136
137 def splitroot(p):
138 """Split a pathname into drive, root and tail. On Posix, drive is always
139 empty; the root may be empty, a single slash, or two slashes. The tail
140 contains anything after the root. For example:
141
142 splitroot('foo/bar') == ('', '', 'foo/bar')
143 splitroot('/foo/bar') == ('', '/', 'foo/bar')
144 splitroot('//foo/bar') == ('', '//', 'foo/bar')
145 splitroot('///foo/bar') == ('', '/', '//foo/bar')
146 """
147 p = os.fspath(p)
148 if isinstance(p, bytes):
149 sep = b'/'
150 empty = b''
151 else:
152 sep = '/'
153 empty = ''
154 if p[:1] != sep:
155 # Relative path, e.g.: 'foo'
156 return empty, empty, p
157 elif p[1:2] != sep or p[2:3] == sep:
158 # Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
159 return empty, sep, p[1:]
160 else:
161 # Precisely two leading slashes, e.g.: '//foo'. Implementation defined per POSIX, see
162 # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
163 return empty, p[:2], p[2:]
164
165
166 # Return the tail (basename) part of a path, same as split(path)[1].
167
168 def basename(p):
169 """Returns the final component of a pathname"""
170 p = os.fspath(p)
171 sep = _get_sep(p)
172 i = p.rfind(sep) + 1
173 return p[i:]
174
175
176 # Return the head (dirname) part of a path, same as split(path)[0].
177
178 def dirname(p):
179 """Returns the directory component of a pathname"""
180 p = os.fspath(p)
181 sep = _get_sep(p)
182 i = p.rfind(sep) + 1
183 head = p[:i]
184 if head and head != sep*len(head):
185 head = head.rstrip(sep)
186 return head
187
188
189 # Is a path a mount point?
190 # (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
191
192 def ismount(path):
193 """Test whether a path is a mount point"""
194 try:
195 s1 = os.lstat(path)
196 except (OSError, ValueError):
197 # It doesn't exist -- so not a mount point. :-)
198 return False
199 else:
200 # A symlink can never be a mount point
201 if stat.S_ISLNK(s1.st_mode):
202 return False
203
204 path = os.fspath(path)
205 if isinstance(path, bytes):
206 parent = join(path, b'..')
207 else:
208 parent = join(path, '..')
209 try:
210 s2 = os.lstat(parent)
211 except OSError:
212 parent = realpath(parent)
213 try:
214 s2 = os.lstat(parent)
215 except OSError:
216 return False
217
218 # path/.. on a different device as path or the same i-node as path
219 return s1.st_dev != s2.st_dev or s1.st_ino == s2.st_ino
220
221
222 # Expand paths beginning with '~' or '~user'.
223 # '~' means $HOME; '~user' means that user's home directory.
224 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
225 # the path is returned unchanged (leaving error reporting to whatever
226 # function is called with the expanded path as argument).
227 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
228 # (A function should also be defined to do full *sh-style environment
229 # variable expansion.)
230
231 def expanduser(path):
232 """Expand ~ and ~user constructions. If user or $HOME is unknown,
233 do nothing."""
234 path = os.fspath(path)
235 if isinstance(path, bytes):
236 tilde = b'~'
237 else:
238 tilde = '~'
239 if not path.startswith(tilde):
240 return path
241 sep = _get_sep(path)
242 i = path.find(sep, 1)
243 if i < 0:
244 i = len(path)
245 if i == 1:
246 if 'HOME' not in os.environ:
247 try:
248 import pwd
249 except ImportError:
250 # pwd module unavailable, return path unchanged
251 return path
252 try:
253 userhome = pwd.getpwuid(os.getuid()).pw_dir
254 except KeyError:
255 # bpo-10496: if the current user identifier doesn't exist in the
256 # password database, return the path unchanged
257 return path
258 else:
259 userhome = os.environ['HOME']
260 else:
261 try:
262 import pwd
263 except ImportError:
264 # pwd module unavailable, return path unchanged
265 return path
266 name = path[1:i]
267 if isinstance(name, bytes):
268 name = os.fsdecode(name)
269 try:
270 pwent = pwd.getpwnam(name)
271 except KeyError:
272 # bpo-10496: if the user name from the path doesn't exist in the
273 # password database, return the path unchanged
274 return path
275 userhome = pwent.pw_dir
276 # if no user home, return the path unchanged on VxWorks
277 if userhome is None and sys.platform == "vxworks":
278 return path
279 if isinstance(path, bytes):
280 userhome = os.fsencode(userhome)
281 root = b'/'
282 else:
283 root = '/'
284 userhome = userhome.rstrip(root)
285 return (userhome + path[i:]) or root
286
287
288 # Expand paths containing shell variable substitutions.
289 # This expands the forms $variable and ${variable} only.
290 # Non-existent variables are left unchanged.
291
292 _varprog = None
293 _varprogb = None
294
295 def expandvars(path):
296 """Expand shell variables of form $var and ${var}. Unknown variables
297 are left unchanged."""
298 path = os.fspath(path)
299 global _varprog, _varprogb
300 if isinstance(path, bytes):
301 if b'$' not in path:
302 return path
303 if not _varprogb:
304 import re
305 _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
306 search = _varprogb.search
307 start = b'{'
308 end = b'}'
309 environ = getattr(os, 'environb', None)
310 else:
311 if '$' not in path:
312 return path
313 if not _varprog:
314 import re
315 _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
316 search = _varprog.search
317 start = '{'
318 end = '}'
319 environ = os.environ
320 i = 0
321 while True:
322 m = search(path, i)
323 if not m:
324 break
325 i, j = m.span(0)
326 name = m.group(1)
327 if name.startswith(start) and name.endswith(end):
328 name = name[1:-1]
329 try:
330 if environ is None:
331 value = os.fsencode(os.environ[os.fsdecode(name)])
332 else:
333 value = environ[name]
334 except KeyError:
335 i = j
336 else:
337 tail = path[j:]
338 path = path[:i] + value
339 i = len(path)
340 path += tail
341 return path
342
343
344 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
345 # It should be understood that this may change the meaning of the path
346 # if it contains symbolic links!
347
348 try:
349 from posix import _path_normpath
350
351 except ImportError:
352 def normpath(path):
353 """Normalize path, eliminating double slashes, etc."""
354 path = os.fspath(path)
355 if isinstance(path, bytes):
356 sep = b'/'
357 dot = b'.'
358 dotdot = b'..'
359 else:
360 sep = '/'
361 dot = '.'
362 dotdot = '..'
363 if not path:
364 return dot
365 _, initial_slashes, path = splitroot(path)
366 comps = path.split(sep)
367 new_comps = []
368 for comp in comps:
369 if not comp or comp == dot:
370 continue
371 if (comp != dotdot or (not initial_slashes and not new_comps) or
372 (new_comps and new_comps[-1] == dotdot)):
373 new_comps.append(comp)
374 elif new_comps:
375 new_comps.pop()
376 comps = new_comps
377 path = initial_slashes + sep.join(comps)
378 return path or dot
379
380 else:
381 def normpath(path):
382 """Normalize path, eliminating double slashes, etc."""
383 path = os.fspath(path)
384 if isinstance(path, bytes):
385 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
386 return _path_normpath(path) or "."
387
388
389 def abspath(path):
390 """Return an absolute path."""
391 path = os.fspath(path)
392 if isinstance(path, bytes):
393 if not path.startswith(b'/'):
394 path = join(os.getcwdb(), path)
395 else:
396 if not path.startswith('/'):
397 path = join(os.getcwd(), path)
398 return normpath(path)
399
400
401 # Return a canonical path (i.e. the absolute location of a file on the
402 # filesystem).
403
404 def realpath(filename, *, strict=False):
405 """Return the canonical path of the specified filename, eliminating any
406 symbolic links encountered in the path."""
407 filename = os.fspath(filename)
408 if isinstance(filename, bytes):
409 sep = b'/'
410 curdir = b'.'
411 pardir = b'..'
412 getcwd = os.getcwdb
413 else:
414 sep = '/'
415 curdir = '.'
416 pardir = '..'
417 getcwd = os.getcwd
418
419 # The stack of unresolved path parts. When popped, a special value of None
420 # indicates that a symlink target has been resolved, and that the original
421 # symlink path can be retrieved by popping again. The [::-1] slice is a
422 # very fast way of spelling list(reversed(...)).
423 rest = filename.split(sep)[::-1]
424
425 # The resolved path, which is absolute throughout this function.
426 # Note: getcwd() returns a normalized and symlink-free path.
427 path = sep if filename.startswith(sep) else getcwd()
428
429 # Mapping from symlink paths to *fully resolved* symlink targets. If a
430 # symlink is encountered but not yet resolved, the value is None. This is
431 # used both to detect symlink loops and to speed up repeated traversals of
432 # the same links.
433 seen = {}
434
435 while rest:
436 name = rest.pop()
437 if name is None:
438 # resolved symlink target
439 seen[rest.pop()] = path
440 continue
441 if not name or name == curdir:
442 # current dir
443 continue
444 if name == pardir:
445 # parent dir
446 path = path[:path.rindex(sep)] or sep
447 continue
448 if path == sep:
449 newpath = path + name
450 else:
451 newpath = path + sep + name
452 try:
453 st = os.lstat(newpath)
454 if not stat.S_ISLNK(st.st_mode):
455 path = newpath
456 continue
457 except OSError:
458 if strict:
459 raise
460 path = newpath
461 continue
462 # Resolve the symbolic link
463 if newpath in seen:
464 # Already seen this path
465 path = seen[newpath]
466 if path is not None:
467 # use cached value
468 continue
469 # The symlink is not resolved, so we must have a symlink loop.
470 if strict:
471 # Raise OSError(errno.ELOOP)
472 os.stat(newpath)
473 path = newpath
474 continue
475 seen[newpath] = None # not resolved symlink
476 target = os.readlink(newpath)
477 if target.startswith(sep):
478 # Symlink target is absolute; reset resolved path.
479 path = sep
480 # Push the symlink path onto the stack, and signal its specialness by
481 # also pushing None. When these entries are popped, we'll record the
482 # fully-resolved symlink target in the 'seen' mapping.
483 rest.append(newpath)
484 rest.append(None)
485 # Push the unresolved symlink target parts onto the stack.
486 rest.extend(target.split(sep)[::-1])
487
488 return path
489
490
491 supports_unicode_filenames = (sys.platform == 'darwin')
492
493 def relpath(path, start=None):
494 """Return a relative version of a path"""
495
496 path = os.fspath(path)
497 if not path:
498 raise ValueError("no path specified")
499
500 if isinstance(path, bytes):
501 curdir = b'.'
502 sep = b'/'
503 pardir = b'..'
504 else:
505 curdir = '.'
506 sep = '/'
507 pardir = '..'
508
509 if start is None:
510 start = curdir
511 else:
512 start = os.fspath(start)
513
514 try:
515 start_list = [x for x in abspath(start).split(sep) if x]
516 path_list = [x for x in abspath(path).split(sep) if x]
517 # Work out how much of the filepath is shared by start and path.
518 i = len(commonprefix([start_list, path_list]))
519
520 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
521 if not rel_list:
522 return curdir
523 return join(*rel_list)
524 except (TypeError, AttributeError, BytesWarning, DeprecationWarning):
525 genericpath._check_arg_types('relpath', path, start)
526 raise
527
528
529 # Return the longest common sub-path of the sequence of paths given as input.
530 # The paths are not normalized before comparing them (this is the
531 # responsibility of the caller). Any trailing separator is stripped from the
532 # returned path.
533
534 def commonpath(paths):
535 """Given a sequence of path names, returns the longest common sub-path."""
536
537 paths = tuple(map(os.fspath, paths))
538
539 if not paths:
540 raise ValueError('commonpath() arg is an empty sequence')
541
542 if isinstance(paths[0], bytes):
543 sep = b'/'
544 curdir = b'.'
545 else:
546 sep = '/'
547 curdir = '.'
548
549 try:
550 split_paths = [path.split(sep) for path in paths]
551
552 try:
553 isabs, = set(p[:1] == sep for p in paths)
554 except ValueError:
555 raise ValueError("Can't mix absolute and relative paths") from None
556
557 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
558 s1 = min(split_paths)
559 s2 = max(split_paths)
560 common = s1
561 for i, c in enumerate(s1):
562 if c != s2[i]:
563 common = s1[:i]
564 break
565
566 prefix = sep if isabs else sep[:0]
567 return prefix + sep.join(common)
568 except (TypeError, AttributeError):
569 genericpath._check_arg_types('commonpath', *paths)
570 raise