Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(807)

Side by Side Diff: Lib/os.py

Issue 15177: Support os.walk(dir_fd=) and os.fwalk(dir_fd=)
Patch Set: Created 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 r"""OS routines for Mac, NT, or Posix depending on what system we're on. 1 r"""OS routines for Mac, NT, or Posix depending on what system we're on.
2 2
3 This exports: 3 This exports:
4 - all functions from posix, nt, os2, or ce, e.g. unlink, stat, etc. 4 - all functions from posix, nt, os2, or ce, e.g. unlink, stat, etc.
5 - os.path is either posixpath or ntpath 5 - os.path is either posixpath or ntpath
6 - os.name is either 'posix', 'nt', 'os2' or 'ce'. 6 - os.name is either 'posix', 'nt', 'os2' or 'ce'.
7 - os.curdir is a string representing the current directory ('.' or ':') 7 - os.curdir is a string representing the current directory ('.' or ':')
8 - os.pardir is a string representing the parent directory ('..' or '::') 8 - os.pardir is a string representing the parent directory ('..' or '::')
9 - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\') 9 - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\')
10 - os.extsep is the extension separator (always '.') 10 - os.extsep is the extension separator (always '.')
(...skipping 313 matching lines...) Expand 10 before | Expand all | Expand 10 after
324 rename(old, new) 324 rename(old, new)
325 head, tail = path.split(old) 325 head, tail = path.split(old)
326 if head and tail: 326 if head and tail:
327 try: 327 try:
328 removedirs(head) 328 removedirs(head)
329 except error: 329 except error:
330 pass 330 pass
331 331
332 __all__.extend(["makedirs", "removedirs", "renames"]) 332 __all__.extend(["makedirs", "removedirs", "renames"])
333 333
334 def walk(top, topdown=True, onerror=None, followlinks=False): 334 def _isdir_dir_fd(path, dir_fd):
335 """
336 A substitute for os.path.isdir that supports dir_fd.
337 """
338 try:
339 return st.S_ISDIR(stat(path, dir_fd=dir_fd).st_mode)
340 except FileNotFoundError:
341 return False
storchaka 2012/06/25 14:08:10 Should onerror handler be used?
larry 2012/06/25 15:04:13 I have a much better implementation, so this one i
342
343 def walk(top=".", topdown=True, onerror=None, followlinks=False, *, dir_fd=None) :
335 """Directory tree generator. 344 """Directory tree generator.
336 345
337 For each directory in the directory tree rooted at top (including top 346 For each directory in the directory tree rooted at top (including top
338 itself, but excluding '.' and '..'), yields a 3-tuple 347 itself, but excluding '.' and '..'), yields a 3-tuple
339 348
340 dirpath, dirnames, filenames 349 dirpath, dirnames, filenames
341 350
342 dirpath is a string, the path to the directory. dirnames is a list of 351 dirpath is a string, the path to the directory. dirnames is a list of
343 the names of the subdirectories in dirpath (excluding '.' and '..'). 352 the names of the subdirectories in dirpath (excluding '.' and '..').
344 filenames is a list of the names of the non-directory files in dirpath. 353 filenames is a list of the names of the non-directory files in dirpath.
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
388 dirs.remove('CVS') # don't visit CVS directories 397 dirs.remove('CVS') # don't visit CVS directories
389 """ 398 """
390 399
391 islink, join, isdir = path.islink, path.join, path.isdir 400 islink, join, isdir = path.islink, path.join, path.isdir
392 401
393 # We may not have read permission for top, in which case we can't 402 # We may not have read permission for top, in which case we can't
394 # get a list of the files the directory contains. os.walk 403 # get a list of the files the directory contains. os.walk
395 # always suppressed the exception then, rather than blow up for a 404 # always suppressed the exception then, rather than blow up for a
396 # minor reason when (say) a thousand readable directories are still 405 # minor reason when (say) a thousand readable directories are still
397 # left to visit. That logic is copied here. 406 # left to visit. That logic is copied here.
407 #
408 # Note that listdir and error are globals in this module due
409 # to earlier import-*.
398 try: 410 try:
399 # Note that listdir and error are globals in this module due 411 if dir_fd is None:
400 # to earlier import-*. 412 close_me = None
401 names = listdir(top) 413 names = listdir(top)
414 else:
415 close_me = open(top, O_RDONLY, dir_fd=dir_fd)
416 names = listdir(close_me)
402 except error as err: 417 except error as err:
418 if close_me is not None:
419 close(close_me)
storchaka 2012/06/25 14:08:10 What if error happens on close?
larry 2012/06/25 15:04:13 Obviated by new implementation.
403 if onerror is not None: 420 if onerror is not None:
404 onerror(err) 421 onerror(err)
405 return 422 return
406 423
407 dirs, nondirs = [], [] 424 dirs, nondirs = [], []
408 for name in names: 425 if close_me is not None:
409 if isdir(join(top, name)): 426 for name in names:
410 dirs.append(name) 427 # name is in directory referenced by close_me!
411 else: 428 # don't bother joining with top here.
412 nondirs.append(name) 429 if _isdir_dir_fd(name, close_me):
430 dirs.append(name)
431 else:
432 nondirs.append(name)
433 close(close_me)
434 else:
435 for name in names:
436 new_path = join(top, name)
437 if isdir(new_path):
438 dirs.append(name)
439 else:
440 nondirs.append(name)
413 441
414 if topdown: 442 if topdown:
415 yield top, dirs, nondirs 443 yield top, dirs, nondirs
416 for name in dirs: 444 for name in dirs:
417 new_path = join(top, name) 445 new_path = join(top, name)
418 if followlinks or not islink(new_path): 446 if followlinks or not islink(new_path):
storchaka 2012/06/25 14:08:10 st.S_ISLINK(stat(name, dir_fd=close_me).st_mode) ?
larry 2012/06/25 15:04:13 Obviated by new implementation.
419 yield from walk(new_path, topdown, onerror, followlinks) 447 yield from walk(new_path, topdown, onerror, followlinks, dir_fd=dir_ fd)
420 if not topdown: 448 if not topdown:
421 yield top, dirs, nondirs 449 yield top, dirs, nondirs
422 450
423 __all__.append("walk") 451 __all__.append("walk")
452 if (listdir in supports_fd) and ({open, stat} <= supports_dir_fd):
453 supports_dir_fd.add(walk)
424 454
425 if open in supports_dir_fd: 455 if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
426 456
427 def fwalk(top, topdown=True, onerror=None, followlinks=False): 457 def fwalk(top=".", topdown=True, onerror=None, followlinks=False, *, dir_fd= None):
428 """Directory tree generator. 458 """Directory tree generator.
429 459
430 This behaves exactly like walk(), except that it yields a 4-tuple 460 This behaves exactly like walk(), except that it yields a 4-tuple
431 461
432 dirpath, dirnames, filenames, dirfd 462 dirpath, dirnames, filenames, dirfd
433 463
434 `dirpath`, `dirnames` and `filenames` are identical to walk() output, 464 `dirpath`, `dirnames` and `filenames` are identical to walk() output,
435 and `dirfd` is a file descriptor referring to the directory `dirpath`. 465 and `dirfd` is a file descriptor referring to the directory `dirpath`.
436 466
437 The advantage of walkfd() over walk() is that it's safe against symlink 467 The advantage of fwalk() over walk() is that it's safe against symlink
438 races (when followlinks is False). 468 races (when followlinks is False).
469
470 If dir_fd is not None, it should be a file descriptor open to a director y,
471 and top should be relative; top will then be relative to that director y.
472 (dir_fd is always supported for fwalk.)
439 473
440 Caution: 474 Caution:
441 Since fwalk() yields file descriptors, those are only valid until the 475 Since fwalk() yields file descriptors, those are only valid until the
442 next iteration step, so you should dup() them if you want to keep them 476 next iteration step, so you should dup() them if you want to keep them
443 for a longer period. 477 for a longer period.
444 478
445 Example: 479 Example:
446 480
447 import os 481 import os
448 for root, dirs, files, rootfd in os.fwalk('python/Lib/email'): 482 for root, dirs, files, rootfd in os.fwalk('python/Lib/email'):
449 print(root, "consumes", end="") 483 print(root, "consumes", end="")
450 print(sum([os.stat(name, dir_fd=rootfd).st_size for name in files]), 484 print(sum([os.stat(name, dir_fd=rootfd).st_size for name in files]),
451 end="") 485 end="")
452 print("bytes in", len(files), "non-directory files") 486 print("bytes in", len(files), "non-directory files")
453 if 'CVS' in dirs: 487 if 'CVS' in dirs:
454 dirs.remove('CVS') # don't visit CVS directories 488 dirs.remove('CVS') # don't visit CVS directories
455 """ 489 """
456 # Note: To guard against symlink races, we use the standard 490 # Note: To guard against symlink races, we use the standard
457 # lstat()/open()/fstat() trick. 491 # lstat()/open()/fstat() trick.
458 orig_st = lstat(top) 492 orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
459 topfd = open(top, O_RDONLY) 493 topfd = open(top, O_RDONLY, dir_fd=dir_fd)
460 try: 494 try:
461 if (followlinks or (st.S_ISDIR(orig_st.st_mode) and 495 if (followlinks or (st.S_ISDIR(orig_st.st_mode) and
462 path.samestat(orig_st, fstat(topfd)))): 496 path.samestat(orig_st, stat(topfd)))):
463 yield from _fwalk(topfd, top, topdown, onerror, followlinks) 497 yield from _fwalk(topfd, top, topdown, onerror, followlinks)
464 finally: 498 finally:
465 close(topfd) 499 close(topfd)
466 500
467 def _fwalk(topfd, toppath, topdown, onerror, followlinks): 501 def _fwalk(topfd, toppath, topdown, onerror, followlinks):
468 # Note: This uses O(depth of the directory tree) file descriptors: if 502 # Note: This uses O(depth of the directory tree) file descriptors: if
469 # necessary, it can be adapted to only require O(1) FDs, see issue 503 # necessary, it can be adapted to only require O(1) FDs, see issue
470 # #13734. 504 # #13734.
471 505
472 names = listdir(topfd) 506 names = listdir(topfd)
(...skipping 22 matching lines...) Expand all
495 529
496 for name in dirs: 530 for name in dirs:
497 try: 531 try:
498 orig_st = stat(name, dir_fd=topfd, follow_symlinks=followlinks) 532 orig_st = stat(name, dir_fd=topfd, follow_symlinks=followlinks)
499 dirfd = open(name, O_RDONLY, dir_fd=topfd) 533 dirfd = open(name, O_RDONLY, dir_fd=topfd)
500 except error as err: 534 except error as err:
501 if onerror is not None: 535 if onerror is not None:
502 onerror(err) 536 onerror(err)
503 return 537 return
504 try: 538 try:
505 if followlinks or path.samestat(orig_st, fstat(dirfd)): 539 if followlinks or path.samestat(orig_st, stat(dirfd)):
506 dirpath = path.join(toppath, name) 540 dirpath = path.join(toppath, name)
507 yield from _fwalk(dirfd, dirpath, topdown, onerror, followli nks) 541 yield from _fwalk(dirfd, dirpath, topdown, onerror, followli nks)
508 finally: 542 finally:
509 close(dirfd) 543 close(dirfd)
510 544
511 if not topdown: 545 if not topdown:
512 yield toppath, dirs, nondirs, topfd 546 yield toppath, dirs, nondirs, topfd
513 547
514 __all__.append("fwalk") 548 __all__.append("fwalk")
515 549
(...skipping 503 matching lines...) Expand 10 before | Expand all | Expand 10 after
1019 return getattr(self._stream, name) 1053 return getattr(self._stream, name)
1020 def __iter__(self): 1054 def __iter__(self):
1021 return iter(self._stream) 1055 return iter(self._stream)
1022 1056
1023 # Supply os.fdopen() 1057 # Supply os.fdopen()
1024 def fdopen(fd, *args, **kwargs): 1058 def fdopen(fd, *args, **kwargs):
1025 if not isinstance(fd, int): 1059 if not isinstance(fd, int):
1026 raise TypeError("invalid fd type (%s, expected integer)" % type(fd)) 1060 raise TypeError("invalid fd type (%s, expected integer)" % type(fd))
1027 import io 1061 import io
1028 return io.open(fd, *args, **kwargs) 1062 return io.open(fd, *args, **kwargs)
OLDNEW
« Doc/library/os.rst ('K') | « Doc/library/os.rst ('k') | Lib/test/test_os.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld cbc36f91f3f7