kernel: implement FIFO (named pipe) semantics — fixes process substitution / brew config#864
Open
brandonpayton wants to merge 1 commit into
Open
kernel: implement FIFO (named pipe) semantics — fixes process substitution / brew config#864brandonpayton wants to merge 1 commit into
brandonpayton wants to merge 1 commit into
Conversation
mknod(S_IFIFO)/mkfifo created a regular file with no pipe semantics — the
mknod dispatcher stripped the S_IFIFO type bits and made a plain file. A
concurrent reader of that "FIFO" therefore saw an empty regular file and
got a premature EOF. This breaks bash process substitution `read < <(cmd)`,
which mknod()s a FIFO in $TMPDIR and reads it while the writer subshell is
still producing output. It blocked Homebrew: `brew config`/`doctor` parse
`git --version` and discover ruby via `read < <(...)`, so both failed
("Please update your system Git", "No Homebrew ruby available").
A FIFO is now a real named kernel pipe:
- crate::fifo: registry mapping a FIFO's canonical path to its backing pipe.
- mknod/mknodat(S_IFIFO) registers a kernel pipe (PipeBuffer::new_fifo, no
endpoints open) instead of creating a regular file.
- open() connects a read or write end to that shared pipe (add_reader/
add_writer) → real block/EAGAIN/EOF semantics. Fork inheritance works via
the existing negative-host_handle Pipe OFD path.
- A FIFO reader with no writer yet blocks (EAGAIN) instead of reporting EOF
until the first writer connects, fixing the reader-opens-first race.
- stat/lstat/newfstatat report S_IFIFO for registered FIFOs (no VFS inode).
- unlink/unlinkat remove the name and free the pipe once idle; a FIFO pipe
is exempt from is_fully_closed so it persists until unlinked.
Validated: process-substitution repro (`read < <(sleep 0.5; printf X)`
returns X, previously empty); `brew config --no-install-from-api` now exits
0 in-Kandelo (previously failed on git-version + portable-ruby); 964 kernel
unit tests pass (adds test_fifo_named_pipe_semantics). No ABI change (no new
syscalls or repr(C) structs; snapshot unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
mknod(S_IFIFO)/mkfifocreated a regular file with no pipe semantics — the mknod dispatcher stripped theS_IFIFOtype bits (mode & 0o7777) and made a plain file. A concurrent reader of that "FIFO" saw an empty regular file and got a premature EOF.This breaks bash process substitution
read < <(cmd): bashmknod()s a FIFO in$TMPDIR(/tmp/sh-np.XXXXXX) and reads it while the writer subshell is still producing output. Traced end-to-end in a Kandelo guest: the reader'sreadreturned 0 because the temp file was an emptyS_IFREG(fstatfifo=false file=true size=0).It blocked Homebrew in Kandelo:
brew config/doctorparsegit --versionviaread < <(printf …)and discover ruby viawhile read < <(which -a ruby). Both got empty output → "Please update your system Git" and "No Homebrew ruby available for wasm32". Both collapse to this one bug.Fix — real named kernel pipes
crate::fifo: registry mapping a FIFO's canonical path → backing pipe index.mknod/mknodat(S_IFIFO)register a kernel pipe (PipeBuffer::new_fifo, no endpoints open) instead of creating a regular file.open()connects a read or write end to that shared pipe (add_reader/add_writer) → realblock/EAGAIN/EOFsemantics. Fork inheritance works through the existing negative-host_handleFileType::PipeOFD path (no new fork logic).stat/lstat/newfstatatreportS_IFIFOfor registered FIFOs (a FIFO has no VFS inode).unlink/unlinkatremove the name and free the pipe once idle; FIFO pipes are exempt fromis_fully_closedso they persist until unlinked.Validation
read a < <(sleep 0.5; printf X); echo [$a]→[X](was[]).brew config --no-install-from-apinow exits 0 inside Kandelo (previously failed on git-version + portable-ruby). API-mode config now proceeds past both blockers to a separate downstream curl issue.test_fifo_named_pipe_semantics(EAGAIN-before-writer, data after write, EAGAIN-when-empty-open, EOF after writer close, statS_IFIFO, unlink).FileType::Pipe; no new syscalls orrepr(C)structs;abi/snapshot.jsonunchanged.🤖 Generated with Claude Code