Skip to content

fix: report the status describing a filesystem error, not always EIO - #13

Merged
AliRamberg merged 1 commit into
masterfrom
fix/errno-nfs-status
Aug 3, 2026
Merged

fix: report the status describing a filesystem error, not always EIO#13
AliRamberg merged 1 commit into
masterfrom
fix/errno-nfs-status

Conversation

@AliRamberg

@AliRamberg AliRamberg commented Aug 3, 2026

Copy link
Copy Markdown

A Filesystem's only channel to the client is the error it returns, but call sites hard-coded the status, so read-only, out-of-space, over-quota and non-empty-directory all reached the user as a bare EIO. The up-front CapabilityCheck can't help: it asks whether the filesystem accepts writes at all, not whether it accepts this one.

statusFromError maps the error to the status describing it (EROFSROFS, ENOSPCNoSPC, EDQUOTDQuot, ENOTEMPTYNotEmpty, EXDEVXDev, plus the io/fs sentinels), matching with errors.Is so wrapped errors resolve; each call site keeps its old status as the fallback. It also replaces the os.IsNotExist/os.IsPermission ladders at 16 sites, which missed wrapped errors.

go test ./... 34 pass, builds for linux/windows/darwin. TestOperationErrorKeepsItsStatus returns EROFS from Rename: NFS3ERR_IO (5) on master, NFS3ERR_ROFS (30) with this change.

Follows #12, whose one-off EROFS mapping in onRename is dropped in favour of covering every operation here.

@AliRamberg
AliRamberg force-pushed the fix/errno-nfs-status branch from 66327fb to d6057a8 Compare August 3, 2026 09:41
@AliRamberg
AliRamberg requested a review from arielshaqed August 3, 2026 13:18

@arielshaqed arielshaqed left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is really useful code. We should probably plan to push it upstream as well.

That said, the comments make the code much less readable - see my notes about them. Also I do not see the values that the largest test provides. Approving, but would gladly see less comments and test code in this PR.

Comment thread errors.go Outdated
// recognizes nothing.
//
// A Filesystem's only channel to the client is the error it returns, and call
// sites used to hard-code a status for it -- almost always NFSStatusIO. That

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment describe the current code. They should not describe some buggy previous code -- "used to hard-code a status". They should not describe bad things that used to happen or could happen. OTOH they should just say to use it.
Also use godoc for function comments. For instance

// statusFromError translates an error code into a suitable NFS status.

Comment thread errors.go Outdated
// into a single EIO, which tells the user nothing and invites clients to retry
// what cannot succeed.
//
// Cases match with errors.Is, so a *PathError or a filesystem's own added

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This is an implementation comment. If it belongs anywhere, it belongs inside the function.
  2. This particular implementation comment explains how the code could be written incorrectly but wasn't. I would just drop it.

Comment thread errors.go Outdated
//
// Cases match with errors.Is, so a *PathError or a filesystem's own added
// context still resolves. The errnos come first: the io/fs sentinels are
// broader and would otherwise swallow them.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand which io/fs sentinels these are that extend syscall.E* errors. But - if there are any - this would be an implementation comment to put somewhere inside the switch.

Comment thread errors_test.go Outdated
Comment on lines +12 to +15
// A filesystem's error is its only channel to the client, so each error it can
// return must reach the client as the status describing it -- not as a blanket
// NFSStatusIO, which tells the user nothing and invites pointless retries.
func TestStatusFromError(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this test? It looks like a second copy of the switch statement of statusFromError - I would just lose this error.

Comment thread errors_test.go Outdated
Comment on lines +12 to +14
// A filesystem's error is its only channel to the client, so each error it can
// return must reach the client as the status describing it -- not as a blanket
// NFSStatusIO, which tells the user nothing and invites pointless retries.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to justify the code. Not relevant above a test.

Comment thread nfs_test.go Outdated
Comment on lines +594 to +597
// A filesystem's error must reach the client as the status describing it. The
// up-front WriteCapability check can't catch a filesystem that accepts writes
// in general and refuses this one, so the error from the operation is the only
// signal -- and reporting it as NFS3ERR_IO leaves the user with a bare EIO.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this comment does not describe the test. I would remove it, maybe keep it in the PR description (TBH I think the description already explains it).

A Filesystem's only channel to the client is the error it returns, and
call sites hard-coded the status for it: NFSStatusIO, or NFSStatusAccess
for the write operations. So "read-only filesystem", "out of space",
"over quota" and "directory not empty" all reached the user as a bare
EIO, which says nothing about what went wrong and invites the client to
retry work that cannot succeed.

Map the error to its status instead. statusFromError matches with
errors.Is, so a *PathError or a filesystem's own added context still
resolves, and each call site keeps its previous status as the fallback
for errors we don't recognize -- unrecognized errors report exactly what
they did before.

This also replaces the os.IsNotExist/os.IsPermission ladders repeated at
16 call sites. Those used os.Is*, which does not see through wrapped
errors; the mapper does, so a wrapped ErrNotExist from a user-supplied
OpenDir now reports NOENT rather than EIO.

Signed-off-by: Yahli Ramberg <lryahli@gmail.com>
@AliRamberg
AliRamberg force-pushed the fix/errno-nfs-status branch from d6057a8 to a3de74b Compare August 3, 2026 13:53
@AliRamberg
AliRamberg merged commit be09cb1 into master Aug 3, 2026
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.

2 participants