feat(storage): scoped grants (subtree, ro/rw), narrow-only, release revokes all holders; IPC KEEP_DELEGATE opt-in; shell cd/pwd, programs get ro cwd

This commit is contained in:
2026-10-27 20:20:34 +01:00
parent c603a578c0
commit 1fc5a54f06
38 changed files with 1237 additions and 170 deletions

View File

@@ -95,6 +95,17 @@ A program's `uses` endpoints arrive as badged copies with `Execute` and
calling, and `Delegate` lets the client send capabilities with its calls
and hand its access to programs it starts.
Storage is the exception: it mints every badged copy of its own endpoint,
and each badge names a *scope*, a subtree (or one file) and whether it may
write. For `uses: ["storage"]` init asks it for the whole disk, read-write.
Any holder can ask for a narrower one (`GRANT(path, write)`: inside its own
scope, writable only if it is) and gets it with `Delegate` kept, so it can
pass it on. Paths resolve inside the scope; `..` is refused. `RELEASE`,
called through a grant, takes it and everything granted from it back from
every holder (storage revokes its own copy, so the kernel deletes the
rest), closes their files and unmaps their buffers. Badge 0 (init) is the
whole disk; a badge storage did not issue, or took back, is `DENIED`.
Input reaches apps through the display server, the input server's only
client. It subscribes to the input server with a notification, drains it
when signalled, and passes events on to the app in focus: the last one to
@@ -107,10 +118,10 @@ is whatever text endpoint a program was given. `READ` (`types::terminal`)
blocks until a line is typed and returns it in chunks, the last ending in
`\n`. The desktop is the shell, given the terminal, storage and a spawn
allowance. A command that is not a built-in runs `name.elf`: the shell
spawns it, gives it call-only copies of the terminal (as its log, so its
output) and of storage, writes its arguments page, starts it and waits for
its death. The programs share the shell's storage badge; storage hands a
client that attaches again the same buffer, so they run one at a time.
spawns it, gives it a call-only copy of the terminal (as its log, so its
output) and a read-only storage grant to the shell's current directory
(`cd`, `pwd`), writes its arguments page, starts it, waits for its death
and releases the grant.
Only init holds `ProcessCreate`. Everyone else runs programs through init's
spawn service (`types::spawn`, client `libkos::spawn`), which init serves
@@ -159,8 +170,11 @@ A message is 4 words and at most one capability. The capability is
transferred only if the sender's endpoint capability has `Delegate` and the
transferred capability itself has `Delegate`; it is **copied** (the sender
keeps its own) with the same badge and permissions minus `Delegate`: the
receiver can use it but not pass it on. To let a process pass a capability
on, give it with the `Delegate` syscall and explicit rights. If the receiver's
receiver can use it but not pass it on. A sender that means the receiver
to pass it on sets `KEEP_DELEGATE` in the capability register
(`syscall_abi::Give`): the copy then keeps exactly the sender's rights,
`Delegate` included. Or it gives the capability with the `Delegate` syscall
and explicit rights. If the receiver's
table is full, or the capability is no longer valid at delivery time, the
message is delivered without it.