Skip to content

ls: contain PermissionError on stat and readdir - #12

Open
ninowalker wants to merge 1 commit into
dbreunig:mainfrom
ninowalker:fix/ls-permission-denied
Open

ls: contain PermissionError on stat and readdir#12
ninowalker wants to merge 1 commit into
dbreunig:mainfrom
ninowalker:fix/ls-permission-denied

Conversation

@ninowalker

@ninowalker ninowalker commented Sep 7, 2026

Copy link
Copy Markdown

Discovered while developing. Companion to #10 and #11 — same family, found the same way, by hitting it.

The problem

ls catches FileNotFoundError on its operand's stat and PermissionError on readdir, but a backend whose stat raises PermissionError escapes bash.exec() as a Python exception. cat and stat fail the same backend in-band:

ls /denied        -> PermissionError escapes
cat /denied/x     -> exit 1, "cat: /denied/x: Permission denied"
stat /denied      -> exit 1, "stat: /denied: ..."

ls -l has the same gap on per-entry stat: a child whose stat refuses escapes mid-listing, where a vanished child already renders as ?????????.

The change

  • Operand stat: except PermissionError alongside FileNotFoundErrorls: cannot access 'PATH': Permission denied, exit 2.
  • Per-entry stat in the -l, -1 -F, and plain -F paths: PermissionError joins FileNotFoundError (placeholder row / skip), so one denied child no longer aborts the listing.
  • readdir already caught PermissionError; untouched.

A sweep found this is not just ls

Running every non-network command against a backend that denies /denied on stat/readdir/read, the same gap is in 32 other commands (head, tail, wc, sort, md5sum, awk, find, du, tree, touch, bash, sh, ...). Those raise for the same reason ls did: the read/stat call catches FileNotFoundError but not PermissionError. #11 contains them all at the boundary; this PR gives ls the coreutils-consistent message the issue asked for. The others can take the same one-line treatment if you'd like them to.

Tests

tests/test_commands/test_minor_commands.py — a DenyingFs backend and four tests: denied operand (plain and -l) reports cannot access with exit 2, denied readdir stays in-band, and a denied child renders as ????????? without aborting the listing.

ruff check on the touched files: the 5 pre-existing findings in ls.py before and after; the test file is clean.

A backend that refuses stat raises PermissionError, which ls let escape
bash.exec() as a Python exception where cat and stat fail the command
in-band. Catch it on the operand and on per-entry stats; readdir
already handled it.
@ninowalker
ninowalker force-pushed the fix/ls-permission-denied branch from f474ea3 to 50fae16 Compare September 7, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant