public inbox for [email protected]
 help / color / mirror / Atom feed
* [ANNOUNCE] mises v0.40.0: dup, dup2 and pipe
@ 2026-07-23 17:35 kaguya3311
  0 siblings, 0 replies; only message in thread
From: kaguya3311 @ 2026-07-23 17:35 UTC (permalink / raw)
  To: [email protected]


[-- Attachment #1.1.1: Type: text/plain, Size: 4192 bytes --]

Mises v0.40.0 is tagged. It adds the three descriptor syscalls thatredirection is made of, which finishes the kernel side of `>` and `|`.

Since v0.39 an fd has been a counted reference to an open file
description, and a child inherits its parent's whole table across
spawn. What was still missing was any way for the parent to change
what a number *means* before it spawns, and any description that is a
channel between two processes rather than a file.

  0x50 dup(fd)              -> newfd   second name, lowest free fd
  0x51 dup2(oldfd, newfd)   -> newfd   second name at a chosen fd
  0x52 pipe(fds_ptr, flags) -> 0       [read_fd, write_fd], two LE u32

These open a new 0x50..=0x5F "descriptor" band rather than spending the
file band's last three numbers. The narrow reason is that 0x10..=0x1F
had exactly three left and link(2) still wants one. The real reason is
that none of these takes a path: they operate on the fd table and on
open file descriptions, which is a different kind of object -- the same
argument the mount band and the system-control band each made. The file
band was going to overflow regardless, since lseek, fstat, fcntl,
ftruncate, ioctl and poll are all fd-shaped and path-free; those are
reserved in the new band. The ABI is append-only, so this was settled
before any code was written.

dup is literally the existing Tables::link. dup2 is the same with a
caller-chosen fd, plus a release of what it displaced -- a real close,
flush included. Two orderings in it are load-bearing: the new reference
is taken before the displaced one is dropped, and old == new returns
early having closed nothing but still validating. Get either wrong and
dup2 destroys the fd it was asked to preserve.

A pipe lives in a new kern::pipe, which is a second leaf lock that the
fd layer never nests under: retiring a pipe end wakes the scheduler, so
every close path extracts the dying description under the fd lock, drop
the guard, and only then retires it. A pipe's readers/writers count
DESCRIPTIONS, not fds, which is the whole of end-of-file -- a dup2 or a
spawn adds an fd to an existing description and moves neither count.
Buffered bytes drain before EOF is reported. A write with no reader is
EPIPE; there is no SIGPIPE to send.

Userland can now deadlock the machine, which is new. A parent that waits
for a child blocked on a pipe nobody drains is the classic pipeline
mistake and Mises now has it, for the same reason POSIX does.

Tested by user/pipetest and tests/pipe.rs. The probe re-launches itself
with a role operand so writer and reader are one binary; the children
write to fd 1 and mention no pipe, and the parent re-emits what it read
back out *framed*, so "arrived through the pipe" and "went straight to
the terminal" are two different strings -- which lets the test require
the raw form to be absent. It moves 12 KiB through a 4 KiB pipe with
both ends blocking.

Six bug-injections were run against it. Three were worth writing down:

  - A dup2(fd, fd) check on stderr proves nothing. fds 0/1/2 share one
    description, so a close-then-link dup2 drops it from three
    references to two and the fd keeps working by accident. Confirmed
    by running the injection with both forms present.

  - A leaked pipe is invisible to every behavioural assertion. One
    whose end counts are right but which is never freed reads, writes,
    blocks and reports EOF perfectly. Only a count() hook sees the page
    it pins each time.

  - The drive-loop bound had to be sized from the measured healthy cost
    (4 spins). At the neighbouring tests' 50,000,000 the deadlock
    injection became a 60-second QEMU timeout with no output; at
    200,000 it fails a named assertion in under a second.

Rationale, alternatives and invariants: ADR-0051.

`>` and `|` are now entirely user/sh's work, with no further kernel
change. There is no O_CLOEXEC yet, so a pipeline must spawn its writer
first and drain before it waits; both are documented.

Suite: 357 [ok] across 58 test binaries (60 Running lines), 0 failed,
0 warnings.


        kaguya

[-- Attachment #1.1.2.1: Type: text/html, Size: 6501 bytes --]

[-- Attachment #1.2: publickey - [email protected] - 0x09BF9D29.asc --]
[-- Type: application/pgp-keys, Size: 856 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 343 bytes --]

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-23 17:35 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 17:35 [ANNOUNCE] mises v0.40.0: dup, dup2 and pipe kaguya3311

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox