Skip to content

Fix read() on a directory fd returning EBADF instead of EISDIR - #869

Open
SebTardif wants to merge 2 commits into
WebAssembly:mainfrom
SebTardif:fix/read-dir-eisdir
Open

Fix read() on a directory fd returning EBADF instead of EISDIR#869
SebTardif wants to merge 2 commits into
WebAssembly:mainfrom
SebTardif:fix/read-dir-eisdir

Conversation

@SebTardif

@SebTardif SebTardif commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

When read() is used on a directory file descriptor, POSIX requires EISDIR. On wasip2 the host read-via-stream call fails with bad-descriptor, and translate_error maps that to EBADF. On wasip3 the same host call traps (ErrorCode::BadDescriptor) because read-via-stream returns a stream, not a result.

That is the mismatch rust-lang/rust#160359 and uutils/coreutils#13625 are already working around in userland.

Change

  • wasip2: after read-via-stream fails with bad-descriptor, get_type and return EISDIR if the fd is a directory.
  • wasip3: descriptor_stat before opening the stream. Checking after the fact is not possible; the import traps.
  • wasip1: after fd_read fails with BADF/ENOTCAPABLE, fd_fdstat_get and return EISDIR for a directory.
  • Genuine EBADF (closed or invalid fd) is unchanged. The wasip2 success path does not add a stat.

Test

test/src/read-dir-eisdir.c: open a directory, read it, expect EISDIR. Invalid fd still EBADF. Regular file still reads.

CI on the first push: wasip1/p2 passed; wasip3 trapped in read-via-stream (the p3 pre-check is the follow-up on this branch).

References

Closes #865

POSIX requires EISDIR when read() is used on a directory. wasip2
read-via-stream fails with bad-descriptor, which we mapped to EBADF.
Remap that error to EISDIR when get_type or fdstat shows a directory.

Closes WebAssembly#865

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
wasip3 read-via-stream returns a stream, not a result. Wasmtime traps
with ErrorCode::BadDescriptor on a directory fd, so the later eof
remap never runs. Check descriptor_stat before opening the stream.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

@alexcrichton alexcrichton left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks! I don't think though that this is something that's best to fix here, however, but rather inside of Wasmtime itself. Needing to perform extra syscalls just to map an error code feels a bit excessive when I think the fix in Wasmtime would probably be just a line or two

@SebTardif

Copy link
Copy Markdown
Contributor Author

@alexcrichton

Thanks! I don't think though that this is something that's best to fix here, however, but rather inside of Wasmtime itself. Needing to perform extra syscalls just to map an error code feels a bit excessive when I think the fix in Wasmtime would probably be just a line or two

Agreed. I opened bytecodealliance/wasmtime#14135 so Descriptor::file() returns is-directory (and wasip3 read-via-stream reports that on the result future instead of trapping).

Once that lands, this libc extra get_type/fd_fdstat_get path is not needed for wasip2/wasip3. wasip1 still returns EBADF in Wasmtime (preview1 match and adapter), so I left that path alone here for now.

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.

wasip2: read() returns EBADF instead of EISDIR when reading a directory fd

2 participants