Skip to content

fix: handle listxattr errors on macOS - #1952

Open
kokhlo wants to merge 3 commits into
eza-community:mainfrom
kokhlo:fix/listxattr-error-1850
Open

kokhlo wants to merge 3 commits into
eza-community:mainfrom
kokhlo:fix/listxattr-error-1850

Conversation

@kokhlo

@kokhlo kokhlo commented Sep 29, 2026

Copy link
Copy Markdown

Closes #1850.

The hang

Reading extended attributes is a two call protocol. The first call passes no buffer and gets back the size, the second passes a buffer of that size and gets the data back. When the second call fails with ERANGE the buffer was too small, so the read is retried against a freshly asked for size.

That retry had no limit. A file system that keeps reporting a size its own second call then rejects never lets the loop finish, and since this runs per entry while listing, eza never finishes listing the directory. The fs_usage trace in the report shows exactly this: listxattr against the mount alternating between a size and ERANGE for as long as the capture runs.

The fix

Bound the retries at MAX_ERANGE_RETRIES = 5 and return the error once they are used up. A mount that cannot be queried now costs one log line and a listing that completes, instead of a listing that never does.

Retries that do converge are unaffected, so a size that grows once between the two calls is still picked up. Errors a larger buffer cannot fix are still reported immediately rather than retried.

File::gather_extended_attributes already logs a failed lookup and carries on with no attributes, so the error path needed no change.

Tests

Three tests drive get_loop with a stand-in for the file system:

  • one that refuses every buffer, which is the hang, and which now returns ERANGE after a bounded number of calls
  • one whose size grows once, which still recovers
  • one that fails with an error a larger buffer cannot fix, which is not retried

The first test does not pass against the previous code, where it spins instead of returning. Full suite: 358 passed, 0 failed.

Reading extended attributes is a two call protocol: one call with no buffer
reports the size, a second call fills a buffer of that size.  When the size
turned out to be too small the second call fails with ERANGE and the read was
retried.  That retry had no limit, so a file system that keeps answering with
a size its own second call rejects loops forever and eza never finishes
listing the directory.

Limit the retries and let the error through when they run out.  The caller
already treats a failed lookup as having no attributes, so a mount that
cannot be queried now costs one log line instead of a hang.  Retries that do
converge are unaffected.

Adds tests covering a mount that refuses every buffer, a size that grows once,
and an error that a larger buffer cannot fix.
cargo fmt wanted the call count in a local before the assertion, so spell
out what is being counted rather than leaving a long expression inline.
The tests set the errno slot directly to drive a specific error path, and
that slot is spelled differently per platform: __error on macOS, iOS and
FreeBSD, __errno_location on Linux, and netbsd and openbsd expose neither.
Choosing __errno_location for everything that was not macOS or iOS
therefore failed to compile on FreeBSD.

Use the symbol each platform actually has, and leave the tests out on the
two platforms that expose no way to reach the slot.
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.

bug: eza hangs indefinitely on macOS when --icons/xattr detection hits a macFUSE mount that returns inconsistent listxattr buffer sizes

1 participant