Skip to content

Commit 151511d

Browse files
committed
pythonGH-116380: Support dir_fd argument in glob._Globber.
Add support for globbing relative to a file descriptor in our internal `glob._Globber` class. This is necessary for unifying the `glob` and `pathlib` globbing implementations. The new functionality isn't exposed in `pathlib.Path.glob()`. It easily could be.
1 parent 6258844 commit 151511d

File tree

1 file changed

+61
-14
lines changed

1 file changed

+61
-14
lines changed

Lib/glob.py

+61-14
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,22 @@ def _compile_pattern(pat, sep, case_sensitive, recursive=True):
327327
return re.compile(regex, flags=flags).match
328328

329329

330+
def _open_dir(path, dir_fd=None, rel_path=None):
331+
"""Prepares the directory for scanning. Returns a 3-tuple with parts:
332+
333+
1. A path or fd to supply to `os.scandir()`.
334+
2. The file descriptor for the directory, or None.
335+
3. Whether the caller should close the fd (bool).
336+
"""
337+
if dir_fd is None:
338+
return path, None, False
339+
elif rel_path == './':
340+
return dir_fd, dir_fd, False
341+
else:
342+
fd = os.open(rel_path, _dir_open_flags, dir_fd=dir_fd)
343+
return fd, fd, True
344+
345+
330346
class _Globber:
331347
"""Class providing shell-style pattern matching and globbing.
332348
"""
@@ -382,9 +398,11 @@ def special_selector(self, part, parts):
382398
"""
383399
select_next = self.selector(parts)
384400

385-
def select_special(path, exists=False):
401+
def select_special(path, dir_fd=None, rel_path=None, exists=False):
386402
path = self.concat_path(self.add_slash(path), part)
387-
return select_next(path, exists)
403+
if dir_fd is not None:
404+
rel_path = self.concat_path(self.add_slash(rel_path), part)
405+
return select_next(path, dir_fd, rel_path, exists)
388406
return select_special
389407

390408
def wildcard_selector(self, part, parts):
@@ -397,11 +415,15 @@ def wildcard_selector(self, part, parts):
397415
if dir_only:
398416
select_next = self.selector(parts)
399417

400-
def select_wildcard(path, exists=False):
418+
def select_wildcard(path, dir_fd=None, rel_path=None, exists=False):
419+
close_fd = False
401420
try:
421+
arg, fd, close_fd = _open_dir(path, dir_fd, rel_path)
422+
if fd is not None:
423+
prefix = self.add_slash(path)
402424
# We must close the scandir() object before proceeding to
403425
# avoid exhausting file descriptors when globbing deep trees.
404-
with self.scandir(path) as scandir_it:
426+
with self.scandir(arg) as scandir_it:
405427
entries = list(scandir_it)
406428
except OSError:
407429
pass
@@ -415,10 +437,16 @@ def select_wildcard(path, exists=False):
415437
except OSError:
416438
continue
417439
entry_path = self.parse_entry(entry)
440+
if fd is not None:
441+
entry_path = self.concat_path(prefix, entry_path)
418442
if dir_only:
419-
yield from select_next(entry_path, exists=True)
443+
yield from select_next(
444+
entry_path, fd, entry.name, exists=True)
420445
else:
421446
yield entry_path
447+
finally:
448+
if close_fd:
449+
os.close(fd)
422450
return select_wildcard
423451

424452
def recursive_selector(self, part, parts):
@@ -444,21 +472,31 @@ def recursive_selector(self, part, parts):
444472
dir_only = bool(parts)
445473
select_next = self.selector(parts)
446474

447-
def select_recursive(path, exists=False):
475+
def select_recursive(path, dir_fd=None, rel_path=None, exists=False):
448476
path = self.add_slash(path)
477+
if dir_fd is not None:
478+
rel_path = self.add_slash(rel_path)
449479
match_pos = len(str(path))
450480
if match is None or match(str(path), match_pos):
451-
yield from select_next(path, exists)
452-
stack = [path]
481+
yield from select_next(path, dir_fd, rel_path, exists)
482+
stack = [(path, dir_fd, rel_path)]
453483
while stack:
454484
yield from select_recursive_step(stack, match_pos)
455485

456486
def select_recursive_step(stack, match_pos):
457-
path = stack.pop()
487+
path, dir_fd, rel_path = stack.pop()
458488
try:
489+
if path is None:
490+
os.close(dir_fd)
491+
return
492+
arg, fd, close_fd = _open_dir(path, dir_fd, rel_path)
493+
if fd is not None:
494+
prefix = self.add_slash(path)
495+
if close_fd:
496+
stack.append((None, fd, None))
459497
# We must close the scandir() object before proceeding to
460498
# avoid exhausting file descriptors when globbing deep trees.
461-
with self.scandir(path) as scandir_it:
499+
with self.scandir(arg) as scandir_it:
462500
entries = list(scandir_it)
463501
except OSError:
464502
pass
@@ -473,28 +511,37 @@ def select_recursive_step(stack, match_pos):
473511

474512
if is_dir or not dir_only:
475513
entry_path = self.parse_entry(entry)
514+
if fd is not None:
515+
entry_path = self.concat_path(prefix, entry_path)
476516
if match is None or match(str(entry_path), match_pos):
477517
if dir_only:
478-
yield from select_next(entry_path, exists=True)
518+
yield from select_next(
519+
entry_path, fd, entry.name, exists=True)
479520
else:
480521
# Optimization: directly yield the path if this is
481522
# last pattern part.
482523
yield entry_path
483524
if is_dir:
484-
stack.append(entry_path)
525+
stack.append((entry_path, fd, entry.name))
485526

486527
return select_recursive
487528

488-
def select_exists(self, path, exists=False):
529+
def select_exists(self, path, dir_fd=None, rel_path=None, exists=False):
489530
"""Yields the given path, if it exists.
490531
"""
491532
if exists:
492533
# Optimization: this path is already known to exist, e.g. because
493534
# it was returned from os.scandir(), so we skip calling lstat().
494535
yield path
495-
else:
536+
elif dir_fd is None:
496537
try:
497538
self.lstat(path)
498539
yield path
499540
except OSError:
500541
pass
542+
else:
543+
try:
544+
self.lstat(rel_path, dir_fd=dir_fd)
545+
yield path
546+
except OSError:
547+
pass

0 commit comments

Comments
 (0)