From 4146e2f68ec2ab7687e0c6e4b704504266c75f57 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:23:53 +0200 Subject: [PATCH] Skip unreadable dirs in recursive file walk (closes #3) paths_in_dir caught only FileNotFoundError, so os.listdir on an OS-protected directory (e.g. macOS's .../com.apple.*/TemporaryItems/ under the temp dir) raised PermissionError and crashed the whole recursive walk. This made FileBytesReader(gettempdir()) / DirReader(gettempdir()) unusable. Extend the caught set to (FileNotFoundError, NotADirectoryError, PermissionError) so the walk skips directories it can't read/list instead of crashing. - Verified: FileBytesReader(gettempdir()) now lists (was PermissionError). - Regression test: an unreadable (chmod 000) subdir is skipped, readable siblings are still returned (POSIX-only; guarded/skipped on Windows and when running as root). - Dependents test-gate: 39/56 pass, 0 PASS->FAIL (same 17 pre-existing env failures). Closes #3 Claude-Session: https://claude.ai/code/session_01H8PmV6xmMhzV64zi1Twraw --- dol/filesys.py | 6 +++- dol/tests/test_walk_permissions.py | 47 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 dol/tests/test_walk_permissions.py diff --git a/dol/filesys.py b/dol/filesys.py index 6fdf7a6d..841c78ab 100644 --- a/dol/filesys.py +++ b/dol/filesys.py @@ -41,7 +41,11 @@ def paths_in_dir(rootdir, include_hidden=False): yield ensure_slash_suffix(filepath) else: yield filepath - except FileNotFoundError: + except (FileNotFoundError, NotADirectoryError, PermissionError): + # Skip directories that vanished, aren't directories, or aren't readable rather + # than crashing the (recursive) walk. Notably, OS-protected directories such as + # macOS's ``.../com.apple.*/TemporaryItems/`` under the temp dir raise + # PermissionError from ``os.listdir`` (Issue #3). pass diff --git a/dol/tests/test_walk_permissions.py b/dol/tests/test_walk_permissions.py new file mode 100644 index 00000000..8b40b4be --- /dev/null +++ b/dol/tests/test_walk_permissions.py @@ -0,0 +1,47 @@ +"""Regression test for Issue #3: the recursive file walk must skip unreadable dirs. + +``FileBytesReader(gettempdir())`` used to raise ``PermissionError`` because ``os.listdir`` +hits OS-protected subdirectories (e.g. macOS's ``.../com.apple.*/TemporaryItems/``). The +walk now skips directories it cannot read instead of crashing. +""" + +import os +import sys + +import pytest + +from dol.filesys import FileBytesReader, paths_in_dir + + +def test_recursive_walk_skips_unreadable_dirs(tmp_path): + # a readable file at the root + (tmp_path / "readable.txt").write_bytes(b"hi") + # a subdirectory we will make unreadable + protected = tmp_path / "protected" + protected.mkdir() + (protected / "secret.txt").write_bytes(b"nope") + + if sys.platform.startswith("win"): + pytest.skip("POSIX directory permissions don't apply on Windows") + + os.chmod(protected, 0o000) + try: + # If we can still list it (e.g. running as root), the scenario is unreachable. + try: + os.listdir(protected) + pytest.skip("cannot make a directory unreadable in this environment") + except PermissionError: + pass + + # The walk must not raise, and must skip the unreadable subdir's contents. + s = FileBytesReader(str(tmp_path)) + keys = list(s) # was: PermissionError + assert any(k.endswith("readable.txt") for k in keys) + assert not any(k.endswith("secret.txt") for k in keys) + finally: + os.chmod(protected, 0o755) # restore so tmp cleanup can remove it + + +def test_paths_in_dir_on_missing_dir_is_empty(tmp_path): + missing = str(tmp_path / "does_not_exist") + assert list(paths_in_dir(missing)) == []