Expand description
A file as a capability, safely, from a service that serves other clients.
This module was called cap until the standard library grew general capability
operations. It never held any: it holds File and nothing else, so the name promised
a capability API and delivered a file one - which is why cap-grant and holder reached
past it into the SDK. The rights constants moved to crate::cap, where cap::READ
reads as what it is: a capability right (CLAUDE.md 7.4).
§What this is
Fs::open asks fs for a delegated resource capability (CLAUDE.md
7.10) to one file, and returns a File. The capability is real and kernel-minted: unforgeable,
revocable, and non-escalating. A READ-only File cannot write, and the refusal comes from the
kernel before fs is ever reached.
let mut fs = gs::fs::Fs::new(ctx);
let mut f = fs.open("/data/log.txt", cap::READ | cap::WRITE)?;
f.write_at(0, b"hello")?;
let mut buf = [0u8; 64];
let n = f.read_at(0, &mut buf)?;
f.close()?;§Why this could not be written until the protocol carried a tag
Using a resource capability is resource_invoke - a SEND that embeds a one-shot reply cap. The
kernel routes it to the owning service and then forgets the exchange, so the caller must wait for
the answer on its own ordinary endpoint, the same endpoint its clients send to. A task owns
exactly one endpoint; there is no second one to wait on.
That leaves a caller two bad choices, and services/shell takes the second:
- block on a plain
recvand read whatever arrives as the reply, or - drain the endpoint first, destroying any client request that was queued.
The shell gets away with draining because the shell serves nobody. A library cannot make that assumption, which is why this module did not exist for as long as the file-cap protocol carried no correlation tag.
It carries one now, matching the convention the NAMED fs protocol in the same service always
had. With a tag, a message that is not the reply is recognisable, and a caller that receives one
can hold it and hand it back instead of losing it. That is what File::take_held is for,
and it is the whole reason this is safe to hand to a service.
Nothing here is a new kernel facility. The tag is a protocol byte and the holding is a fixed array; the standard library is re-serving what the system already does, which is the only thing it is allowed to do.
§The obligation this puts on a serving caller
If your task answers clients on its endpoint, you must drain File::take_held after any
operation and feed those messages back into your own loop. They are real client requests that
arrived while you were waiting. Ignoring them loses them just as surely as draining would have -
the difference is that here you are told, and there you were not.
A program that serves nobody (most programs) can ignore all of this: nothing will ever be held.
Structs§
- File
- An open file, held as a capability.