Skip to main content

Module file

Module file 

Source
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 recv and 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.

Constants§

HELD_MAX
How many messages that are NOT our reply a File will hold before it stops taking them.
IO_CHUNK
The most bytes one invocation moves, as services/fs frames it.