diff --git a/Cargo.lock b/Cargo.lock index 648b9e6e73e..7845d6cd699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3053,6 +3053,13 @@ dependencies = [ "toyos-abi", ] +[[package]] +name = "shell-copies" +version = "0.1.0" +dependencies = [ + "toyos-abi", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index f54f94b9778..5ae48789a26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,7 @@ members = [ "system/hda/driver", "system/session", "system/shell", + "system/shell/copies", "system/soundserver", "system/soundserver/mixer", "system/soundserver/output", diff --git a/build/build.rs b/build/build.rs index de159d2ea9f..696186232ec 100644 --- a/build/build.rs +++ b/build/build.rs @@ -179,7 +179,7 @@ struct ProgramConfig { /// hands the connector down to its own children. The supervisor creates nothing and /// holds nothing for these — `surface` is the whole of this kind. provides: Vec, - /// Names in this program's namespace, each a *connector*. + /// The services this program is given, each a *connector*. receives: Vec, /// Claims the supervisor mints for this row's one process and endows /// (`toyos_manifest::Program::devices`). @@ -760,7 +760,7 @@ fn held_by_their_holders_alone(config: &SystemConfig) -> Result<(), String> { return Err(format!("`{name}` asks for `slots` and is in `[boot] start`, and the slots are granted to a launch alone")); } // A row any login could start would hand every login what it holds, - // and so would a login authority, whose namespace reaches every + // and so would a login authority, whose services reach every // program a login runs directly: an authorization that changes a // disk is the image's to grant, to a row that is neither. for word in &program.authorize { @@ -1155,7 +1155,7 @@ fn symlink_target_name(to: &str) -> &str { } /// The converse of the crate assertion above: a `bin/` entry no name reaches. -/// A name buys authority — the supervisor builds a `[programs]` row's namespace and +/// A name buys authority — the supervisor gives a `[programs]` row its services and /// device claims — and a harness binary has none, holding only what its spawner /// moved in, so it is legal exactly when the config starts something that could /// spawn it. **An inventory over the `bin/` namespace, not a reachability @@ -1183,7 +1183,7 @@ fn unnamed_program( } return Err(format!( "the image carries {name} and no `[programs]` row names it, so the supervisor can build \ - it no namespace and no device claim; add a row or take the file out" + it no service and no device claim; add a row or take the file out" )); } Ok(()) diff --git a/docs/src/architecture/authority.md b/docs/src/architecture/authority.md index 7a2bbb8a884..b505fc1da11 100644 --- a/docs/src/architecture/authority.md +++ b/docs/src/architecture/authority.md @@ -18,8 +18,8 @@ nothing. - Every system call states the right it needs on each handle it takes. - Nothing else grants authority: process and thread ids are identities, object - ids for diagnostics reach nothing, and a service name means something only - inside a namespace someone handed you. + ids for diagnostics reach nothing, and a name means something only in the + process whose parent wrote it. ## Rights only narrow @@ -37,7 +37,7 @@ ## Kernel objects - A closed set of kinds, among them pipes, connections, service ports, - namespaces, shared memory, paged memory supplied by a userland pager, events, + shared memory, paged memory supplied by a userland pager, events, inboxes for asynchronous requests, processes, jobs, device claims and the system capability. - An object crosses a process boundary in exactly two ways: in a message over a @@ -61,9 +61,13 @@ ## Creating a process -- A child starts by spawn, never by fork, with an empty handle table. -- It receives exactly one set of endowments, moved out of the parent, all or - nothing (see [The program interface](../using/interface.md)). +- A child starts by spawn, never by fork. Its table starts with its handle to + itself, its notice, then exactly one list of endowments moved out of the + parent, all or nothing, in the order the spawn listed them + (see [The program interface](../using/interface.md)). +- The kernel keeps no name for any handle. What a parent calls each endowment + is bytes it writes into the child's memory, and the child's SDK checks them + once before it looks one up. - The parent receives a handle to the child's process, with which it waits for the child's end and can stop it. - Without any handle a process can act only on itself and read machine facts: @@ -91,17 +95,27 @@ ## The system capability -- One per machine, held by the supervisor. +- One per machine, held by the supervisor, which the kernel starts holding it + and the images it boots from at handles the interface states, since no + parent names them. - Every privileged right, such as claiming a device, real-time scheduling, reading the log, reading counters, listing processes or powering off, is a narrowed copy the supervisor hands to the programs its manifest grants. -## Namespaces - -- A namespace is an immutable kernel object mapping service names to - connectors; a narrower one is a new object. -- A process connects to a service only by looking its name up in a namespace it - holds. There is no global registry. +## Services + +- A service is a port. Its server holds the acceptor; a client holds a + connector and connects through it, which takes the right to write on the + connector. A service one program provides to a launch, such as a terminal's + surface, reaches the launched program only where its row receives that + service. +- A process finds a service only among the connectors its parent gave it, + by the name its parent wrote. There is no global registry and no connect by + name. +- A helper a program spawns is given a copy of every service connector its + parent holds, unless the spawn names fewer; a launch is given its row's. + What a process gives itself, as the supervisor does for its own files, no + child inherits. ## Files diff --git a/docs/src/architecture/kernel.md b/docs/src/architecture/kernel.md index 4a0093a7016..6ef8ffd1e63 100644 --- a/docs/src/architecture/kernel.md +++ b/docs/src/architecture/kernel.md @@ -16,9 +16,10 @@ The kernel is small, fast and safe, and does what userland cannot: It holds no filesystem, no storage, input, network, audio or USB driver, no network protocol, no audio mixing, no login and no naming policy, and parses nothing one program writes for another. What a parent tells the child it -starts, its arguments and environment, is memory the parent lays out in the -child: the kernel keeps none of it. It knows a process by a short name its -parent gives it, a label that carries no authority. The firmware's AML is +starts, its arguments, its environment and what it calls each handle it +gives, is memory the parent lays out in the child: the kernel keeps none of +it. It knows a process by a short name its parent gives it, a label that +carries no authority. The firmware's AML is interpreted by a userland ACPI service, which tells the kernel the little it needs, such as how to power off. diff --git a/docs/src/using/interface.md b/docs/src/using/interface.md index b02fd1f5b1e..8eb9122913d 100644 --- a/docs/src/using/interface.md +++ b/docs/src/using/interface.md @@ -36,7 +36,7 @@ boundary: protocols between programs define their own. ## The native SDK The SDK is the Rust interface to ToyOS: typed handles and rights, endowments a -program was started with, namespaces and service connections, framed messages, +program was started with and what its parent calls each, service connections, framed messages, pollers, shared memory, files through the program's view, spawning and launching, device claims for drivers, logging, and windows for programs that draw. Each service's protocol is a library of its own that owns its message @@ -59,21 +59,27 @@ userland library over the SDK. ## How a program starts ```text - endowments ──> standard streams (descriptors 0, 1, 2 and those named) - ──> a namespace of services it may connect to - ──> directory capabilities: its view - ──> anything else its row or its parent gave it + handles ──> itself and its notice, first + ──> its endowments, in the order its spawn listed them: + standard streams (descriptors 0, 1, 2 and those named), + connectors to the services it may connect to, + directory capabilities: its view, + anything else its row or its parent gave it its memory ──> its program, in the ranges its parent laid out - ──> a startup block below it: its arguments and its - environment (HOME, the starting directory, ...) + ──> a startup block below it: what its parent calls each + endowment, its arguments and its environment + (HOME, the starting directory, ...) ``` A parent reads the child's program and lays out the child's memory, each range the pages of a memory object or a private copy of them. What it tells the child goes in the startup block, which the kernel copies and never reads; the SDK, std and libc read it where it lies. A child whose parent wrote none -starts with no argument and an empty environment. +starts with no argument, an empty environment and no name for anything it +holds. The names are the one part of the block the child checks: one that is +no list of distinct names ends the child before it looks one up, with the +reason on its standard error where the list still names one. A program holds nothing it was not given. It asks for more only by connecting -to a service in its namespace, whose server decides. +to a service it was given, whose server decides. diff --git a/docs/src/using/manifest.md b/docs/src/using/manifest.md index 3e5d21f0db5..249dc805d94 100644 --- a/docs/src/using/manifest.md +++ b/docs/src/using/manifest.md @@ -6,7 +6,7 @@ starting authority from it and from nothing else, and the build refuses a manifest that contradicts itself, such as two programs claiming one device. ```text - manifest row ──> supervisor ──> spawn with: namespace (services it receives) + manifest row ──> supervisor ──> spawn with: connectors (services it receives) acceptors (services it serves) view (directories) device claims diff --git a/issues/a-c-shell-cannot-tell-a-child-its-quit-asked-from-one-that-landed-after.md b/issues/a-c-shell-cannot-tell-a-child-its-quit-asked-from-one-that-landed-after.md new file mode 100644 index 00000000000..1b0db6970a6 --- /dev/null +++ b/issues/a-c-shell-cannot-tell-a-child-its-quit-asked-from-one-that-landed-after.md @@ -0,0 +1,25 @@ +--- +status: open +kind: defect +opened: 2026-11-11 +--- + +# A `-c` shell cannot tell a child its quit asked from one that landed after it + +A quit of a `-c` shell asks or ends the children there. A child that lands +after it holds the reason unread, or none if the shell had read it, and that +quit does not ask it (`kernel/proclife/src/quit.rs`). So the shell asks each +child it starts once an interrupt has reached it, right after that child's +spawn (`system/shell/src/quit.rs`, `Asked::landed`). + +When the interrupt and the landing both fall within that one spawn, no step +of the shell's tells which came first. A child that listens between its +landing and the shell's look, and has already been asked by the walk or read +the inherited reason, is asked a second time. It is ended if it has not read +the first. `shell-copies`'s model shows exactly those orders. A child that +never listens, such as `cat`, is not affected: it ends in every order. + +Exit: the kernel either tells the spawner whether the child landed holding a +reason its parent had not read, so the shell asks only a child nothing asked, +or asks a child that lands under an asked parent itself. Either changes the +quit's rule, which is the kernel's. diff --git a/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md b/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md index a3ea7e3a67d..b04bbdf2a09 100644 --- a/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md +++ b/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md @@ -136,7 +136,7 @@ a signal an event raises (`sdk/libc/src/sigreq.rs`). session's `logout` acts on every session. An owner's end other than its sign-out's is a failure, and a login authority whose sessions failed more than the restart budget's three times in ten seconds is refused its next - open; no other open is counted. Its namespace is + open; no other open is counted. What it holds is `issues/a-user-is-a-home-tree-and-a-login-row.md`'s login row; the SSH session's shell holds no host key and no SSH service authority. *Exit*: ending the desktop's session owner ends every program of that @@ -173,7 +173,7 @@ a signal an event raises (`sdk/libc/src/sigreq.rs`). as the kill does, and the reason is `toyos_abi::quit::Reason`: interrupt, hang-up or terminate. A quit reaches the process's subtree. Each process is started holding its notice, the object kind `Notice` - under `QUIT_LABEL` (`quit`), `READ` and `WAIT` alone, readable while a + at `RawHandle::NOTICE`, `READ` and `WAIT` alone, readable while a reason is asked and not yet read, whose read takes it and an empty read takes nothing. A process listens from its first read of its notice or the first readable watch armed on it. A quit kills a process that never diff --git a/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md b/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md index be28dada464..ee4854a74fe 100644 --- a/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md +++ b/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md @@ -11,7 +11,7 @@ opened: 2026-10-20 (`sys_device_dma_map`, `kernel/src/syscall/device.rs`), so a handle of another kind is `HandleError::WrongType`, which ends the caller, exit 139 (`process::handle_fault`). `HandleTable::get_sent` answers `InvalidArgument` -for it instead, and is what `SYS_SPAWN` and `SYS_NAMESPACE_BUILD` take a +for it instead, and is what `SYS_SPAWN` and `SYS_CONNECT` take a handle a peer sent with (`kernel/src/object/handle.rs`). The region a holder lends is a peer's by contract: `toyos_pci_claim::lend` maps @@ -27,11 +27,11 @@ as a region before the session lends it (`Sessions::open`, The kernel owns the fix rather than each lender: what a holder lends is a peer's by `lend`'s contract, and the kernel already answers a peer's handle of -the wrong kind `InvalidArgument` at `SYS_SPAWN` and `SYS_NAMESPACE_BUILD`; a +the wrong kind `InvalidArgument` at `SYS_SPAWN` and `SYS_CONNECT`; a check before every lend would repeat that in each driver. Owner: the kernel's device calls (`kernel/src/syscall/device.rs`). **Exit**: `SYS_DEVICE_DMA_MAP` takes its region through `get_sent`, and `HandleError::WrongType`'s comment names it beside `SYS_SPAWN` and -`SYS_NAMESPACE_BUILD`. +`SYS_CONNECT`. diff --git a/issues/a-file-server-whose-pager-is-refused-for-memory-panics-blaming-its-row.md b/issues/a-file-server-whose-pager-is-refused-for-memory-panics-blaming-its-row.md new file mode 100644 index 00000000000..2261a04f803 --- /dev/null +++ b/issues/a-file-server-whose-pager-is-refused-for-memory-panics-blaming-its-row.md @@ -0,0 +1,34 @@ +--- +status: open +kind: defect +opened: 2026-11-11 +--- + +# A file server whose pager is refused for memory panics, blaming its row + +`pager` (`system/fileserver/src/main.rs`) panics whenever +`toyos::paged::Pager::create` is refused, saying "its row is no pager row". +Under memory exhaustion the refusal is `ResourceExhausted`. That is an +expected condition, not a manifest error, so the message is false, and the +server ends instead of reporting it. + +Seen in guest run 43092179151 (#1252 at 041f1b81e), `kernel_objects_refused_alone`. +While the test held every frame, the Log file server (pid 9) ended. Each of +the supervisor's next three restarts went the same way: + +- it said `fileserver: diskserver would not open the partition: Kernel(ResourceExhausted)`; +- it said `fileserver: Log serving /log — absent`; +- it panicked at `main.rs:290` with + `fileserver: the Log server makes no pager (ResourceExhausted); its row is no pager row`. + +The supervisor then closed the role for the rest of the boot: +`supervisor: fileserver log ended 4 times in 10 s; its ports are closed, its +clients answered Gone, and a program started from now on is given none of +its directories`. Main has the same code (e10b6fd49). + +Owner: the file server, with the supervisor's restart budget. + +Exit: a refused pager is no panic. The file server reports what the kernel +answered, and a role refused its pager for memory ends with a status that the +supervisor reads as want of memory, as it reads a start refused for memory. +A host test of the refusal holds this. diff --git a/issues/a-namespace-is-a-kernel-object-and-service-lookup-is-kept-out-of-the-kernel.md b/issues/a-namespace-is-a-kernel-object-and-service-lookup-is-kept-out-of-the-kernel.md deleted file mode 100644 index c82c3e4441b..00000000000 --- a/issues/a-namespace-is-a-kernel-object-and-service-lookup-is-kept-out-of-the-kernel.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -status: open -kind: finding -opened: 2026-11-11 ---- - -# A namespace is a kernel object, and service lookup is kept out of the kernel - -An open design question, the orchestrator's to resolve. Found by the -inventory in -`issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`. - -The kernel holds a table of service names to connectors per namespace -(`kernel/src/object/namespace.rs`) and three calls over it, -`SYS_NAMESPACE_BUILD`, `SYS_NAMESPACE_OPEN` and `SYS_NAMESPACE_NAMES` -(`kernel/src/syscall/ipc.rs`), about 215 lines: it copies name text of up -to 64 KiB (`SYS_NAMESPACE_OPEN`, through `user_str`), compares it and lists -it. The design keeps a namespace as an immutable kernel object and, in its -statement of what stays out of the kernel, names service lookup. Both -cannot be the whole truth of this code. - -The two readings: the object is the mechanism and "lookup" means a global -registry, which the kernel has none of, so nothing moves; or name text is -userland's, a process is endowed connectors and its parent's library maps -names to them, and the kernel's table and three calls go. The second is an -inference, not a design anyone wrote. - -**Exit**: one reading is chosen and the design says it in one place; under -the first this file is deleted, under the second it becomes a stage of the -track. diff --git a/issues/desktop-window-child-freeze.md b/issues/desktop-window-child-freeze.md index 6531b524eff..2a6b2874b7c 100644 --- a/issues/desktop-window-child-freeze.md +++ b/issues/desktop-window-child-freeze.md @@ -154,7 +154,7 @@ manifest does not carry with nothing. **The endowment travels with the spawn, and that closed it.** `tests/desktopcase/system.toml`'s `[programs.shell]` receives `compositor`, and -a child the shell spawns directly inherits the shell's namespace — so the client +a child the shell spawns directly inherits the shell's services — so the client gets its window from the process that started it, which is the whole of "a process holds exactly what its parent moved into it". Every probe this test was written for now runs. diff --git a/issues/every-job-test-runner-starts-holds-its-whole-system-capability.md b/issues/every-job-test-runner-starts-holds-its-whole-system-capability.md index cd1415b3273..c8a90563ee0 100644 --- a/issues/every-job-test-runner-starts-holds-its-whole-system-capability.md +++ b/issues/every-job-test-runner-starts-holds-its-whole-system-capability.md @@ -9,7 +9,7 @@ opened: 2026-09-29 `run_one` endows each job it spawns a `SysCap::duplicate` of test-runner's own capability (`tests/guest/test-runner/src/main.rs`), and a duplicate carries every right the original does (`sdk/toyos/src/syscap.rs`). The job also -inherits test-runner's whole namespace. On `tests/images/testcases.toml` the +inherits every service test-runner holds. On `tests/images/testcases.toml` the row grants `device`, `dup`, `logread`, `power`, `roster`, `counters`, `trace` and `inventory`, so every test binary may mint a device claim, read every kernel record, reset or power off the machine, list every process, and hand diff --git a/issues/every-program-sees-only-the-files-it-was-given.md b/issues/every-program-sees-only-the-files-it-was-given.md index 47a3eb0bbb4..425da63745d 100644 --- a/issues/every-program-sees-only-the-files-it-was-given.md +++ b/issues/every-program-sees-only-the-files-it-was-given.md @@ -67,9 +67,8 @@ other, with the same per-session isolation (owner, 2026-11-05). directory where it keeps state (`service = true` or `state = true`: the daemons' own state is built); `/system` is read-only, `/log` and `/apps` are read-only to all but one row each, and the rest is read-write. The - storage rows are started outside that - (`issues/storage-rows-start-holding-the-supervisors-whole-tree.md`), - which this stage's exit closes. + storage rows hold no directory at all (#1252): a service the + supervisor adopted for its own files is inherited by no child. Only the installer service writes `/apps` (owner ruling, 2026-11-05). Built: `/apps` is read-only in every declared row's view but the row that declares `installs = true` (`toyos_manifest::Program::installs`), diff --git a/issues/kernel-objects-refused-takes-every-frame-while-its-own-line-may-still-be-written.md b/issues/kernel-objects-refused-takes-every-frame-while-its-own-line-may-still-be-written.md new file mode 100644 index 00000000000..555dbf95c02 --- /dev/null +++ b/issues/kernel-objects-refused-takes-every-frame-while-its-own-line-may-still-be-written.md @@ -0,0 +1,38 @@ +--- +status: open +kind: defect +opened: 2026-11-11 +--- + +# `kernel_objects_refused` takes every frame while its own line may still be written + +`tests/guest/rust-tests/src/bin/kernel_objects_refused.rs` promises: "No +filesystem service is asked to allocate while the machine has no room". But +it prints `taking every frame a process may` and commits every frame at +once. logkeeper carries that line to the Log file server, which may still be +writing it when the test holds every frame. If a process ends inside that +window, its memory comes back, and the test's later refusals are judged +against memory it did not take. + +Seen in guest run 43092179151 (#1252 at 041f1b81e): +- `kernel_objects_refused_alone` failed `a pipe's first write, its frame with + every frame taken` (`Ok(())`, expected `ResourceExhausted`). +- The pipe, port, event and null object before it were refused. +- In the same window the Log file server (pid 9) ended, and + `logkeeper: /log has not answered (the file server is gone)` followed. +- pid 9 left no panic line in the job's capture. The serial artifact did not + upload, so the kernel's account of its end is lost. +- So the mechanism is inferred, not established: a write the test caused, + refused for want of a frame, ended the server. + +The row passed at 2bbd17005 and 8f5cfaf65 of the same branch. Its result +depends on whether logkeeper's write lands before the commit, which the test +leaves to timing. The control, the same row at main e10b6fd49 in run +43102568233, passed. One green run cannot tell an intermittent race on main +from a defect of #1252: the case for the race rests on reading the test. + +Owner: the guest test. + +Exit: the test commits only once nothing it caused is still being written, +or it tells a premise broken by another process's end apart from a refusal +it failed to see. A red then names the cause, not the pipe's write. diff --git a/issues/nothing-holds-that-a-child-spawned-directly-holds-no-launcher.md b/issues/nothing-holds-that-a-child-spawned-directly-holds-no-launcher.md index f37b5d8d250..6583c0b362c 100644 --- a/issues/nothing-holds-that-a-child-spawned-directly-holds-no-launcher.md +++ b/issues/nothing-holds-that-a-child-spawned-directly-holds-no-launcher.md @@ -9,13 +9,13 @@ opened: 2026-10-12 The `launch_authority` metal row, deleted by the owner's ruling on pull request #889, ended by spawning its own binary, which no row declares, and asserting the child held no launcher: none under the `launcher` label, and -none in the `svc` namespace it inherits. No test at any tier asserts that +none among the services it inherits. No test at any tier asserts that now. This file owes std's half of it: a direct spawn endows the child a copy of -the caller's `svc` and only what the caller endowed, never the caller's +the caller's services and only what the caller endowed, never the caller's launcher (`sdk/std/sys/process.rs`). The supervisor's half, a row's launcher -endowed under `launcher` and never in `svc`, is owed by stage 2 of +endowed under `launcher` and never as a service, is owed by stage 2 of `issues/the-supervisor-is-host-tested-and-owns-the-stop.md`. **Owner**: the orchestrator. diff --git a/issues/one-manifest-row-grants-every-applet-and-one-is-compared.md b/issues/one-manifest-row-grants-every-applet-and-one-is-compared.md index 6359e2d0f64..da9eea42557 100644 --- a/issues/one-manifest-row-grants-every-applet-and-one-is-compared.md +++ b/issues/one-manifest-row-grants-every-applet-and-one-is-compared.md @@ -18,9 +18,8 @@ endowed a duplicate with `Rights::ROSTER` and a duplicate without it, and its ow comment says what that buys: "the manifest's name, the kernel's demand and the program are one line rather than three that agree by luck". That is one allowed and one forbidden operation for one authority class on one of them. The -effective-authority side is a shipped syscall too — `SYS_ENDOWMENTS` -(`sdk/abi/src/syscall.rs:173`, wrapper at `:849`) lets a process read its own -endowment table back. +effective-authority side is readable too: a process reads what its parent +calls each endowment off its own startup block (`toyos::endow::Endowments`). **The enumeration exists now, and it reds on the image that boots.** `endowment_denied`'s `every_applet_holds_only_what_its_policy_names` reads the diff --git a/issues/one-program-that-fills-a-servers-table-holds-every-other-client-out.md b/issues/one-program-that-fills-a-servers-table-holds-every-other-client-out.md index 0ff499097b4..bd8c528e21d 100644 --- a/issues/one-program-that-fills-a-servers-table-holds-every-other-client-out.md +++ b/issues/one-program-that-fills-a-servers-table-holds-every-other-client-out.md @@ -42,7 +42,7 @@ names the holder: The supervisor mints the connector of every server it spawns, but hands out one unbadged connector per port to every row that receives it -(`build_namespace`); console and terminal each create their own surface port +(`received`); console and terminal each create their own surface port and move its connector only into the shell they spawn. A program that holds several sessions, or that launches other rows, holds as many keys: `issues/four-shares-take-a-file-server.md`. diff --git a/issues/storage-rows-start-holding-the-supervisors-whole-tree.md b/issues/storage-rows-start-holding-the-supervisors-whole-tree.md deleted file mode 100644 index f70b24beb81..00000000000 --- a/issues/storage-rows-start-holding-the-supervisors-whole-tree.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-11-11 ---- - -# Storage rows start holding the supervisor's whole-tree namespace - -By inspection, and unverified on a machine: `diskserver` and the file servers -for the `memory` and `system` roles are started holding a duplicate of the -supervisor's own namespace, every directory of the tree read-write but -`/system`; `/apps` and `/log` are among them. - -The supervisor gives a storage row no view (`Grants::view` in -`system/supervisor/src/main.rs`), since a file server resolving a path -through itself waits for ever. Where such a row has no other name either, -`build_namespace` answers no namespace at all, and a spawn that names none -endows a duplicate of the spawner's own (`Helper::spawn`, -`sdk/toyos/src/spawn.rs`). The supervisor's own is the whole tree -(`toyos_manifest::whole_tree`). - -All three are the signed image's storage stack and open no path, so no -boundary between a session's programs and `/apps` or `/log` depends on it. -But "a storage row sees none" is not what happens, and no other row is -started this way. - -Evidence owed: `names.open("fs:/apps")` from one of those processes, which -answers a connection if this is right; if not, the process holds no -namespace, or one without `fs:/apps`. - -Exit: a storage row is started with an empty namespace where its row names -nothing, the check above refused on a machine, as part of stage 2 of -`issues/every-program-sees-only-the-files-it-was-given.md`, whose exit is -that nothing still sees the global tree. - -Owner: the file-isolation track. diff --git a/issues/the-kernel-heaps-fragmentation-is-charged-to-nobody.md b/issues/the-kernel-heaps-fragmentation-is-charged-to-nobody.md index 11da3dec8af..02b0bbbc08c 100644 --- a/issues/the-kernel-heaps-fragmentation-is-charged-to-nobody.md +++ b/issues/the-kernel-heaps-fragmentation-is-charged-to-nobody.md @@ -15,8 +15,8 @@ size. So a process that keeps its charged total constant while it changes the sizes of what it holds forces the heap to grow with no new charge: - with every frame taken, it frees every other small name it made on `/tmp` - and makes larger ones in their place, or does the same with port badges or - namespaces, whose sizes it chooses; + and makes larger ones in their place, or does the same with port badges, + whose sizes it chooses; - that growth takes a spare superframe while one is left, then a kept one; - past the kept superframes, `alloc` answers null and the kernel panics, naming the allocation. diff --git a/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md b/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md index 7fe5935eb4d..f5e9a970048 100644 --- a/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md +++ b/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md @@ -173,14 +173,15 @@ Beside that order, each waiting on something outside it: that `PWD` among it, are a startup block its parent lays out below its program (`toyos::startup`), which the kernel neither stores nor answers (#1200): `SYS_GET_ENV` (73) and `ProcessData::env` are - gone. The kernel still keeps three userland texts of each process: - its name, 27 bytes its parent chooses (`SpawnArgs::name_ptr`), which - is what a bounded process name needs; the path its parent read the - program from (`SpawnArgs::path_ptr`), which the spawn's record and a - crash's frames name the file by and `symbolize` opens; and its - endowment labels, kept for its life and answered through - `SYS_ENDOWMENTS` as the environment was, the next candidate for the - block. `/tmp` is the + gone. What a parent calls each endowment is in the block too + (#1252): `SYS_ENDOWMENTS` (99) and the kernel's namespaces are + gone, and a child's handles follow its own two in the order its + spawn listed them. The kernel still keeps two userland texts of each + process: its name, 27 bytes its parent chooses + (`SpawnArgs::name_ptr`), which is what a bounded process name needs; + and the path its parent read the program from + (`SpawnArgs::path_ptr`), which the spawn's record and a crash's + frames name the file by and `symbolize` opens. `/tmp` is the MEMORY file server's, one for the machine, of which each installed package's `/tmp` is its own directory (#1181); per session, as the owner ruled ("Per session", 2026-10-03), is the isolation track's stage 3. @@ -384,7 +385,7 @@ the first. | What | Size | Dependents | Owner in the design, and what blocks it | |---|---|---|---| | **Done** (#1200): the parent lays them out in a startup block. A child's arguments and environment | about 140 | std, libc, `toyos::startup` | the parent's startup block | -| Endowment labels: a blob per process for its life, answered by `SYS_ENDOWMENTS`, of which the kernel compares three (`JOB_LABEL`, `SELF_LABEL`, `QUIT_LABEL`) and writes three more for the supervisor | ~130 | `toyos::endow`, the supervisor's table | the startup block (inferred); needs the child's handle numbers known to its parent and the three compared labels as typed spawn fields; after #1200 | +| **Done** (#1252): the parent names them in the startup block, the child's handles follow its own two in order, and a job is a spawn's field. Endowment labels, and the namespaces with them | about 420 | `toyos::endow`, `toyos::spawn`, the supervisor | the parent's startup block | | The TTY mark on a pipe end (`SYS_MARK_TTY`, `FileType::Tty`) | ~40 | std's spawn, libc's `isatty` and stdio, std's stdio | a fact about a stream that only libc and std read; a label or the block could carry it (inferred); after the labels | | The main thread's stack: 8 MiB and a guard the kernel chooses and reserves | ~30 | `SYS_STACK_INFO`, which only a guest test calls | its creator's, since the design gives a thread's stack a caller-chosen size; a child's stack starts empty since #1200 | | **Done** (#1210): the loader writes one record, the kernel hands it to the supervisor as a read-only boot image, and the supervisor parses it. What the loader named: partition GUIDs by role, in the inventory, and the boot sources `SYS_BOOT_SOURCES` answers, whose device paths `loaded.rs` parses (`toyos-devicepath`) only to refuse the loader's own bytes | ~95, and the kernel's link to a 324-line parser | the supervisor, `toyos::syscap`, the manifest's inventory reader | the design gives the kernel a typed description of what was loaded and keeps a partition GUID out of it; passing the description on unread, as a read-only boot image as ROOT is, fits both (inferred) and waits on no other change | @@ -398,8 +399,6 @@ Changes of the design, each inferred and none of them work until resolved: design reports a killed program by file, offset and build-id, so removing the path changes that. It needs a frame named by build-id alone and something in userland that maps a build-id to a file; neither is built. -- **Namespaces**: - `issues/a-namespace-is-a-kernel-object-and-service-lookup-is-kept-out-of-the-kernel.md`. ### The next removals, ranked (a proposal) @@ -408,7 +407,29 @@ By mechanisms eliminated: 1. #1200: `SYS_GET_ENV`, the environment kept per process, the kernel's write onto a child's stack. **Done** (#1200). 2. Endowment labels, after #1200: `SYS_ENDOWMENTS`, the per-process blob - and its charge, and label text the kernel compares. + and its charge, and label text the kernel compares. **Done** (#1252), + with the namespaces. + - **Owed on metal:** the `endow-a` boots, `testcases`, `shared-debug` + and `proctreecase`, on the T14 and the AMD bench. Main must show: + - every job green, `endowment_denied`, `abuse_handle_table` (97 + endowments refused, 96 start), `abuse_connect_flood` (a refused + connect leaves the caller's table as it was), `abuse_listener_hijack`, + `user_copy_faults`, `adopted_services_stay`, `handle_lifetime`, + `process_tree` and `launch_toctou` among them; + - the `testcases` members #1252 adapted, which have only been compiled + in that form, passing: `poll_belongs_to_its_handle` (#1261's, which + connects through its connector), `messages_are_whole`, `moved_rights` + and `mutual_kill`. The shared-boot guest row #1271 adds runs them + too, once it lands; + - no kernel panic from the slot-order assertions; + - `spawn_cost` within its guard. + - **Owed on metal:** the spawn A/B of main before #1252 against main + after, in one session on the T14 and the AMD bench. The rows are + `spawn_cost` and osbench's spawn rows (`system`'s process spawn, the + `procs` group). Main after must show no spawn slower beyond run-to-run + spread; a slower one is answered by measuring a spawn entry that + copies rather than moves. osbench's IPC rendezvous row is not + comparable across the change. 3. What the loader named, independent of the others: `SYS_BOOT_SOURCES`, the inventory's partition records, the device-path parser. **Done** (#1210). @@ -427,12 +448,6 @@ out, and a larger number needs one of the design changes above. - `SYS_STACK_INFO` is called by `tests/guest/rust-tests/src/bin/std_threads.rs` alone, and `SYS_VM_DECOMMIT` by two guest tests alone (`mmap_stress`, `starveling`). -- `KernelStack::heap` (`kernel/src/loader/start.rs`) charges every thread - two strings at 64 KiB and a page, "as a rename's paths". No rename is - left: a spawn's path and arguments were the two strings. Since #1200 - no call copies two, and one string of that bound remains for as long as - a spawn's path or `SYS_NAMESPACE_OPEN`'s name, which nothing bounds below - 64 KiB, is copied; the page is the namespace names'. - `FileType::File`, `Directory` and `Symlink` are answered by no kernel path: the file protocol's clients (`sdk/toyos/src/files/owner.rs`, `sdk/libc/src/posix_io.rs`, `sdk/std/sys/fs.rs`) reuse the kernel ABI's diff --git a/issues/the-scheduler-and-the-clocks-never-talk.md b/issues/the-scheduler-and-the-clocks-never-talk.md index 0106ae98ef9..1cc100d0495 100644 --- a/issues/the-scheduler-and-the-clocks-never-talk.md +++ b/issues/the-scheduler-and-the-clocks-never-talk.md @@ -74,10 +74,10 @@ Stage 3 is in the kernel (`kernel/pure/sched/queue.rs`, `kernel/pure/sched/job.r - **sshserver sessions.** `sshserver` starts every session as one of its own helpers, so all remote logins share its job: it has no ToyOS dependency through which to make one. Exit: sshserver holds `jobs` and starts each session in a job - of its own, through std's endowment of `JOB_LABEL` or the std/libc path + of its own, through std's `CommandExt::job` or the std/libc path below. - **A new job from std and libc.** Their spawn reaches a new job only through - `CommandExt::endow(JOB_LABEL, …)`, and libc has no such call. This waits + `CommandExt::job`, and libc has no such call. This waits for the fd-table work that rewrites spawn. Exit: std and libc each take a job for a child through their ordinary spawn interface, which is then the route the bullet above uses. diff --git a/issues/the-supervisor-is-host-tested-and-owns-the-stop.md b/issues/the-supervisor-is-host-tested-and-owns-the-stop.md index dcba3a744c8..85bd8e605d8 100644 --- a/issues/the-supervisor-is-host-tested-and-owns-the-stop.md +++ b/issues/the-supervisor-is-host-tested-and-owns-the-stop.md @@ -54,7 +54,7 @@ nothing. restart is owed, the function a `devices` matcher names and which functions a `drives` row has a process on. The supervisor's decisions live in `toyos-supervisor`, with host tests; `system/supervisor` keeps only - handles, spawns and the loop. The decisions: namespace and claim selection + handles, spawns and the loop. The decisions: service and claim selection from a manifest row; the claims a restart is owed; `SysCap` narrowing; launch resolution and every launcher refusal; the swap ladder and probation; restart policy; the stop order derived from the manifest, with a @@ -63,8 +63,8 @@ nothing. `cargo test --workspace --exclude toyos-build`; a host test refuses a manifest whose dependencies form an undeclared cycle; each refusal and each decision above has a mutation that reds a host test, named in the PR, - namespace selection's among them: a row's launcher endowed as an entry of - `svc` (`launcher_namespace`), which a child its holder spawns directly + service selection's among them: a row's launcher endowed as a service + (`svc:`), which a child its holder spawns directly would inherit, and no such child holding one is what the `launch_authority` metal row, deleted in #889, last asserted; and the stage deletes each decision from `system/supervisor`, which calls diff --git a/kernel/proclife/Cargo.toml b/kernel/proclife/Cargo.toml index 29367e5c091..4c6d9e352dd 100644 --- a/kernel/proclife/Cargo.toml +++ b/kernel/proclife/Cargo.toml @@ -76,7 +76,7 @@ mutate-walk-in-one-hold = [] # The model's spawn mints its caller's handle to the child once the spawn has # landed and its retires are posted, as a kernel that installed it after # `loader::spawn` returned would: a child claimed as it lands closes its table, -# its own `self` with it, before that. +# its handle to itself with it, before that. # `interleave::tests::a_spawn_racing_its_places_kill_leaves_nothing_under_it_and_publishes_it` # must red under this. mutate-spawner-handle-after-the-landing = [] diff --git a/kernel/pure/sched/cpu.rs b/kernel/pure/sched/cpu.rs index bcea129385a..cbdc68387da 100644 --- a/kernel/pure/sched/cpu.rs +++ b/kernel/pure/sched/cpu.rs @@ -1367,7 +1367,7 @@ fn home_of(state: TaskState) -> Option { /// where the budget sits: 2% of a quantum, and an order of magnitude above any /// pass that is doing scheduling rather than work. /// -/// It is a *policy* number, like `MAX_USER_STR` and `MAX_HANDLES`: nothing in the +/// It is a *policy* number, like `MAX_HANDLES`: nothing in the /// design forces 200 µs. If a measurement crosses it on honest work, the honest /// response is to find out which pass grew and why — not to raise it. /// diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs index 9ebd96877c0..7fe14170cc7 100644 --- a/kernel/src/loader/mod.rs +++ b/kernel/src/loader/mod.rs @@ -32,12 +32,12 @@ use alloc::vec::Vec; use crate::object::{ops, HandleTable, KObjectRef}; use crate::mm::space::CopySource; use crate::process::{ - Admission, Endowments, Parent, Pid, ProcessAccounting, ProcessData, + Admission, Parent, Pid, ProcessAccounting, ProcessData, ProcessEntry, ThreadData, ThreadEntry, UserImage, PROCESS_TABLE, }; use crate::sync::Lock; use crate::{scheduler, UserAddr}; -use toyos_abi::handle::Rights; +use toyos_abi::handle::{RawHandle, Rights}; use crate::object::shm::SharedMemObject; use toyos_abi::syscall::{SpawnBacking, SpawnMap, SyscallError}; use toyos_symbols::frame::BuildId; @@ -212,14 +212,13 @@ pub fn spawn( let notice = crate::object::notice::NoticeObject::new().map_err(SyscallError::from)?; // The point of no return: every failure above answers the caller with its // table untouched. - let (handles, endowments, held) = commit.commit(start::Own::new(&object, ¬ice))?; + let (handles, held) = commit.commit(start::Own::new(&object, ¬ice))?; let handles = crate::process::Handles::new(handles, pager); let proc_data = Arc::new(Lock::new(ProcessData { handles: Arc::clone(&handles), space: Arc::clone(&child_pt), spawn_ns: crate::clock::nanos_since_boot(), accounting: ProcessAccounting::default(), - endowments, job: job.clone(), _charge: charge, })); @@ -282,17 +281,15 @@ pub fn spawn( const SUPERVISOR: &str = "supervisor"; /// Start the supervisor from its boot image as the build laid it out, holding the machine's one -/// full-rights `SysCap`, the file server's image, ROOT's and the loader's boot names -/// (`toyos_abi::syscall::FILESERVER_IMAGE_LABEL`, `ROOT_IMAGE_LABEL`, `BOOT_NAMES_LABEL`). +/// full-rights `SysCap`, the file server's image, ROOT's and the loader's boot names, at the handles +/// `toyos_abi::boot::supervisor` states. /// /// Nothing else can construct one: what the supervisor endows is the entire set of /// processes that can ever claim a device, enter the RT band, or power off. /// Panics on failure: a boot that cannot start the supervisor has nowhere to report to. pub fn spawn_supervisor(images: crate::bootimage::Objects, laid: &toyos_kimage::Supervisor<'_>) -> Pid { + use toyos_abi::boot::supervisor as at; let job = crate::object::job::new_job(); - let mut handles = HandleTable::new(&job); - let mut entries = Vec::new(); - let mut labels = Vec::new(); let cap = KObjectRef::SysCap(crate::object::syscap::SysCap::new().expect("spawn_supervisor: no heap for the system capability")); // Every machine-wide authority the system has: rights only shrink from // here, so a bit absent here is a bit no manifest can ever name. LOG and @@ -315,36 +312,18 @@ pub fn spawn_supervisor(images: crate::bootimage::Objects, laid: &toyos_kimage:: .union(Rights::PAGER) .union(Rights::CONSOLE) .union(Rights::LAST_WORDS); - let cap_handle = handles - .install(crate::object::HandleEntry::new(cap, rights)) - .expect("spawn_supervisor: an empty table refused the system capability"); - let label = toyos_abi::syscall::SYSCAP_LABEL; - entries.push(toyos_abi::syscall::EndowEntry { - label_off: labels.len() as u32, - label_len: label.len() as u32, - handle: cap_handle, - _pad: 0, - }); - labels.extend_from_slice(label.as_bytes()); - // The two images go on to the file server; the names are the supervisor's to read. - for (label, object, keeps) in [ - (toyos_abi::syscall::FILESERVER_IMAGE_LABEL, &images.fileserver, None), - (toyos_abi::syscall::ROOT_IMAGE_LABEL, &images.root, None), - (toyos_abi::syscall::BOOT_NAMES_LABEL, &images.names, Some(toyos_abi::handle::Rights::MAP)), - ] { + let memory = |object: &Arc, keeps: Option| { let object = KObjectRef::SharedMem(Arc::clone(object)); let rights = keeps.unwrap_or_else(|| ops::initial_rights(&object)); - let handle = handles - .install(crate::object::HandleEntry::new(object, rights)) - .expect("spawn_supervisor: a table of four handles refused a boot image"); - entries.push(toyos_abi::syscall::EndowEntry { - label_off: labels.len() as u32, - label_len: label.len() as u32, - handle, - _pad: 0, - }); - labels.extend_from_slice(label.as_bytes()); - } + crate::object::HandleEntry::new(object, rights) + }; + // The two images go on to the file server; the names are the supervisor's to read. + let endowed = [ + (at::SYSCAP, crate::object::HandleEntry::new(cap, rights)), + (at::FILESERVER_IMAGE, memory(&images.fileserver, None)), + (at::ROOT_IMAGE, memory(&images.root, None)), + (at::BOOT_NAMES, memory(&images.names, Some(Rights::MAP))), + ]; // Its ranges as the build laid them out of the supervisor section // (`toyos_kimage`), checked as a spawn's are. let maps = laid.maps().map(|raw| { @@ -358,7 +337,7 @@ pub fn spawn_supervisor(images: crate::bootimage::Objects, laid: &toyos_kimage:: build_id: BuildId::new(laid.build_id), }; // Built by the kernel and owing nobody anything: no table but the supervisor's own holds it. - let table = SupervisorTable { job, handles, entries, labels }; + let table = SupervisorTable { handles: HandleTable::new(&job), job, endowed }; let name = toyos_abi::syscall::process_name(SUPERVISOR.as_bytes()).expect("spawn_supervisor: its own name"); match spawn(SUPERVISOR, name, table, program, Parent::Root) { Ok((pid, ())) => pid, @@ -371,8 +350,8 @@ pub fn spawn_supervisor(images: crate::bootimage::Objects, laid: &toyos_kimage:: struct SupervisorTable { job: Arc, handles: HandleTable, - entries: Vec, - labels: Vec, + /// What it holds beside its own two, each at the handle the ABI states. + endowed: [(RawHandle, crate::object::HandleEntry); 4], } impl Commit for SupervisorTable { @@ -386,9 +365,13 @@ impl Commit for SupervisorTable { false } - fn commit(self, own: start::Own) -> Result<(HandleTable, Endowments, ()), crate::object::Refusal> { - let Self { mut handles, mut entries, mut labels, .. } = self; - own.endow(&mut handles, &mut entries, &mut labels); - Ok((handles, Endowments::new(entries, labels), ())) + fn commit(self, own: start::Own) -> Result<(HandleTable, ()), crate::object::Refusal> { + let Self { mut handles, endowed, .. } = self; + own.install(&mut handles); + for (at, entry) in endowed { + let handle = handles.install(entry).expect("spawn_supervisor: an empty table refused what the supervisor starts with"); + assert_eq!(handle, at, "spawn_supervisor: a new table issued its handles out of order"); + } + Ok((handles, ())) } } diff --git a/kernel/src/loader/start.rs b/kernel/src/loader/start.rs index a9114523dff..0803ad6349c 100644 --- a/kernel/src/loader/start.rs +++ b/kernel/src/loader/start.rs @@ -9,19 +9,14 @@ use alloc::sync::Arc; use alloc::vec::Vec; use crate::mm::charge::{self, Charge}; -use crate::object::job::JobObject; use crate::object::notice::NoticeObject; use crate::object::process::ProcessObject; use crate::object::{ops, HandleEntry, HandleTable, KObjectRef, Refusal}; -use crate::process::{ - handles, process_data, with_handles, Endowments, KERNEL_STACK_SIZE, -}; +use crate::process::{handles, KERNEL_STACK_SIZE}; use crate::user_ptr::UserBytes; -use toyos_abi::handle::{RawHandle, Rights}; +use toyos_abi::handle::{RawHandle, Rights, TransferEntry}; use kernel::sched::job::Job; -use toyos_abi::syscall::{ - EndowEntry, SpawnEndow, SyscallError, JOB_LABEL, MAX_SPAWN_ENDOWMENTS, MAX_SPAWN_LABELS_LEN, QUIT_LABEL, SELF_LABEL, -}; +use toyos_abi::syscall::{SyscallError, MAX_SPAWN_ENDOWMENTS}; /// Where a new context first runs. pub(crate) enum Start { @@ -44,18 +39,23 @@ pub(crate) struct KernelStack { impl KernelStack { /// The heap a thread takes beside its stack's frames: its scheduler records, its place /// on the one watch it may wait on, whose list doubles, its place in its - /// process's thread table, its own data, and what one of its calls copies - /// in and may hold while it is preempted — two strings at their bound, as - /// a rename's paths, and a page besides, as a namespace's names. + /// process's thread table, its own data, and what a spawn it makes copies + /// in and holds where it may be preempted: its path, name and build-id at + /// their bounds, its ranges, its endowments, and what the device claims it + /// moves handed out. fn heap() -> usize { + use toyos_abi::syscall::{MAX_BUILD_ID, MAX_PATH, MAX_PROCESS_NAME, MAX_SPAWN_MAPS}; let waiter = 2 * core::mem::size_of::<(usize, u64)>(); - let call = 2 * crate::user_ptr::MAX_USER_STR as usize + crate::mm::PAGE_BYTES; + let spawn = [MAX_PATH, MAX_PROCESS_NAME, MAX_BUILD_ID].into_iter().map(charge::array::).sum::() + + charge::array::(MAX_SPAWN_MAPS) + + charge::array::(MAX_SPAWN_ENDOWMENTS) + + charge::array::(MAX_SPAWN_ENDOWMENTS); let records = kernel::sched::task::TaskBuilder::::allocations().into_iter().map(charge::heap).sum::() + charge::arc::() + waiter; let process = charge::map_entry::() + charge::arc::>() - + call; + + spawn; records + process } @@ -97,8 +97,7 @@ pub(crate) fn alloc_kernel_stack(job: &Arc, start: Start) -> Option<(Kernel // The move must be last: an earlier move leaves a failed spawn's caller holding handles that name nothing. pub struct PendingHandles { table: HandleTable, - endow: Vec, - labels: Vec, + endow: Vec, /// The job the child runs in, which everything built for it is counted to. job: Arc, /// `SpawnArgs::pager` named a `SysCap` carrying `Rights::PAGER`. @@ -117,9 +116,9 @@ pub trait Commit { /// Whether the child starts as a pager (`process::Handles::is_pager`). fn pager(&self) -> bool; - /// The child's table around `own`, what the kernel puts beside its - /// endowments. - fn commit(self, own: Own) -> Result<(HandleTable, Endowments, Self::Held), Refusal>; + /// The child's table: `own`, then what the caller gives it, in the + /// order `RawHandle::endowed` states. + fn commit(self, own: Own) -> Result<(HandleTable, Self::Held), Refusal>; } impl Commit for PendingHandles { @@ -134,89 +133,70 @@ impl Commit for PendingHandles { } /// Take the endowed handles out of the caller's table and put its handle to the child in it, all under one lock hold: a refusal leaves the table unchanged. - /// `own` is what the kernel puts in the child's table beside the endowments; the caller's handle to the child is the - /// last answer. The handle under [`JOB_LABEL`] still names [`job`](Self::job), or the spawn is refused. - fn commit(self, own: Own) -> Result<(HandleTable, Endowments, RawHandle), Refusal> { - let Self { mut table, endow, mut labels, job, .. } = self; - let own_job = process_data().lock().job.clone(); + fn commit(self, own: Own) -> Result<(HandleTable, RawHandle), Refusal> { + let Self { mut table, endow, .. } = self; let caller = handles(); let mut handles = caller.lock(); - let mut moving: Vec<(EndowEntry, RawHandle, Rights)> = Vec::with_capacity(endow.len()); - let mut endowed_job: Option> = None; - for SpawnEndow { label_off, label_len, moved } in endow { - let handle = moved.handle; + let mut moving: Vec<(RawHandle, Rights)> = Vec::with_capacity(endow.len()); + for moved in endow { let want = moved.rights().map_err(|_| SyscallError::InvalidArgument)?; - let label = label_of(&labels, label_off, label_len).ok_or(SyscallError::InvalidArgument)?; - // The kernel's own, which a caller's of that name would shadow in the child's lookup. - if Own::LABELS.iter().any(|own| label == own.as_bytes()) { - return Err(SyscallError::InvalidArgument.into()); - } - if label == JOB_LABEL.as_bytes() { - // One owner: a second would be a job the child's lookup cannot name. - if endowed_job.is_some() { - return Err(SyscallError::InvalidArgument.into()); - } - endowed_job = Some(handles.get_sent::(handle, Rights::WRITE)?.job().clone()); - } // Checked before any removal, so a missing `TRANSFER` refuses the spawn instead of leaving a hole. - let rights = handles.rights_to_move(handle, want)?; + let rights = handles.rights_to_move(moved.handle, want)?; // A duplicate is refused here: unremoved handles let it pass twice, then the second removal's // `expect` below panics. - if moving.iter().any(|(_, seen, _)| *seen == handle) { + if moving.iter().any(|(seen, _)| *seen == moved.handle) { return Err(SyscallError::InvalidArgument.into()); } - moving.push((EndowEntry { label_off, label_len, handle, _pad: 0 }, handle, rights)); - } - // Before any removal: the table changed under the spawn if the job - // everything was built for is no longer the one it names. - if !Arc::ptr_eq(&job, endowed_job.as_ref().unwrap_or(&own_job)) { - return Err(SyscallError::InvalidArgument.into()); + moving.push((moved.handle, rights)); } // Checked before any removal, so a failed install can't strand a handle out of a table that never spawned. - if !table.make_room(moving.len() + Own::LABELS.len()) || !handles.make_room(1) { + if !table.make_room(Own::HANDLES + moving.len()) || !handles.make_room(1) { return Err(SyscallError::ResourceExhausted.into()); } - let mut entries = Vec::with_capacity(moving.len()); + // Minted while the child's table holds its own, a table no thread reaches until the child lands: another thread of the caller closing this handle never closes the object's last. + let child = own.process.object().clone(); + own.install(&mut table); // What each device claim moving into the child handed out, taken off - // it under the caller's lock and revoked once that lock is let go. + // it under the caller's lock and revoked once that lock is let go: + // sized at the first for every handle left to move, so it never + // grows past what `KernelStack::heap` charges. let mut handed = Vec::new(); - for (mut entry, parent_handle, rights) in moving { + let count = moving.len(); + for (i, (parent_handle, rights)) in moving.into_iter().enumerate() { let moved = handles .remove(parent_handle) .expect("an endowed handle verified under this lock stopped resolving") .arriving_with(rights); if let KObjectRef::Device(claim) = moved.object() { - handed.extend(claim.take_mappings()); + if let Some(mappings) = claim.take_mappings() { + handed.reserve_exact(count - i); + handed.push(mappings); + } } - entry.handle = table - .install(moved) - .expect("a child table with verified room refused an endowment"); - entries.push(entry); + let installed = table.install(moved).expect("a child table with verified room refused an endowment"); + assert_eq!(installed, RawHandle::endowed(i), "a new table issued its handles out of order"); } - // Minted while `own` is held, and `own` goes into a table no thread reaches until the child lands: another thread of the caller closing this handle never closes the object's last. - let held = ops::install(&mut handles, own.process.object().clone()) - .expect("a caller's table with verified room refused its child"); + let held = ops::install(&mut handles, child).expect("a caller's table with verified room refused its child"); drop(handles); for handed in handed { handed.revoke(); } - own.endow(&mut table, &mut entries, &mut labels); - Ok((table, Endowments::new(entries, labels), held)) + Ok((table, held)) } } -/// What the kernel puts in every new process's table beside its endowments: -/// its handle to itself under [`SELF_LABEL`], the first its object has, and -/// its notice under [`QUIT_LABEL`], the only one its object ever has. +/// What the kernel puts first in every new process's table: its handle to +/// itself at [`RawHandle::SELF`], the first its object has, and its notice +/// at [`RawHandle::NOTICE`], the only one its object ever has. pub struct Own { process: HandleEntry, notice: HandleEntry, } impl Own { - /// The labels [`Own::endow`] installs under, which no caller's endowment may use. - const LABELS: [&str; 2] = [SELF_LABEL, QUIT_LABEL]; + /// How many handles [`Own::install`] installs. + const HANDLES: usize = 2; /// `WRITE` to be named a spawn's place, `DUP` and `TRANSFER` to hand that on. pub(super) fn new(object: &Arc, notice: &Arc) -> Self { @@ -227,52 +207,30 @@ impl Own { } } - /// Install both under their labels. The caller verified the room. - pub(super) fn endow(self, table: &mut HandleTable, entries: &mut Vec, labels: &mut Vec) { - for (entry, label) in [self.process, self.notice].into_iter().zip(Self::LABELS) { + /// Install both in `table`, which holds nothing yet. The caller verified the room. + pub(super) fn install(self, table: &mut HandleTable) { + for (entry, at) in [(self.process, RawHandle::SELF), (self.notice, RawHandle::NOTICE)] { let handle = table.install(entry).expect("a child table with verified room refused the kernel's own handles"); - entries.push(EndowEntry { label_off: labels.len() as u32, label_len: label.len() as u32, handle, _pad: 0 }); - labels.extend_from_slice(label.as_bytes()); + assert_eq!(handle, at, "a new table issued its handles out of order"); } } } -/// Reads `SpawnArgs`'s endowments into the child's pending handle state. +/// Reads `SpawnArgs`'s endowments into the child's pending handle state, in the job `job`. // Every refusal here precedes any child state, so nothing is enqueued or orphaned. -pub fn build_child_handles(endow: &UserBytes, labels: &[u8], pager: bool) -> Result { - let count = endow.len() / SPAWN_ENDOW_LEN; +pub fn build_child_handles(endow: &UserBytes, job: Arc, pager: bool) -> Result { + let count = endow.len() / TRANSFER_ENTRY_LEN; if count > MAX_SPAWN_ENDOWMENTS { return Err(SyscallError::InvalidArgument.into()); } - if labels.len() > MAX_SPAWN_LABELS_LEN { - return Err(SyscallError::InvalidArgument.into()); - } let mut entries = Vec::with_capacity(count); for i in 0..count { - let mut entry: SpawnEndow = toyos_abi::usersafe::zeroed(); - endow.read_at(i * SPAWN_ENDOW_LEN, toyos_abi::usersafe::bytes_mut(&mut entry)).map_err(SyscallError::from)?; + let mut entry: TransferEntry = toyos_abi::usersafe::zeroed(); + endow.read_at(i * TRANSFER_ENTRY_LEN, toyos_abi::usersafe::bytes_mut(&mut entry)).map_err(SyscallError::from)?; entries.push(entry); } - let job = child_job(&entries, labels)?; - Ok(PendingHandles { table: HandleTable::new(&job), endow: entries, labels: labels.to_vec(), job, pager }) + Ok(PendingHandles { table: HandleTable::new(&job), endow: entries, job, pager }) } /// One endowment of a spawn's request on the wire. -const SPAWN_ENDOW_LEN: usize = core::mem::size_of::(); - -/// The label an entry's offset and length name in `labels`, if they are inside it. -fn label_of(labels: &[u8], off: u32, len: u32) -> Option<&[u8]> { - labels.get(off as usize..(off as usize).checked_add(len as usize)?) -} - -/// The job a child runs in: the one endowed under [`JOB_LABEL`], or else the -/// caller's. Only looked up: [`PendingHandles::commit`] refuses the rest. -fn child_job(endow: &[SpawnEndow], labels: &[u8]) -> Result, Refusal> { - for entry in endow { - if label_of(labels, entry.label_off, entry.label_len) == Some(JOB_LABEL.as_bytes()) { - let named = with_handles(|handles| handles.get_sent::(entry.moved.handle, Rights::WRITE))?; - return Ok(named.job().clone()); - } - } - Ok(process_data().lock().job.clone()) -} +const TRANSFER_ENTRY_LEN: usize = core::mem::size_of::(); diff --git a/kernel/src/object/handle.rs b/kernel/src/object/handle.rs index 0fe71d6f194..7ea3944a1b1 100644 --- a/kernel/src/object/handle.rs +++ b/kernel/src/object/handle.rs @@ -41,7 +41,7 @@ pub enum HandleError { BadHandle, /// The slot has moved past this handle: closed, or its generations ran out. Stale, - /// Ends the caller like the two above, except through [`HandleTable::get_sent`], the one lookup of a handle a peer sent — `SYS_NAMESPACE_BUILD`'s connector and `SYS_SPAWN`'s place — where it is `InvalidArgument`. + /// Ends the caller like the two above, except through [`HandleTable::get_sent`], the one lookup of a handle a peer sent — `SYS_CONNECT`'s connector and `SYS_SPAWN`'s place and job — where it is `InvalidArgument`. WrongType { held: &'static str, wanted: &'static str }, /// The handle is fine and does not carry what the call needs. Rights { held: Rights, needed: Rights }, diff --git a/kernel/src/object/job.rs b/kernel/src/object/job.rs index 34414d1ffac..d5c2102c69b 100644 --- a/kernel/src/object/job.rs +++ b/kernel/src/object/job.rs @@ -1,7 +1,7 @@ //! A handle to a job: the owner a process's threads, and its children's unless //! a spawn names another, share CPU time under (`kernel::sched::job`), and the //! account their memory is counted to (`kernel::frames::Ledger`). A spawn -//! that endows one under `JOB_LABEL` with `WRITE` puts its child in it. +//! that names one carrying `WRITE` (`SpawnArgs::job`) puts its child in it. //! //! The job lives while a thread of it runs, a handle to it is held or an //! object charged to it lives, and not past all three: its launcher ending diff --git a/kernel/src/object/mod.rs b/kernel/src/object/mod.rs index 39e7e74f8ca..4557ce5bba3 100644 --- a/kernel/src/object/mod.rs +++ b/kernel/src/object/mod.rs @@ -23,7 +23,6 @@ pub mod event; pub mod handle; pub mod inbox; pub mod job; -pub mod namespace; pub mod notice; pub mod null; pub mod ops; @@ -278,8 +277,6 @@ kobject! { deferred SharedMem => shm::SharedMemObject, // A service with no clients right now is not a service that has stopped. immediate Connector => port::Connector, - // Immutable once built: its `Arc`s go with the last reference and nothing observes it. - immediate Namespace => namespace::Namespace, // The authority is the rights on the handle; a handle going away *is* the whole event. immediate SysCap => syscap::SysCap, // The last handle's loss is the loss of the *ability to wait*, not proof the process should stop. diff --git a/kernel/src/object/namespace.rs b/kernel/src/object/namespace.rs deleted file mode 100644 index 6f9ef7528c0..00000000000 --- a/kernel/src/object/namespace.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! The only place a process can resolve a service name. Immutable once -//! built — no insert, no remove, no replace — and a namespace narrowed -//! for a child cannot be widened back. There is no global registry: a -//! name a process was not given resolves to nothing. - -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; - -use toyos_abi::syscall::{MAX_NAMESPACE_ENTRIES, MAX_SERVICE_NAME}; - -use crate::mm::charge::{self, Exhausted}; - -use super::port::Connector; -use super::{KObjectVariant, ObjectCore}; - -pub struct Namespace { - pub(super) core: ObjectCore, - /// Sorted by name: lookup binary-searches this order. - entries: Box<[(Box, Arc)]>, -} - -/// Why a namespace could not be built. -pub enum BuildError { - /// Too many entries, or a name past the length limit — refused, not truncated. - TooMany, - /// Two entries share a name. - Duplicate, - Exhausted, -} - -impl Namespace { - /// Entries must already carry every name this namespace is to hold. - pub fn build(mut entries: Vec<(Box, Arc)>) -> Result, BuildError> { - if entries.len() > MAX_NAMESPACE_ENTRIES { - return Err(BuildError::TooMany); - } - if entries.iter().any(|(name, _)| name.len() > MAX_SERVICE_NAME) { - return Err(BuildError::TooMany); - } - entries.sort_by(|a, b| a.0.cmp(&b.0)); - if entries.windows(2).any(|w| w[0].0 == w[1].0) { - return Err(BuildError::Duplicate); - } - let names: usize = entries.iter().map(|(name, _)| charge::heap(core::alloc::Layout::for_value::(name))).sum(); - let core = Self::new_core(charge::array::<(Box, Arc)>(entries.len()) + names) - .map_err(|Exhausted| BuildError::Exhausted)?; - Ok(Arc::new(Self { core, entries: entries.into_boxed_slice() })) - } - - pub fn lookup(&self, name: &str) -> Option<&Arc> { - let i = self.entries.binary_search_by(|(n, _)| (**n).cmp(name)).ok()?; - Some(&self.entries[i].1) - } - - /// Every binding, for a build carrying the whole of this one over. - pub fn entries(&self) -> &[(Box, Arc)] { - &self.entries - } -} diff --git a/kernel/src/object/notice.rs b/kernel/src/object/notice.rs index 1506411fce4..c66758d30c9 100644 --- a/kernel/src/object/notice.rs +++ b/kernel/src/object/notice.rs @@ -1,7 +1,7 @@ //! A process's notice: what a quit asks it, readable until it is read. //! -//! Each process is started holding its own under `QUIT_LABEL`, and nothing -//! else holds it. A read takes the reason asked, one byte +//! Each process is started holding its own at `RawHandle::NOTICE`, and +//! nothing else holds it. A read takes the reason asked, one byte //! (`toyos_abi::quit::Reason::to_byte`), and blocks while none is; a read or //! an armed readable watch makes the process a listener, which a quit asks //! rather than kills (`toyos_proclife::quit`). diff --git a/kernel/src/object/ops.rs b/kernel/src/object/ops.rs index 8062538b9d5..cb9d7ca2deb 100644 --- a/kernel/src/object/ops.rs +++ b/kernel/src/object/ops.rs @@ -49,10 +49,8 @@ pub fn initial_rights(object: &KObjectRef) -> Rights { let read = Rights::DUP.union(Rights::TRANSFER).union(Rights::MAP); if m.immutable().is_some() { read } else { read.union(Rights::WRITE) } } - // A connector has no read/write path: put it in a namespace, or give the namespace away. - KObjectRef::Connector(_) => Rights::DUP.union(Rights::TRANSFER), - // `READ` is what resolving a name through it, and narrowing into a child's, both take. - KObjectRef::Namespace(_) => Rights::DUP.union(Rights::TRANSFER).union(Rights::READ), + // `WRITE` is what connecting through it takes: a holder given it without may only pass it on. + KObjectRef::Connector(_) => Rights::DUP.union(Rights::TRANSFER).union(Rights::WRITE), // A spawner gets everything a child handle offers: exit code, kill, and accounting. KObjectRef::Process(_) => BASE.union(Rights::READ).union(Rights::MANAGE), KObjectRef::Null(_) => super::null::NullObject::RIGHTS, @@ -106,7 +104,7 @@ pub fn pipe_read(object: &KObjectRef) -> Option<&PipeRef> { KObjectRef::Connection(_) | KObjectRef::PipeWrite(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Null(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => None, } @@ -119,7 +117,7 @@ pub fn pipe_write(object: &KObjectRef) -> Option<&PipeRef> { KObjectRef::Connection(_) | KObjectRef::PipeRead(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Null(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => None, } @@ -181,7 +179,7 @@ pub fn read_watch(object: &KObjectRef) -> Option { // Named unconditionally: the watch alone cannot enforce rights. KObjectRef::SysCap(_) => Some(WatchRef::Static(&crate::log::user::WATCH)), KObjectRef::PipeWrite(_) | KObjectRef::Inbox(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Null(_) => None, } } @@ -193,7 +191,7 @@ pub fn watched_readable(object: &KObjectRef) { KObjectRef::Notice(n) => n.listen(), KObjectRef::PipeRead(_) | KObjectRef::PipeWrite(_) | KObjectRef::Connection(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SharedMem(_) | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) - | KObjectRef::Namespace(_) | KObjectRef::SysCap(_) + | KObjectRef::SysCap(_) | KObjectRef::Process(_) | KObjectRef::Null(_) | KObjectRef::Job(_) | KObjectRef::DeviceSet(_) => {} } } @@ -206,7 +204,7 @@ pub fn write_watch(object: &KObjectRef) -> Option { KObjectRef::PipeRead(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Null(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => None, } @@ -222,7 +220,7 @@ pub fn pipe_end_watch(object: &KObjectRef) -> Option { | KObjectRef::Notice(_) | KObjectRef::Device(_) | KObjectRef::SysCap(_) | KObjectRef::Inbox(_) | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) - | KObjectRef::Namespace(_) | KObjectRef::SharedMem(_) | KObjectRef::Null(_) | KObjectRef::DeviceSet(_) => None, + | KObjectRef::SharedMem(_) | KObjectRef::Null(_) | KObjectRef::DeviceSet(_) => None, } } @@ -236,7 +234,7 @@ pub fn other_end_gone(object: &KObjectRef) -> bool { | KObjectRef::Notice(_) | KObjectRef::Device(_) | KObjectRef::SysCap(_) | KObjectRef::Inbox(_) | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) - | KObjectRef::Namespace(_) | KObjectRef::SharedMem(_) | KObjectRef::Null(_) | KObjectRef::DeviceSet(_) => false, + | KObjectRef::SharedMem(_) | KObjectRef::Null(_) | KObjectRef::DeviceSet(_) => false, } } @@ -358,7 +356,7 @@ pub fn try_read(object: &KObjectRef, buf: &mut UserBytesMut) -> Option { // A connection carries whole messages, which `SYS_MESSAGE_RECV` takes. KObjectRef::Connection(_) | KObjectRef::PipeWrite(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::DeviceSet(_) => Some(SyscallError::PermissionDenied.to_u64()), } } @@ -392,7 +390,7 @@ pub fn try_write(object: &KObjectRef, buf: &UserBytes) -> Option { // A connection carries whole messages, which `SYS_MESSAGE_SEND` sends. KObjectRef::Connection(_) | KObjectRef::PipeRead(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SharedMem(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => { Some(SyscallError::PermissionDenied.to_u64()) } @@ -438,7 +436,7 @@ pub fn fstat(object: &KObjectRef) -> Stat { node: 0, }, KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => plain(FileType::Unknown), KObjectRef::Device(d) => plain(match d.class() { device_registry::DeviceType::Framebuffer => FileType::Framebuffer, @@ -474,7 +472,7 @@ pub fn has_data(object: &KObjectRef) -> bool { device_registry::DeviceType::Framebuffer => true, }, KObjectRef::PipeWrite(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) => false, } } @@ -487,7 +485,7 @@ pub fn read_posts_are_readiness(object: &KObjectRef) -> bool { KObjectRef::SysCap(_) => true, KObjectRef::PipeRead(_) | KObjectRef::PipeWrite(_) | KObjectRef::Connection(_) | KObjectRef::Acceptor(_) | KObjectRef::Device(_) - | KObjectRef::Inbox(_) | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Inbox(_) | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Null(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => false, } @@ -502,7 +500,7 @@ pub fn has_space(object: &KObjectRef) -> bool { KObjectRef::Null(_) => true, KObjectRef::PipeRead(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::SharedMem(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => false, } } @@ -521,7 +519,7 @@ pub fn mark_tty(object: &KObjectRef) -> u64 { KObjectRef::Connection(_) | KObjectRef::Device(_) | KObjectRef::Acceptor(_) | KObjectRef::Inbox(_) | KObjectRef::SysCap(_) | KObjectRef::SharedMem(_) - | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Namespace(_) + | KObjectRef::Connector(_) | KObjectRef::WakeLatency(_) | KObjectRef::EventSignal(_) | KObjectRef::EventWait(_) | KObjectRef::Pager(_) | KObjectRef::Paged(_) | KObjectRef::Process(_) | KObjectRef::Job(_) | KObjectRef::Null(_) | KObjectRef::Notice(_) | KObjectRef::DeviceSet(_) => { SyscallError::InvalidArgument.to_u64() } diff --git a/kernel/src/process.rs b/kernel/src/process.rs index cfe1cd16ae2..3c72a6ab2b4 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -47,16 +47,13 @@ use crate::loader::{alloc_kernel_stack, Start}; pub use toyos_abi::{Pid, Tid}; pub use crate::scheduler::TaskId; use toyos_abi::end::{End, Fault}; -use toyos_abi::syscall::{EndowEntry, SyscallError}; +use toyos_abi::syscall::SyscallError; /// The lifecycle's decisions; this file only performs them. pub use toyos_proclife::{ThreadLocation, Watch}; use toyos_proclife::quit::{self, Notice, Reason}; use toyos_proclife::{join, reap, spawn as proclife_spawn, teardown as proclife, tree, Lifecycle, Node, Processes, Tids}; -/// One `EndowEntry` on the wire; [`Endowments::encode`] indexes by it. -const ENDOW_ENTRY_LEN: usize = core::mem::size_of::(); - pub use crate::loader::{build_child_handles, spawn, spawn_supervisor}; /// A process's space, which every thread of it runs in. @@ -66,7 +63,7 @@ pub use crate::mm::space::PageTables; /// a lookup ([`with_handles`]) takes neither the process table nor /// [`ProcessData`]'s lock. pub struct Handles { - /// Every kernel object it can name; stdio is handles `0`, `1`, `2`. + /// Every kernel object it can name. table: Lock, /// Started as a pager, which no call changes (`object::paged`): it may /// make one pager object, and is refused a holder's read, which would wait @@ -284,8 +281,6 @@ pub struct ProcessData { pub spawn_ns: u64, pub accounting: ProcessAccounting, - /// What this process's parent called each handle it moved in at spawn. - pub endowments: Endowments, /// The job its threads, and its children unless a spawn names another, /// run in. pub job: Arc, @@ -297,7 +292,7 @@ impl ProcessData { /// The heap a process of the program at `path` takes beside what is /// charged on its own — its handles, threads, ranges and objects: its /// records, its place in the process table and among its parent's - /// children, its label, and its endowments at their bound. + /// children, and its label. pub fn charge(job: &Arc, path: &str) -> Result { // The process table doubles, and its control byte beside each. let entry = 2 * (core::mem::size_of::<(Pid, ProcessEntry)>() + 1); @@ -312,40 +307,7 @@ impl ProcessData { + threads + 2 * core::mem::size_of::(); let label = charge::array::(path.len()); - let endowments = charge::array::(toyos_abi::syscall::MAX_ENDOWMENTS) - + charge::array::(toyos_abi::syscall::MAX_LABELS_LEN); - Charge::new(job, records + label + endowments) - } -} - -/// The labels a parent put on the handles it endowed — names, not authority: the handle is in the table whether or not the child ever asks. -pub struct Endowments { - entries: alloc::boxed::Box<[EndowEntry]>, - labels: alloc::boxed::Box<[u8]>, -} - -impl Endowments { - pub fn new(entries: Vec, labels: Vec) -> Self { - Self { entries: entries.into_boxed_slice(), labels: labels.into_boxed_slice() } - } - - /// Bytes `SYS_ENDOWMENTS` answers with: a `u64` count, the entries, then the blob their offsets index. - pub fn encoded_len(&self) -> usize { - 8 + self.entries.len() * ENDOW_ENTRY_LEN + self.labels.len() - } - - /// Render into `out`, sized at [`Self::encoded_len`]. Field by field, not a slice transmute: `EndowEntry`'s padding word is written as zero here, so nothing of the kernel's reaches a child in it. - pub fn encode(&self, out: &mut [u8]) { - out[..8].copy_from_slice(&(self.entries.len() as u64).to_ne_bytes()); - for (i, entry) in self.entries.iter().enumerate() { - let at = 8 + i * ENDOW_ENTRY_LEN; - out[at..at + 4].copy_from_slice(&entry.label_off.to_ne_bytes()); - out[at + 4..at + 8].copy_from_slice(&entry.label_len.to_ne_bytes()); - out[at + 8..at + 12].copy_from_slice(&entry.handle.0.to_ne_bytes()); - out[at + 12..at + 16].copy_from_slice(&0u32.to_ne_bytes()); - } - let blob = 8 + self.entries.len() * ENDOW_ENTRY_LEN; - out[blob..blob + self.labels.len()].copy_from_slice(&self.labels); + Charge::new(job, records + label) } } diff --git a/kernel/src/syscall/dispatch.rs b/kernel/src/syscall/dispatch.rs index e65f07ac541..070fcc42093 100644 --- a/kernel/src/syscall/dispatch.rs +++ b/kernel/src/syscall/dispatch.rs @@ -34,7 +34,7 @@ use super::io::{ use super::ipc::{ Incoming, Outgoing, sys_accept, sys_inbox_setup, sys_message_recv, sys_message_send, - sys_inbox_submit, sys_namespace_build, sys_namespace_names, sys_namespace_open, sys_pipe, + sys_connect, sys_inbox_submit, sys_pipe, sys_port_badge, sys_port_create, sys_port_mint, sys_shm_create, sys_shm_sub, }; use super::machine::{ @@ -47,7 +47,7 @@ use super::machine::{ use super::machine::lower_sysinfo_bound; use super::paged::{sys_paged_create, sys_paged_holder, sys_paged_notices, sys_paged_op, sys_paged_read, sys_pager_create}; use super::proc::{ - spawn_pager, spawn_place, spawn_program, sys_endowments, sys_exit, sys_nanosleep, sys_process_stats, + spawn_job, spawn_pager, spawn_place, spawn_program, sys_exit, sys_nanosleep, sys_process_stats, sys_account_info, sys_job_create, sys_job_floor, sys_job_own, sys_process_quit, sys_process_wait, sys_rt_enter, sys_spawn, sys_thread_exit, sys_thread_get_affinity, sys_thread_join, sys_thread_set_affinity, sys_set_thread_pointer, sys_thread_spawn, sys_wake_latency_hold, sys_yield, }; use super::vm::{ @@ -120,6 +120,10 @@ pub(crate) fn syscall_dispatch(num: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> Ok(parent) => parent, Err(refused) => return refused, }; + let job = match spawn_job(args.job) { + Ok(job) => job, + Err(refused) => return refused, + }; let pager = match spawn_pager(args.pager) { Ok(pager) => pager, Err(refused) => return refused, @@ -137,24 +141,15 @@ pub(crate) fn syscall_dispatch(num: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> let Some(name) = toyos_abi::syscall::process_name(&name) else { return SyscallError::InvalidArgument.to_u64(); }; - if args.endow_count as usize > toyos_abi::syscall::MAX_SPAWN_ENDOWMENTS - || args.labels_len > toyos_abi::syscall::MAX_SPAWN_LABELS_LEN as u64 - { + // Bounded before it is multiplied; a window, not a copy: each entry is read once. + if args.endow_count > toyos_abi::syscall::MAX_SPAWN_ENDOWMENTS as u64 { return SyscallError::InvalidArgument.to_u64(); } - // endow stays a window, not a copy: its count is userland's and each entry - // is read once. labels is copied because the child keeps it. - let Some(endow) = (args.endow_count as usize) - .checked_mul(core::mem::size_of::()) - .and_then(|len| ctx.user_bytes(UserAddr::new(args.endow_ptr), len as u64)) - else { + let endow_len = args.endow_count * core::mem::size_of::() as u64; + let Some(endow) = ctx.user_bytes(UserAddr::new(args.endow_ptr), endow_len) else { return bad_addr; }; - let labels = match ctx.user_vec(UserAddr::new(args.labels_ptr), args.labels_len) { - Ok(bytes) => bytes, - Err(e) => return e.to_u64(), - }; - let pending = match process::build_child_handles(&endow, &labels, pager) { + let pending = match process::build_child_handles(&endow, job, pager) { Ok(built) => built, Err(e) => return e.refuse(), }; @@ -280,10 +275,6 @@ pub(crate) fn syscall_dispatch(num: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> } SYS_DEVICE_DMA_UNMAP => sys_device_dma_unmap(RawHandle(a1 as u32), a2), SYS_DEVICE_DMA_START => sys_device_dma_start(RawHandle(a1 as u32)), - SYS_ENDOWMENTS => { - let Some(mut buf) = ctx.user_bytes_mut(UserAddr::new(a1), a2) else { return bad_addr }; - sys_endowments(&mut buf) - } SYS_DEVICE_CLAIM => sys_device_claim(RawHandle(a1 as u32), a2, [a3, a4]), SYS_RT_ENTER => sys_rt_enter(RawHandle(a1 as u32)), SYS_JOB_CREATE => sys_job_create(RawHandle(a1 as u32)), @@ -354,22 +345,7 @@ pub(crate) fn syscall_dispatch(num: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> }; sys_port_badge(RawHandle(a1 as u32), RawHandle(a2 as u32), &mut out) } - SYS_NAMESPACE_BUILD => { - let Ok(args) = ctx.copy_in::(UserAddr::new(a1)) else { - return bad_addr; - }; - // ctx lets sys_namespace_build bound both counts before it reads either vector. - sys_namespace_build(&ctx, &args) - } - SYS_NAMESPACE_NAMES => { - let len = a3.min((MAX_NAMESPACE_ENTRIES * (2 + MAX_SERVICE_NAME)) as u64); - let Some(mut out) = ctx.user_bytes_mut(UserAddr::new(a2), len) else { return bad_addr }; - sys_namespace_names(RawHandle(a1 as u32), &mut out) - } - SYS_NAMESPACE_OPEN => { - let name = match ctx.user_str(UserAddr::new(a2), a3) { Ok(s) => s, Err(e) => return e.to_u64() }; - sys_namespace_open(RawHandle(a1 as u32), &name) - } + SYS_CONNECT => sys_connect(RawHandle(a1 as u32)), SYS_INBOX_SETUP => { // Taken before the inbox exists: a bad address must not leave a handle the caller was never told. let len = core::mem::size_of::() as u64; diff --git a/kernel/src/syscall/ipc.rs b/kernel/src/syscall/ipc.rs index a66a8bed9b5..f11d46be15d 100644 --- a/kernel/src/syscall/ipc.rs +++ b/kernel/src/syscall/ipc.rs @@ -1,7 +1,7 @@ -//! What one process reaches another through: pipes, ports and the namespaces -//! that name them, connections, shared memory, and inboxes. -//! No peer is addressed by name or pid: ports, namespaces and connections are -//! built only from connectors and handles the caller already holds. +//! What one process reaches another through: pipes, ports, connections, +//! shared memory, and inboxes. +//! No peer is addressed by name or pid: a connection is made only through a +//! connector the caller already holds. //! Every all-or-nothing claim here is structural: whatever can refuse is //! resolved and checked before anything is installed or removed, or under the //! hold that installs it. No handle is taken back across a released hold: a @@ -15,8 +15,6 @@ use crate::message::{MessageBounce, QueueRef, SendRefused, Unclaimed, Unsent}; use crate::object::handle::{HandleEntry, HandleTable}; use crate::object::service::ConnectionEnd; use crate::time::Deadline; -use crate::user_ptr::SyscallContext; -use crate::UserAddr; use crate::{message, pipe, process}; use toyos_abi::handle::{NotRights, RawHandle, ReceivedEntry, Rights, TransferEntry}; @@ -65,162 +63,13 @@ pub(super) fn sys_port_create() -> u64 { } } -/// Build a namespace from a base's kept names plus new bindings; a refusal leaves the caller's table unchanged (every name resolved and connector checked first). -pub(super) fn sys_namespace_build(ctx: &SyscallContext, args: &NamespaceBuild) -> u64 { - // A bit this kernel does not define is a caller asking for something it will not get, so it is refused rather than skipped. - if args.flags & !NAMESPACE_FLAGS_KNOWN != 0 { - return SyscallError::InvalidArgument.to_u64(); - } - let keep_all = args.flags & NAMESPACE_KEEP_ALL != 0; - if keep_all && args.keep_n > 0 { - return SyscallError::InvalidArgument.to_u64(); - } - let total = args.keep_n.saturating_add(args.add_n); - if total > MAX_NAMESPACE_ENTRIES as u64 { - return SyscallError::InvalidArgument.to_u64(); - } - if args.names_len > (MAX_NAMESPACE_ENTRIES * MAX_SERVICE_NAME) as u64 { - return SyscallError::InvalidArgument.to_u64(); - } - let names = match ctx.user_vec(UserAddr::new(args.names_ptr), args.names_len) { - Ok(bytes) => bytes, - Err(e) => return e.to_u64(), - }; - let name_at = |off: u32, len: u32| -> Option> { - let end = (off as usize).checked_add(len as usize)?; - let bytes = names.get(off as usize..end)?; - Some(alloc::string::String::from(core::str::from_utf8(bytes).ok()?).into_boxed_str()) - }; - - let mut entries: Vec<(alloc::boxed::Box, alloc::sync::Arc)> = - Vec::new(); - - if keep_all { - let base = match process::with_handles(|handles| { - handles.get::(args.base, Rights::READ) - }) { - Ok(base) => base, - Err(e) => return e.refuse(), - }; - for (name, connector) in base.entries() { - entries.push((name.clone(), connector.clone())); - } - } - - if args.keep_n > 0 { - let Some(keep) = (args.keep_n as usize) - .checked_mul(core::mem::size_of::()) - .and_then(|len| ctx.user_bytes(UserAddr::new(args.keep_ptr), len as u64)) - else { - return SyscallError::BadAddress.to_u64(); - }; - let base = match process::with_handles(|handles| { - handles.get::(args.base, Rights::READ) - }) { - Ok(base) => base, - Err(e) => return e.refuse(), - }; - for i in 0..args.keep_n as usize { - let mut raw = [0u8; 8]; - if keep.read_at(i * 8, &mut raw).is_err() { - return SyscallError::BadAddress.to_u64(); - } - let off = u32::from_ne_bytes([raw[0], raw[1], raw[2], raw[3]]); - let len = u32::from_ne_bytes([raw[4], raw[5], raw[6], raw[7]]); - let Some(name) = name_at(off, len) else { - return SyscallError::InvalidArgument.to_u64(); - }; - // A name absent from the base is silently absent from the child: narrowing asks for an intersection, not an error. - if let Some(connector) = base.lookup(&name) { - entries.push((name, connector.clone())); - } - } - } - - if args.add_n > 0 { - let Some(add) = (args.add_n as usize) - .checked_mul(core::mem::size_of::()) - .and_then(|len| ctx.user_bytes(UserAddr::new(args.add_ptr), len as u64)) - else { - return SyscallError::BadAddress.to_u64(); - }; - for i in 0..args.add_n as usize { - let mut raw = [0u8; 16]; - if add.read_at(i * 16, &mut raw).is_err() { - return SyscallError::BadAddress.to_u64(); - } - let off = u32::from_ne_bytes([raw[0], raw[1], raw[2], raw[3]]); - let len = u32::from_ne_bytes([raw[4], raw[5], raw[6], raw[7]]); - let handle = RawHandle(u32::from_ne_bytes([raw[8], raw[9], raw[10], raw[11]])); - let Some(name) = name_at(off, len) else { - return SyscallError::InvalidArgument.to_u64(); - }; - let connector = match process::with_handles(|handles| { - handles.get_sent::(handle, Rights::TRANSFER) - }) { - Ok(c) => c, - Err(e) => return e.refuse(), - }; - entries.push((name, connector)); - } - } - - let namespace = match crate::object::namespace::Namespace::build(entries) { - Ok(ns) => ns, - Err(crate::object::namespace::BuildError::TooMany) => { - return SyscallError::InvalidArgument.to_u64() - } - Err(crate::object::namespace::BuildError::Duplicate) => { - return SyscallError::AlreadyExists.to_u64() - } - Err(crate::object::namespace::BuildError::Exhausted) => { - return SyscallError::ResourceExhausted.to_u64() - } - }; - process::with_handles(|handles| { - handle_result(ops::install(handles, KObjectRef::Namespace(namespace))) - }) -} - -/// Open a connection to `name` in the namespace `ns_h` holds; `NotFound` means the namespace lacks the name, `Gone` means its port has closed. -pub(super) fn sys_namespace_open(ns_h: RawHandle, name: &str) -> u64 { - let connector = match process::with_handles(|handles| { - let ns = handles.get::(ns_h, Rights::READ)?; - Ok::<_, crate::object::HandleError>(ns.lookup(name).cloned()) - }) { - Ok(Some(c)) => c, - Ok(None) => return SyscallError::NotFound.to_u64(), - Err(e) => return e.refuse(), - }; - connect_through(&connector) -} - -/// Inspect only the names this namespace already holds. The Arc keeps the -/// immutable entries alive after the handle-table lock is released; copying -/// into user memory therefore holds no object or handle-table lock. -pub(super) fn sys_namespace_names(ns_h: RawHandle, out: &mut crate::user_ptr::UserBytesMut) -> u64 { - let namespace = match process::with_handles(|handles| { - handles.get::(ns_h, Rights::READ) - }) { - Ok(namespace) => namespace, +/// Open a connection through the connector `h` names, which needs `WRITE`: the client's end is installed and the server's queued on the port. +/// `Gone` for a port whose acceptor closed; a handle that is no connector is `InvalidArgument`, since a connector is one a peer sent. +pub(super) fn sys_connect(h: RawHandle) -> u64 { + let connector = match process::with_handles(|handles| handles.get_sent::(h, Rights::WRITE)) { + Ok(connector) => connector, Err(e) => return e.refuse(), }; - let required: usize = namespace.entries().iter().map(|(name, _)| 2 + name.len()).sum(); - if out.len() >= required { - let mut at = 0; - for (name, _) in namespace.entries() { - let wrote = out.write_at(at, &(name.len() as u16).to_le_bytes()).and_then(|()| out.write_at(at + 2, name.as_bytes())); - if let Err(faulted) = wrote { - return SyscallError::from(faulted).to_u64(); - } - at += 2 + name.len(); - } - } - required as u64 -} - -/// The client half of a connection, and the server half queued on the port. -fn connect_through(connector: &port::Connector) -> u64 { if connector.closed() { return SyscallError::Gone.to_u64(); } diff --git a/kernel/src/syscall/machine.rs b/kernel/src/syscall/machine.rs index b9814f1c9a9..5d3e330e310 100644 --- a/kernel/src/syscall/machine.rs +++ b/kernel/src/syscall/machine.rs @@ -8,6 +8,7 @@ use alloc::vec::Vec; use core::sync::atomic::{AtomicBool, Ordering}; use crate::log::console::StopWire; +use crate::mm::charge::{self, Charge}; use crate::power; use crate::user_ptr::{SyscallContext, UserBytes, UserBytesMut}; use crate::UserAddr; @@ -255,10 +256,20 @@ pub(super) fn sys_sysinfo(syscap: RawHandle, out: &mut UserBytesMut) -> u64 { return e.refuse(); } } - match sysinfo_laid(max_entries) { - Ok(laid) => out.write_at(0, &laid).map_or_else(|faulted| SyscallError::from(faulted).to_u64(), |()| laid.len() as u64), - Err(e) => e.to_u64(), - } + let laid = match sysinfo_laid(max_entries) { + Ok(laid) => laid, + Err(e) => return e.to_u64(), + }; + // A roster is charged to the caller's job for the copy-out, which can + // fault and wait; a header alone is a fixed few bytes, and never refused. + let charged = match max_entries { + 0 => Ok(None), + _ => Charge::current(charge::array::(laid.capacity())).map(Some), + }; + let Ok(_held) = charged else { + return SyscallError::ResourceExhausted.to_u64(); + }; + out.write_at(0, &laid).map_or_else(|faulted| SyscallError::from(faulted).to_u64(), |()| laid.len() as u64) } /// [`sys_sysinfo`]'s answer, its header and at most `max_entries` entries, @@ -276,9 +287,6 @@ fn sysinfo_laid(max_entries: usize) -> Result, SyscallError> { let table = guard.as_ref().unwrap(); let entry_count = live_threads(table) as u32; - if entry_count as usize > sysinfo_thread_bound() { - return Err(SyscallError::ResourceExhausted); - } let mut header = [0u8; HEADER_SIZE]; header[0..8].copy_from_slice(&total_mem.to_le_bytes()); @@ -291,10 +299,14 @@ fn sysinfo_laid(max_entries: usize) -> Result, SyscallError> { let mut laid = Vec::new(); laid.extend_from_slice(&header); - // Header-only callers pay for no sort of the roster below. + // Header-only callers pay for no sort of the roster below, and the bound + // guards the roster's allocations alone. if max_entries == 0 { return Ok(laid); } + if entry_count as usize > sysinfo_thread_bound() { + return Err(SyscallError::ResourceExhausted); + } laid.reserve_exact(max_entries.min(entry_count as usize) * ENTRY_SIZE); let mut entries: Vec<(process::Tid, &process::ProcessEntry, &process::ThreadEntry)> = diff --git a/kernel/src/syscall/proc.rs b/kernel/src/syscall/proc.rs index 8b20aa50e1c..174abfa09aa 100644 --- a/kernel/src/syscall/proc.rs +++ b/kernel/src/syscall/proc.rs @@ -66,6 +66,19 @@ pub(super) fn spawn_place(place: u64) -> Result { .map_err(|refused| refused.refuse()) } +/// `SpawnArgs::job`: the caller's own, or the job a handle carrying `WRITE` names — a handle a peer sent, as a place is. +pub(super) fn spawn_job(job: u64) -> Result, u64> { + if job == u64::from(toyos_abi::HANDLE_INVALID.0) { + return Ok(process::with_process_data(|data| data.job.clone())); + } + let Ok(raw) = u32::try_from(job) else { + return Err(SyscallError::InvalidArgument.to_u64()); + }; + process::with_handles(|handles| handles.get_sent::(RawHandle(raw), Rights::WRITE)) + .map(|object| object.job().clone()) + .map_err(|refused| refused.refuse()) +} + /// The program `args` lays out: each range checked as an ordinary mapping, /// and each object it names looked up with the rights its range needs before /// an endowment could move the handle naming it. @@ -238,27 +251,6 @@ pub(super) fn sys_job_own() -> u64 { } } -/// Answer this process's endowment table; an empty buffer asks only for its size. -pub(super) fn sys_endowments(out: &mut crate::user_ptr::UserBytesMut) -> u64 { - let data_arc = process::process_data(); - let data = data_arc.lock(); - let needed = data.endowments.encoded_len(); - if out.is_empty() { - return needed as u64; - } - // Refused rather than truncated: a partial table would look up labels it lacks. - if out.len() < needed { - return SyscallError::InvalidArgument.to_u64(); - } - let mut buf = alloc::vec![0u8; needed]; - data.endowments.encode(&mut buf); - drop(data); - match out.write_at(0, &buf) { - Ok(()) => needed as u64, - Err(faulted) => SyscallError::from(faulted).to_u64(), - } -} - /// Point the calling thread's thread pointer at `tp` (x86-64's FS base, /// AArch64's `TPIDR_EL0`), which the thread keeps across every switch, and /// answer its tid. The kernel never reads through it: what lies there is the diff --git a/kernel/src/user_ptr.rs b/kernel/src/user_ptr.rs index eb02760de11..5fe35d4d086 100644 --- a/kernel/src/user_ptr.rs +++ b/kernel/src/user_ptr.rs @@ -21,7 +21,6 @@ //! Every single-word user read is `read_volatile`, including the futex word //! and the crash dump's walk, both outside this module. -use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::marker::PhantomData; @@ -33,9 +32,6 @@ use toyos_abi::UserSafe; use crate::arch::uaccess; use crate::UserAddr; -/// Longest string, in bytes, the kernel accepts from userspace; one bound for every string syscall, since each copies or tokenizes rather than streaming. -pub const MAX_USER_STR: u64 = 64 * 1024; - pub(crate) use toyos_userbound::Access; fn asked(access: Access) -> Asked { @@ -110,15 +106,6 @@ impl<'a> SyscallContext<'a> { Some(UserBytesMut(View::window(ptr, len)?, PhantomData)) } - /// Copy a user string of at most [`MAX_USER_STR`] bytes into kernel memory; an over-long or non-UTF-8 string is `InvalidArgument`, not `BadAddress`. - pub fn user_str(&self, ptr: UserAddr, len: u64) -> Result { - if len > MAX_USER_STR { - return Err(SyscallError::InvalidArgument); - } - let bytes = self.user_vec(ptr, len)?; - String::from_utf8(bytes).map_err(|_| SyscallError::InvalidArgument) - } - /// Read a typed value out of user memory, copied rather than borrowed. pub fn copy_in(&self, ptr: UserAddr) -> Result { let view = object::(ptr)?; diff --git a/lib/manifest/src/authorize.rs b/lib/manifest/src/authorize.rs index e9c9d5bc3e5..812c14f3447 100644 --- a/lib/manifest/src/authorize.rs +++ b/lib/manifest/src/authorize.rs @@ -1,7 +1,7 @@ //! What a row may do to the machine's disks beyond its own partitions, which //! only the signed image grants: `authorize = [...]` in its row. The //! supervisor mints each at every start of the row and puts it in the -//! row's namespace under [`Authorization::name`]. The build refuses one that +//! row's services under [`Authorization::name`]. The build refuses one that //! changes a disk to a login authority or a row a login can start, and no //! `[apps]` row can spell one. @@ -35,7 +35,7 @@ impl Authorization { Self::ALL.into_iter().find(|a| a.word() == word) } - /// The name it has in the row's namespace. + /// The name it has among the row's services. pub const fn name(self) -> &'static str { match self { Self::Tables => "block-tables", diff --git a/lib/manifest/src/lib.rs b/lib/manifest/src/lib.rs index bef2a07675b..5c2f81575a0 100644 --- a/lib/manifest/src/lib.rs +++ b/lib/manifest/src/lib.rs @@ -18,7 +18,7 @@ //! arg argv after argv[0] //! serve the supervisor makes one machine-wide port and endows the acceptor //! provide this program makes its own port, once per instance -//! receive a connector in this program's namespace +//! receive a connector to a service this program is given //! device a claim the supervisor mints and endows ([`device`]) //! drive one process of it per PCI function the matcher names //! syscap a right on the SysCap dup the supervisor endows @@ -118,7 +118,7 @@ pub const STATE: &str = "/state"; /// One directory a file-server role serves. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RoleDir { - /// What every program's namespace calls it, after `fs:`: `/home`. + /// What every program calls it among its services, after `fs:`: `/home`. pub dir: &'static str, /// Where it is on the role's volume, which a grant on it names /// (`toyos::fs::Grant`): DATA keeps its directories side by side, and @@ -136,6 +136,10 @@ const fn dir(dir: &'static str, root: &'static str) -> RoleDir { /// The role serving ROOT, which the supervisor starts before it reads a file. pub const SYSTEM: &str = "system"; +/// What the supervisor calls ROOT's bytes, the memory nobody writes the +/// kernel handed it, where it endows them to the server of [`SYSTEM`]. +pub const ROOT_IMAGE_LABEL: &str = "boot:root"; + /// The file-server roles, and the directories each serves: one port per role, /// and one grant on it per directory. pub const ROLES: [(&str, &[RoleDir]); 5] = [ @@ -156,7 +160,7 @@ pub fn role_dirs(role: &str) -> Option<&'static [RoleDir]> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct View { pub role: &'static str, - /// What the program's namespace calls it, after `fs:`. + /// What the program calls it among its services, after `fs:`. pub dir: String, /// Where it is on the role's volume. pub root: String, @@ -326,7 +330,7 @@ pub struct Program { /// Ports this program makes for itself, once per instance. The supervisor creates /// nothing and holds nothing for these. pub provides: Vec, - /// Names in this program's namespace, each a connector. + /// The services this program is given, each a connector. pub receives: Vec, /// Claims the supervisor mints for the one process of this row ([`device::Entry`]). pub devices: Vec, diff --git a/lib/session/Cargo.toml b/lib/session/Cargo.toml index 72ae1833e2c..2bee7398c0a 100644 --- a/lib/session/Cargo.toml +++ b/lib/session/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" doctest = false [dependencies] -# A login authority's open of a session, through its namespace. +# A login authority's open of a session, through its login connector. toyos = { path = "../../sdk/toyos" } toyos-abi = { path = "../../sdk/abi" } diff --git a/lib/session/src/lib.rs b/lib/session/src/lib.rs index 500af22e567..ca01b6212c4 100644 --- a/lib/session/src/lib.rs +++ b/lib/session/src/lib.rs @@ -32,19 +32,11 @@ pub const ADMIN_HOLDER: &str = "logout"; /// The session owner's row. pub const OWNER: &str = "session"; -/// The label of the namespace the session owner connects its link to the -/// supervisor through, holding [`LINK`]: the owner connects, so the link is -/// counted to the owner's job and not the supervisor's. +/// The label of the connector the session owner connects its link to the +/// supervisor through: the owner connects, so the link is counted to the +/// owner's job and not the supervisor's. pub const LINK_LABEL: &str = "session-link"; -/// The name of the link in the owner's [`LINK_LABEL`] namespace. -pub const LINK: &str = "link"; - -/// The name of a session's controls in a namespace a login authority is -/// handed: it connects its control connection itself, which is then its -/// own, as a launcher's is. -pub const CONTROL: &str = "control"; - /// The label of the session launcher's acceptor in the session owner. pub const LAUNCHER_ACCEPTOR_LABEL: &str = "session-launcher"; @@ -53,14 +45,14 @@ pub const LAUNCHER_ACCEPTOR_LABEL: &str = "session-launcher"; pub const CONTROL_ACCEPTOR_LABEL: &str = "session-controls"; /// The label a login authority the supervisor started with its session holds -/// that session's controls under, a namespace holding [`CONTROL`]. +/// that session's controls under: a connector, through which it connects its +/// control connection itself, which is then its own, as a launcher's is. pub const CONTROL_LABEL: &str = "session-control"; /// On [`LOGIN_PORT`]: open a remote session. No payload. pub const MSG_OPEN: u32 = 1; -/// The answer: the session's launcher, as a namespace holding -/// `toyos::launch::LAUNCHER`, then its controls, a namespace holding -/// [`CONTROL`]. +/// The answer: the session's launcher's connector, then its controls' +/// connector. pub const MSG_OPENED: u32 = 2; /// The answer to a request refused: the reason, UTF-8. pub const MSG_REFUSED: u32 = 3; @@ -275,23 +267,21 @@ impl Listed { /// still hands out after this long is one it does not read as ended. const ENDED_UNREAD: std::time::Duration = std::time::Duration::from_secs(5); -/// A session opened on [`LOGIN_PORT`] through `login`, the namespace a -/// login authority is endowed under that name: its launcher, as a namespace -/// holding `toyos::launch::LAUNCHER`, and its control connection. +/// A session opened on [`LOGIN_PORT`] through `login`, the connector a +/// login authority is endowed under that name: its launcher's connector, +/// and its control connection. /// /// **A session whose controls are gone is asked for again**, at once: its /// owner ended, and the supervisor answers from what it has read of that /// end, which an ask can outrun more than once. The asking ends with a /// session whose controls connect, with the supervisor's refusal, which a /// failing owner's budget comes to, or after [`ENDED_UNREAD`] as an error. -pub fn open( - login: &toyos::namespace::Namespace, -) -> Result<(toyos::namespace::Namespace, toyos::ipc::Connection), String> { +pub fn open(login: &toyos::port::Connector) -> Result<(toyos::port::Connector, toyos::ipc::Connection), String> { let mut rx = Box::new(toyos::ipc::FrameRx::<{ toyos::ipc::MAX_FRAME_LEN as usize }>::new()); let began = std::time::Instant::now(); let mut asks = 0u32; loop { - let conn = login.open(LOGIN_PORT).map_err(|e| format!("the supervisor's login port refused: {e:?}"))?; + let conn = login.connect().map_err(|e| format!("the supervisor's login port refused: {e:?}"))?; conn.signal(MSG_OPEN).map_err(|e| format!("the open was not sent: {e:?}"))?; let frame = rx.recv(&conn).map_err(|e| format!("the open was not answered: {e:?}"))?; if frame.msg_type != MSG_OPENED { @@ -299,12 +289,12 @@ pub fn open( return Err(format!("the supervisor refused a session: {why}")); } let [launcher, controls] = rx.take_handles::<2>().ok_or("the session came without its handles")?; - let namespace = |handle: toyos::ipc::ReceivedHandle| { - handle.typed::().map_err(|why| format!("the session's handles are not namespaces: {why}")) + let connector = |handle: toyos::ipc::ReceivedHandle| { + handle.typed::().map_err(|why| format!("the session's handles are not connectors: {why}")) }; - let (launcher, controls) = (namespace(launcher)?, namespace(controls)?); + let (launcher, controls) = (connector(launcher)?, connector(controls)?); asks += 1; - match controls.open(CONTROL) { + match controls.connect() { Ok(control) => return Ok((launcher, control)), Err(toyos_abi::syscall::SyscallError::Gone) if began.elapsed() < ENDED_UNREAD => {} Err(toyos_abi::syscall::SyscallError::Gone) => { diff --git a/lib/swap/src/lib.rs b/lib/swap/src/lib.rs index 7dabd64e5db..6cbd0e9284a 100644 --- a/lib/swap/src/lib.rs +++ b/lib/swap/src/lib.rs @@ -39,10 +39,10 @@ /// session. pub const PORT: &str = toyos_manifest::SWAP_PORT; -/// The endowment label [`PORT`] reaches its holder under: a namespace holding -/// that one name, and never an entry of the holder's `svc` namespace — which -/// std hands, duplicated, to every program the holder spawns directly, so a -/// name in it is a name whatever the holder runs undeclared would hold too. +/// The endowment label [`PORT`]'s connector reaches its holder under, never +/// as one of the holder's services, which std hands, duplicated, to every +/// program the holder spawns directly, so a service is one whatever the +/// holder runs undeclared would hold too. pub const LABEL: &str = "swap"; /// The one program a build may let receive [`PORT`]: `/system/bin/swap`, diff --git a/sdk/abi/src/boot.rs b/sdk/abi/src/boot.rs index f1e4bebcf41..e60101e3c99 100644 --- a/sdk/abi/src/boot.rs +++ b/sdk/abi/src/boot.rs @@ -111,6 +111,20 @@ pub struct BootImage { pub len: u64, } +/// What the kernel starts the supervisor holding, beside every process's own +/// two handles: the machine's one full system capability, then three memory +/// objects nobody writes, the file server's executable and ROOT's bytes as +/// the loader verified them, and what the loader wrote of where it booted +/// from, which arrives carrying `MAP` alone. +pub mod supervisor { + use crate::RawHandle; + + pub const SYSCAP: RawHandle = RawHandle::endowed(0); + pub const FILESERVER_IMAGE: RawHandle = RawHandle::endowed(1); + pub const ROOT_IMAGE: RawHandle = RawHandle::endowed(2); + pub const BOOT_NAMES: RawHandle = RawHandle::endowed(3); +} + /// The bytes of [`KernelArgs::gop_edid`]: one EDID base block. pub const EDID_BLOCK: usize = 128; diff --git a/sdk/abi/src/handle.rs b/sdk/abi/src/handle.rs index 893460d11b4..5c7c961760c 100644 --- a/sdk/abi/src/handle.rs +++ b/sdk/abi/src/handle.rs @@ -59,6 +59,25 @@ impl RawHandle { (self.0 & Self::SLOT_MASK) as u16 } + /// Every process's handle to itself, carrying [`Rights::WRITE`], + /// [`Rights::DUP`] and [`Rights::TRANSFER`]: what it hands on for a child + /// to be placed under it (`SpawnArgs::place`). The first handle the + /// kernel puts in a new table. + pub const SELF: RawHandle = RawHandle::new(0, 0); + + /// Every process's notice, readable while a quit has asked it a reason + /// it has not read, which a read takes (`SYS_PROCESS_QUIT`). The second + /// handle the kernel puts in a new table. + pub const NOTICE: RawHandle = RawHandle::new(1, 0); + + /// The `i`th handle a process was endowed at its start, in the order its + /// spawn listed them (`SpawnArgs::endow_ptr`): a new table's handles + /// follow [`SELF`](Self::SELF) and [`NOTICE`](Self::NOTICE) in that order. + pub const fn endowed(i: usize) -> RawHandle { + assert!(i < crate::syscall::MAX_SPAWN_ENDOWMENTS); + RawHandle::new(2 + i as u16, 0) + } + pub const fn generation(self) -> u32 { self.0 >> Self::SLOT_BITS } @@ -74,7 +93,8 @@ pub const HANDLE_INVALID: RawHandle = RawHandle(u32::MAX); /// /// **A kind's word is ABI: it is never renumbered and never given to another /// kind.** A new kind takes the next word, a kind that goes leaves its word -/// unused for good, and a receiver refuses a word it does not know. +/// unused for good, as 8, the namespace's, is, and a receiver refuses a word it +/// does not know. #[repr(u32)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum ObjectKind { @@ -86,7 +106,6 @@ pub enum ObjectKind { Inbox = 5, SharedMem = 6, Connector = 7, - Namespace = 8, SysCap = 9, Process = 10, Null = 11, @@ -101,7 +120,8 @@ pub enum ObjectKind { } impl ObjectKind { - pub const ALL: [ObjectKind; 20] = [ + /// Every kind, in word order. + pub const ALL: [ObjectKind; 19] = [ Self::PipeRead, Self::PipeWrite, Self::Connection, @@ -110,7 +130,6 @@ impl ObjectKind { Self::Inbox, Self::SharedMem, Self::Connector, - Self::Namespace, Self::SysCap, Self::Process, Self::Null, @@ -124,19 +143,27 @@ impl ObjectKind { Self::DeviceSet, ]; + /// The kind `word` names: `None` for a word no kind has, a retired one's among them. pub const fn from_word(word: u32) -> Option { - if (word as usize) < Self::ALL.len() { Some(Self::ALL[word as usize]) } else { None } + let mut i = 0; + while i < Self::ALL.len() { + if Self::ALL[i] as u32 == word { + return Some(Self::ALL[i]); + } + i += 1; + } + None } } -// `from_word` indexes `ALL` by the word: a row out of order would count a -// kind's neighbour. +// `ALL` lists each kind once, in word order, and the namespace's word stays retired. const _: () = { - let mut i = 0; + let mut i = 1; while i < ObjectKind::ALL.len() { - assert!(ObjectKind::ALL[i] as usize == i, "ObjectKind::ALL is out of word order"); + assert!((ObjectKind::ALL[i - 1] as u32) < ObjectKind::ALL[i] as u32, "ObjectKind::ALL is out of word order"); i += 1; } + assert!(ObjectKind::from_word(8).is_none(), "word 8, the namespace's, was reassigned"); }; /// What the holder of a handle may do with the object behind it. @@ -329,7 +356,7 @@ impl Rights { crate::user_safe! { /// One handle a move takes out of its holder's table, by a message - /// ([`message_send`]) or a spawn's endowment ([`SpawnEndow`]), and what it + /// ([`message_send`]) or a spawn's endowment (`SpawnArgs::endow_ptr`), and what it /// carries where it arrives. /// /// The mover needs [`Rights::TRANSFER`] on it and names its own set or @@ -338,7 +365,6 @@ crate::user_safe! { /// given a handle it cannot pass on. /// /// [`message_send`]: crate::syscall::message_send - /// [`SpawnEndow`]: crate::syscall::SpawnEndow #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct TransferEntry { pub handle: RawHandle, diff --git a/sdk/abi/src/syscall.rs b/sdk/abi/src/syscall.rs index d4e5763dca3..31bd2316bc8 100644 --- a/sdk/abi/src/syscall.rs +++ b/sdk/abi/src/syscall.rs @@ -111,15 +111,6 @@ pub const SYS_SET_THREAD_NAME: u64 = 95; /// [`device_reg_read`]. pub const SYS_DEVICE_REG_READ: u64 = 97; -/// Read this process's endowment table back: the `(label, handle)` pairs its -/// parent gave it at spawn, as an `[EndowEntry]` count followed by the entries -/// and the label blob they index into. `buf_len == 0` asks how many bytes the -/// answer needs. See [`endowments`]. -/// -/// The handles themselves are in the table whether or not this is ever called — -/// the labels are *names* for them, not the authority. -pub const SYS_ENDOWMENTS: u64 = 99; - /// Make a port: one [`Acceptor`] for the server, one `Connector` for its /// clients, packed `(acceptor << 32) | connector`. See [`port_create`]. /// @@ -134,12 +125,8 @@ pub const SYS_ENDOWMENTS: u64 = 99; /// /// [`Acceptor`]: port_create pub const SYS_PORT_CREATE: u64 = 100; -/// Build a namespace from a base and a set of `(name, connector)` additions. -/// See [`NamespaceBuild`]. -pub const SYS_NAMESPACE_BUILD: u64 = 101; -/// Open a connection to a name **in a namespace this process holds**. There is -/// no other place to ask, and a name it was not given resolves to nothing. -pub const SYS_NAMESPACE_OPEN: u64 = 102; +/// Open a connection through a connector this process holds. See [`connect`]. +pub const SYS_CONNECT: u64 = 102; /// Make a shared-memory region, sized up to whole [`PAGE_SIZE`]s. /// See [`shm_create`]. @@ -355,8 +342,6 @@ pub const SYS_DEVICE_SET_READ: u64 = 167; pub const SYS_YIELD: u64 = 168; /// The caller's last words, once per boot. See [`log_last_words`]. pub const SYS_LOG_LAST_WORDS: u64 = 169; -/// List the names held by a namespace, without opening any connector. -pub const SYS_NAMESPACE_NAMES: u64 = 170; /// Point the calling thread's thread pointer somewhere. See [`set_thread_pointer`]. pub const SYS_SET_THREAD_POINTER: u64 = 171; @@ -386,8 +371,12 @@ crate::user_safe! { /// so a parent that wants to keep what it endows duplicates first. That is /// what makes endowing a device claim work with no special case: a claim /// carries no [`Rights::DUP`], so the move is the only expressible form and - /// the parent provably no longer holds it. A child's descriptors are - /// endowments labelled [`FD_PREFIX`] and their number. + /// the parent provably no longer holds it. + /// + /// **The child's table starts in a stated order**: [`RawHandle::SELF`], + /// [`RawHandle::NOTICE`], then the endowments as `endow` lists them, the + /// `i`th at [`RawHandle::endowed`]. The kernel holds no name for any of + /// them: what a parent calls each is bytes of the child's memory. /// /// [`Rights::DUP`]: crate::handle::Rights::DUP #[derive(Clone, Copy)] @@ -402,12 +391,10 @@ crate::user_safe! { /// authority. `InvalidArgument` unless [`process_name`] takes it. pub name_ptr: u64, pub name_len: u64, - /// `[SpawnEndow]`, moved out of the parent's table. + /// `[TransferEntry]`, at most [`MAX_SPAWN_ENDOWMENTS`], each moved out + /// of the caller's table with the rights it names. pub endow_ptr: u64, pub endow_count: u64, - /// The label blob every [`SpawnEndow`]'s `label_off`/`label_len` indexes. - pub labels_ptr: u64, - pub labels_len: u64, /// The child's memory, `[SpawnMap]`, as the caller laid out its program: /// 1 to [`MAX_SPAWN_MAPS`] ranges, none meeting another or the clock /// page, each checked as an ordinary mapping (`SpawnMap`). Everything @@ -428,7 +415,7 @@ crate::user_safe! { pub build_id_len: u64, /// The process the child is placed under, whose end takes it down: a /// handle carrying [`Rights::WRITE`] to it — a copy of that process's - /// [`SELF_LABEL`] — or [`HANDLE_INVALID`] for the caller itself. + /// [`RawHandle::SELF`] — or [`HANDLE_INVALID`] for the caller itself. /// `PermissionDenied` for a handle without `WRITE` and `InvalidArgument` /// for one to no process, since a place is a handle a peer sent; /// `Gone` for a process being torn down, and `ResourceExhausted` for a @@ -436,6 +423,14 @@ crate::user_safe! { /// /// [`Rights::WRITE`]: crate::handle::Rights::WRITE pub place: u64, + /// The job the child runs in, the owner its threads, and its + /// children's unless a spawn names another, share CPU time under: a + /// handle carrying [`Rights::WRITE`] to a job — the caller's own is + /// [`job_own`]'s, and a new one [`job_create`]'s — or + /// [`HANDLE_INVALID`] for the caller's. `PermissionDenied` for a + /// handle without `WRITE` and `InvalidArgument` for one to no job. The + /// handle stays the caller's. + pub job: u64, /// A `SysCap` handle carrying [`Rights::PAGER`], which starts the /// child as a pager for its life, or [`HANDLE_INVALID`] for none. /// `PermissionDenied` for a handle without the right. The handle stays @@ -542,99 +537,10 @@ pub const MAX_SPAWN_MAPS: usize = 64; /// The most bytes of a build-id a spawn carries. pub const MAX_BUILD_ID: usize = 32; -crate::user_safe! { - /// One handle a spawn moves out of its caller's table into the child's, - /// and the label the child finds it under ([`EndowEntry`]): `label_off` - /// and `label_len` index [`SpawnArgs`]'s label blob. - #[derive(Clone, Copy)] - pub struct SpawnEndow { - pub label_off: u32, - pub label_len: u32, - pub moved: TransferEntry, - } -} - -const _: () = assert!(core::mem::size_of::() == 16); - -/// One `(label, handle)` pair of a process's endowment table, as -/// [`endowments`] answers it. -/// -/// `label_off`/`label_len` index the label blob that travels beside the -/// entries. The label is a *local name* in one process's own table and buys -/// nothing to guess: a name not in your table resolves to nothing. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct EndowEntry { - pub label_off: u32, - pub label_len: u32, - pub handle: RawHandle, - /// Named, so nothing leaks kernel stack into it. - pub _pad: u32, -} - -const _: () = assert!(core::mem::size_of::() == 16); - -/// The label the kernel puts on the supervisor's system capability, and the one -/// the supervisor puts on the `RT`-only dup it endows a `realtime` program. -/// -/// Here rather than in the SDK because the kernel writes it and userland reads -/// it, and a label spelled twice is a label that can be spelled two ways. -pub const SYSCAP_LABEL: &str = "syscap"; -/// The label for a program's namespace — what its manifest `receives` becomes. -pub const SVC_LABEL: &str = "svc"; -/// The labels the kernel puts on the immutable memory objects it hands the -/// supervisor beside the system capability: the file server's executable and -/// ROOT's bytes, both as the loader verified them, and what the loader wrote -/// of where it booted from (`boot::KernelArgs`). -pub const FILESERVER_IMAGE_LABEL: &str = "boot:fileserver"; -pub const ROOT_IMAGE_LABEL: &str = "boot:root"; -pub const BOOT_NAMES_LABEL: &str = "boot:names"; -/// The label every process starts holding a handle to itself under, carrying -/// `WRITE`, `DUP` and `TRANSFER`: what it hands on for a child to be placed -/// under it ([`SpawnArgs::place`]). The kernel puts it in every table. -pub const SELF_LABEL: &str = "self"; -/// The label every process starts holding its notice under: readable while a -/// quit has asked it a reason it has not read, which a read takes -/// ([`SYS_PROCESS_QUIT`]). The kernel puts it in every table. -pub const QUIT_LABEL: &str = "quit"; -/// The label a spawn endows a job under to put its child in it: the owner its -/// threads, and its children's unless a spawn names another, share CPU time -/// under. It takes a handle carrying [`Rights::WRITE`] to a job — the caller's -/// own is [`job_own`]'s, and a new one [`job_create`]'s. A spawn naming none -/// puts the child in its spawner's job. -/// -/// [`Rights::WRITE`]: crate::handle::Rights::WRITE -pub const JOB_LABEL: &str = "job"; -/// `serve:`: the acceptor of a machine-wide port this program serves. -pub const SERVE_PREFIX: &str = "serve:"; -/// `dev:`: the claim for a device class this program was given. -pub const DEV_PREFIX: &str = "dev:"; -/// `fd:`: the child's descriptor `n`, which its descriptor table installs -/// at that number (`toyos::fd`). Numbering is userland's: the kernel only -/// moves the handle. -pub const FD_PREFIX: &str = "fd:"; -/// `provide:`: a connector a *launching client* transferred, beside the -/// namespace entry the launcher made from it. -/// -/// **Both, and the second is what makes the chain work.** A namespace answers -/// with connections, not with connectors, so a process holding `surface` only -/// in its namespace cannot hand `surface` to a child — and the terminal → shell -/// → `locale` chain is exactly that. The connector arrives labelled as well, so -/// the holder can pass it on and the child's own manifest row still decides -/// everything else it gets. -pub const PROVIDE_PREFIX: &str = "provide:"; - -/// `(label, handle)` pairs one endowment table holds, the kernel's own -/// [`SELF_LABEL`] and [`QUIT_LABEL`] among them. Policy on the primitive, -/// refused by name, never truncated — the widest manifest row plus stdio. -pub const MAX_ENDOWMENTS: usize = 32; -/// `(label, handle)` pairs one spawn may carry: the kernel adds [`SELF_LABEL`] -/// and [`QUIT_LABEL`]. -pub const MAX_SPAWN_ENDOWMENTS: usize = MAX_ENDOWMENTS - 2; -/// Bytes of label blob one endowment table holds, the kernel's labels among them. -pub const MAX_LABELS_LEN: usize = 4096; -/// Bytes of label blob one spawn may carry: the kernel adds its two labels. -pub const MAX_SPAWN_LABELS_LEN: usize = MAX_LABELS_LEN - SELF_LABEL.len() - QUIT_LABEL.len(); +/// The most handles one spawn endows. Policy on the primitive, refused by +/// name, never truncated. Nothing here bounds what a parent calls them: names +/// are bytes of the child's startup block, which its parent lays out. +pub const MAX_SPAWN_ENDOWMENTS: usize = 96; use crate::end::End; use crate::handle::{ReceivedEntry, Rights, TransferEntry}; @@ -661,9 +567,9 @@ pub enum SyscallError { /// The object was there and its other end is not. /// /// **A different fact from `NotFound`, and the design does not work without - /// the difference.** "The name is not in the namespace this process was - /// given" is a statement about this process and the answer is "you have a - /// bug"; "the server exited" is a statement about the machine. The SDK sees + /// the difference.** "This process was not given that" is a statement about + /// this process and the answer is "you have a bug"; "the server exited" is + /// a statement about the machine. The SDK sees /// one `u64`, so if the kernel gives one word the SDK has one answer — and /// the same rule the storage layer already obeys applies here: a dead peer /// must not be indistinguishable from a handle that was never there. @@ -1191,16 +1097,6 @@ pub unsafe fn spawn(args: &SpawnArgs) -> Result { .map(|h| RawHandle(h as u32)) } -/// Read this process's endowment table into `buf`: an `[EndowEntry]` count and -/// entries followed by the label blob. Returns the bytes written, or — when -/// `buf` is empty — the bytes the answer needs. -/// -/// The one place a name is resolved to a handle at all: there is no global -/// registry, so a process learns what it holds only from its own table. -pub fn endowments(buf: &mut [u8]) -> usize { - syscall(SYS_ENDOWMENTS, buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0) as usize -} - /// Block until the process `proc` names has ended, and take how. pub fn process_wait(proc: RawHandle) -> Result { check(syscall(SYS_PROCESS_WAIT, proc.0 as u64, 0, 0, 0)).map(end_of) @@ -1824,8 +1720,8 @@ pub fn device_claim(syscap: RawHandle, request: DeviceRequest) -> Result Result { @@ -1934,7 +1830,7 @@ pub fn device_reg_read( .map(|v| v as u32) } -// Ports and namespaces +// Ports /// Both ends of a fresh port. /// @@ -1959,101 +1855,21 @@ pub fn port_create() -> Result { }) } -/// One `(name, connector)` pair `SYS_NAMESPACE_BUILD` adds. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct NamespaceEntry { - pub off: u32, - pub len: u32, - pub connector: RawHandle, - /// Named, so nothing leaks kernel stack into it. - pub _pad: u32, -} - -/// One name carried over from the base namespace. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct NameRef { - pub off: u32, - pub len: u32, -} - -crate::user_safe! { - /// Arguments for [`SYS_NAMESPACE_BUILD`], passed as a single pointer. - /// - /// A namespace is immutable once built: there is no insert, no remove and no - /// replace, so a narrower one is a *new* object built from this one and a - /// handle to a namespace is a handle to a fixed set. - #[derive(Clone, Copy)] - pub struct NamespaceBuild { - /// [`HANDLE_INVALID`] for an empty base. - /// - /// [`HANDLE_INVALID`]: crate::handle::HANDLE_INVALID - pub base: RawHandle, - /// [`NAMESPACE_KEEP_ALL`], and nothing else. - pub flags: u32, - /// `[NameRef]` — the names to carry over from `base`. - pub keep_ptr: u64, - pub keep_n: u64, - /// `[NamespaceEntry]` — new bindings. - pub add_ptr: u64, - pub add_n: u64, - /// The blob every `off`/`len` above indexes into. - pub names_ptr: u64, - pub names_len: u64, - } -} - -const _: () = assert!(core::mem::size_of::() == 16); -const _: () = assert!(core::mem::size_of::() == 8); - -/// Carry over every name `base` holds — no syscall enumerates a namespace, so -/// no `keep` list can spell it. `keep_n` must be `0` beside it, and a name it -/// carries that `add` also binds is `AlreadyExists`. -pub const NAMESPACE_KEEP_ALL: u32 = 1 << 0; -/// Every bit [`NamespaceBuild::flags`] defines. A caller that sets another is -/// refused `InvalidArgument`, never served the request it did not make. -pub const NAMESPACE_FLAGS_KNOWN: u32 = NAMESPACE_KEEP_ALL; - -/// Names one namespace may bind. Policy on the primitive; a caller asking for -/// one more is refused by name and never truncated. -pub const MAX_NAMESPACE_ENTRIES: usize = 64; -/// Bytes in one service name. -pub const MAX_SERVICE_NAME: usize = 64; - -/// # Safety -/// Every pointer in `args` must name `args`'s stated length of readable memory. -pub unsafe fn namespace_build(args: &NamespaceBuild) -> Result { - check(syscall(SYS_NAMESPACE_BUILD, args as *const _ as u64, 0, 0, 0)) - .map(|v| RawHandle(v as u32)) -} - -/// Open a connection to `name` in the namespace `ns` holds. +/// Open a connection to the port `connector` names, which needs +/// [`Rights::WRITE`] on it: the client's end is the answer, and the server's +/// waits on the port for its [`accept`]. /// -/// `NotFound` means the name is not in this namespace — a fact about this -/// process. [`SyscallError::Gone`] means the server that held the acceptor has -/// exited. There is no third answer, and in particular there is no "not yet": -/// the port exists before either process runs. -pub fn namespace_open(ns: RawHandle, name: &str) -> Result { - check(syscall( - SYS_NAMESPACE_OPEN, - ns.0 as u64, - name.as_ptr() as u64, - name.len() as u64, - 0, - )) - .map(|v| RawHandle(v as u32)) -} - -/// The namespace's names, sorted, encoded as a little-endian `u16` length -/// followed by that many UTF-8 bytes for each name. Requires READ. Returns -/// the complete required byte length; writes nothing when `out` is smaller. -/// A namespace is immutable, so a size query and a later read agree. -pub fn namespace_names(namespace: RawHandle, out: &mut [u8]) -> Result { - check(syscall(SYS_NAMESPACE_NAMES, namespace.0 as u64, out.as_mut_ptr() as u64, out.len() as u64, 0)).map(|n| n as usize) +/// [`SyscallError::Gone`] means the server that held the acceptor has exited, +/// and `ResourceExhausted` that the port's queue or the caller's table is +/// full. There is no "not yet": the port exists before either process runs. +/// `InvalidArgument` is a handle that is no connector, since a connector is +/// routinely a handle a peer sent. +/// +/// [`Rights::WRITE`]: crate::handle::Rights::WRITE +pub fn connect(connector: RawHandle) -> Result { + check(syscall(SYS_CONNECT, connector.0 as u64, 0, 0, 0)).map(|v| RawHandle(v as u32)) } - /// Bytes one badge may carry. Policy on the primitive: a badge is kernel /// memory no process is charged for, so the bound is what its one reader, the /// supervisor's launch authority, needs. @@ -2064,7 +1880,7 @@ pub const MAX_BADGE: usize = 64; /// `InvalidArgument`. /// /// **The kernel vouches for what was granted, never for who connects**: the -/// connector is duplicated, moved and put in a namespace like any other, and +/// connector is duplicated and moved like any other, and /// every copy stamps the same bytes. pub fn port_mint(acceptor: RawHandle, badge: &[u8]) -> Result { check(syscall(SYS_PORT_MINT, acceptor.0 as u64, badge.as_ptr() as u64, badge.len() as u64, 0)) @@ -2378,6 +2194,10 @@ pub fn log_last_words(syscap: RawHandle, text: &[u8]) -> Result<(), SyscallError /// `syscap`: a capability without the bit is refused with a word, and a handle /// the caller does not hold ends it. /// +/// A roster the kernel lays out is charged to the caller's job while it is +/// copied out; a job without the room is answered 0. A header alone is never +/// refused for memory. +/// /// [`HANDLE_INVALID`]: crate::handle::HANDLE_INVALID pub fn sysinfo(syscap: RawHandle, buf: &mut [u8]) -> usize { let n = syscall( diff --git a/sdk/libc/src/signal.rs b/sdk/libc/src/signal.rs index 3e85a5b1d6f..bbad5b195d9 100644 --- a/sdk/libc/src/signal.rs +++ b/sdk/libc/src/signal.rs @@ -124,14 +124,13 @@ impl State { keeper.knock(); } - /// Listen to the notice if the signals now ask it: `ENOSYS` when this - /// process holds no notice to listen to, so a quit would kill it. + /// Listen to the notice if the signals now ask it: `ENOSYS` when the + /// kernel refuses the listen, so a quit would kill it. fn listen(&mut self) -> Result<(), i32> { if self.listening || !self.signals.listens() { return Ok(()); } - let notice = toyos::quit::notice().ok_or(ENOSYS)?; - notice.listen().map_err(|_| ENOSYS)?; + toyos::quit::notice().listen().map_err(|_| ENOSYS)?; self.listening = true; self.kick(); Ok(()) @@ -494,8 +493,7 @@ unsafe extern "C" fn keep(_: *mut u8) -> *mut u8 { }; poller.watch(&control, READABLE, CONTROL); if listening { - let notice = toyos::quit::notice().expect("libc listens only to a notice it holds"); - poller.watch(notice, READABLE, NOTICE); + poller.watch(toyos::quit::notice(), READABLE, NOTICE); } for (pid, handle) in &children { poller.watch(handle, READABLE, *pid as u64); @@ -513,8 +511,7 @@ unsafe extern "C" fn keep(_: *mut u8) -> *mut u8 { } let mut raised = alloc::vec::Vec::new(); if asked { - let notice = toyos::quit::notice().expect("libc listens only to a notice it holds"); - while let Ok(Some(reason)) = notice.try_read() { + while let Ok(Some(reason)) = toyos::quit::notice().try_read() { raised.push(signal_of(reason)); } } diff --git a/sdk/libc/src/spawn.rs b/sdk/libc/src/spawn.rs index f9eef3e33ac..a3d4f28df6f 100644 --- a/sdk/libc/src/spawn.rs +++ b/sdk/libc/src/spawn.rs @@ -342,6 +342,8 @@ fn start(path: &str, argv: &[u8], env: &[u8], fds: &[(u32, toyos::BorrowedHandle cwd, fds, endow: Vec::new(), + inherit: toyos::spawn::Inherit::All, + job: None, provided: Vec::new(), parent: Parent::Caller, independent: false, diff --git a/sdk/std/os/fs.rs b/sdk/std/os/fs.rs index b930b0e66a6..0b4c04100eb 100644 --- a/sdk/std/os/fs.rs +++ b/sdk/std/os/fs.rs @@ -64,20 +64,21 @@ pub fn symlink, Q: AsRef>(original: P, link: Q) -> io::Resu crate::sys::fs::symlink(original.as_ref(), link.as_ref()) } -/// Resolve this process's files through `namespace`, a namespace handle it -/// gives up here. +/// Resolve this process's files through `services`, each a connector it +/// gives up here under the name it is a service by. /// -/// For the one process no parent endows a namespace: the supervisor builds the machine's -/// directory capabilities itself. Refused, and the handle closed, when this -/// process already resolves through one. +/// For the one process no parent endows a service: the supervisor makes the +/// machine's directory capabilities itself. No child inherits them. Refused, +/// and the connectors closed, when this process has adopted some already or +/// has resolved a file. /// /// # Safety -/// `namespace` names a namespace: the kernel ends a process that opens a name -/// through a handle to anything else. +/// Each handle names a connector: it is taken as one on the caller's word. #[stable(feature = "toyos_ext", since = "1.0.0")] -pub unsafe fn adopt_namespace(namespace: crate::os::toyos::io::OwnedHandle) -> io::Result<()> { +pub unsafe fn adopt_services(services: Vec<(String, crate::os::toyos::io::OwnedHandle)>) -> io::Result<()> { // SAFETY: the caller's. - let namespace = unsafe { toyos::endow::FromHandle::from_handle(namespace.into_inner()) }; - toyos::endow::adopt_namespace(namespace) - .map_err(|_| io::const_error!(io::ErrorKind::AlreadyExists, "this process already has a namespace")) + let services = services.into_iter().map(|(name, connector)| (name, unsafe { toyos::endow::FromHandle::from_handle(connector.into_inner()) })).collect(); + toyos::endow::adopt(services) + .map(drop) + .map_err(|_| io::const_error!(io::ErrorKind::AlreadyExists, "this process already resolves its files")) } diff --git a/sdk/std/os/process.rs b/sdk/std/os/process.rs index 2adc7cf0d45..53bf6e24cbd 100644 --- a/sdk/std/os/process.rs +++ b/sdk/std/os/process.rs @@ -55,19 +55,37 @@ pub trait CommandExt { #[stable(feature = "toyos_ext", since = "1.0.0")] fn endow_narrowed(&mut self, label: &str, handle: crate::os::toyos::io::OwnedHandle, rights: u32) -> &mut Self; - /// Put `connector` in an [`independent`](CommandExt::independent) - /// child's namespace under `name`, on top of what its manifest row holds. + /// Give an [`independent`](CommandExt::independent) child `connector` + /// as the service `name`, on top of what its manifest row holds. /// /// A terminal's `surface` port exists once per terminal, so no manifest /// can name it, but the shell's own row is what should decide the rest of /// what a shell holds. So the caller supplies this one connector, the - /// launcher supplies the row, and the child's namespace is the union. + /// launcher supplies the row, and the child's services are the union. A + /// child whose row does not receive `name` is not given it; one that is, + /// without `DUP`, `TRANSFER` and `WRITE`, refuses the launch. /// /// The connector is **moved**, like [`endow`](CommandExt::endow). A /// command that is not independent is refused `InvalidInput`. #[stable(feature = "toyos_ext", since = "1.0.0")] fn provide(&mut self, name: &str, connector: crate::os::toyos::io::OwnedHandle) -> &mut Self; + /// Run the child in the job `job` names, a handle carrying `WRITE`, + /// rather than in this process's own: the owner its threads share CPU + /// time under and its memory is counted to. The command takes a copy of + /// it now; an [`independent`](CommandExt::independent) launch carries it + /// too. + #[stable(feature = "toyos_ext", since = "1.0.0")] + fn job(&mut self, job: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self; + + /// Give a helper only those of this process's services `names` names, + /// and none where it is empty, rather than a duplicate of every one. A + /// service the command [`endow`](CommandExt::endow)s is the child's + /// either way. An [`independent`](CommandExt::independent) launch runs + /// its row, and is refused `InvalidInput`. + #[stable(feature = "toyos_ext", since = "1.0.0")] + fn services_only(&mut self, names: &[&str]) -> &mut Self; + /// Make now, on this thread, every file read this command's spawn needs: /// find the program, judge the working directory, and read a program on a /// file server into memory. The spawn then calls no file server for them — @@ -118,14 +136,14 @@ pub trait CommandExt { /// launcher this process holds: the program its manifest row declares, /// holding that row's authority, its own identity and the connectors /// [`provide`](CommandExt::provide)d. Without it a spawn is a helper, - /// which inherits this process's namespace, environment and the + /// which inherits this process's services, environment and the /// descriptors its spawn names. /// /// **A launch or nothing.** It is refused `PermissionDenied`, with the /// reason, where this process holds no launcher, the launcher will not /// open, or no row declares the program or this one may not start it; and - /// `InvalidInput` for an endowment other than a job, a descriptor past - /// stdio or an [`image_from`](CommandExt::image_from). It is never + /// `InvalidInput` for an endowment, a descriptor past stdio or an + /// [`image_from`](CommandExt::image_from). It is never /// started as a helper instead. A relative program is made absolute /// against the child's working directory. #[stable(feature = "toyos_ext", since = "1.0.0")] @@ -138,12 +156,12 @@ pub trait CommandExt { fn detached(&mut self) -> &mut Self; /// Make an [`independent`](CommandExt::independent) launch through the - /// launcher `namespace` names, a namespace holding `launcher` that this - /// process holds and keeps, in place of the one it was endowed: a login - /// authority's for a session it opened. It reaches only what that launcher - /// does. A command that is not independent is refused `InvalidInput`. + /// launcher `launcher` names, a connector this process holds and keeps, + /// in place of the one it was endowed: a login authority's for a session + /// it opened. It reaches only what that launcher does. A command that is + /// not independent is refused `InvalidInput`. #[stable(feature = "toyos_ext", since = "1.0.0")] - fn launcher(&mut self, namespace: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self; + fn launcher(&mut self, launcher: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self; /// Start the child as a pager, by `syscap`, a `SysCap` carrying /// `Rights::PAGER`, which this process keeps: it may then make the pager @@ -186,6 +204,16 @@ impl CommandExt for crate::process::Command { self } + fn job(&mut self, job: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self { + self.as_inner_mut().job(job); + self + } + + fn services_only(&mut self, names: &[&str]) -> &mut Self { + self.as_inner_mut().services_only(names); + self + } + fn prepare(&mut self) -> crate::io::Result<&mut Self> { self.as_inner_mut().prepare()?; Ok(self) @@ -220,8 +248,8 @@ impl CommandExt for crate::process::Command { self } - fn launcher(&mut self, namespace: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self { - self.as_inner_mut().launcher(namespace); + fn launcher(&mut self, launcher: crate::os::toyos::io::BorrowedHandle<'_>) -> &mut Self { + self.as_inner_mut().launcher(launcher); self } diff --git a/sdk/std/sys/fs.rs b/sdk/std/sys/fs.rs index adf0586eb99..22b6e806eda 100644 --- a/sdk/std/sys/fs.rs +++ b/sdk/std/sys/fs.rs @@ -1,4 +1,4 @@ -//! Files through the process's directory capabilities and namespace ancestors. +//! Files through the process's directory capabilities and their read-only ancestors. //! `toyos::files` owns routing, connections and descriptors shared with libc. use toyos::files::{self, At, Open}; diff --git a/sdk/std/sys/process.rs b/sdk/std/sys/process.rs index b145b73f761..d8d3be06a53 100644 --- a/sdk/std/sys/process.rs +++ b/sdk/std/sys/process.rs @@ -27,6 +27,10 @@ pub struct Command { extra_fds: Vec<(u32, Given)>, /// Taken by the next spawn: moved to its child, or closed if it is refused. endowments: Vec<(String, toyos::Moved)>, + /// The services of this process a helper is given, where not every one. + services: Option>, + /// A copy of the job the child joins, where it is not this process's. + job: Option>, provided: Vec<(String, toyos::OwnedHandle)>, parent: Parent, /// An independent launch, where a plain spawn is a helper. @@ -69,11 +73,6 @@ enum Parent { Detached, } -/// The job `endowments` puts the child in. -fn job_of(endowments: &[(String, toyos::Moved)]) -> Option> { - endowments.iter().find(|(label, _)| label == toyos::job::JOB_LABEL).map(|(_, handle)| toyos::AsHandle::as_handle(handle)) -} - /// A copy of a handle the caller lends, owned by the command until it is /// dropped or set again. fn copy(handle: crate::os::toyos::io::BorrowedHandle<'_>) -> io::Result { @@ -122,6 +121,8 @@ impl Command { stderr: None, extra_fds: Vec::new(), endowments: Vec::new(), + services: None, + job: None, provided: Vec::new(), parent: Parent::Caller, independent: false, @@ -177,7 +178,7 @@ impl Command { if !kept { self.image = None; if self.held_image.is_none() && !self.independent { - let image = crate::sys::fs::read_image(Path::new(&source), self.endowed_job())?; + let image = crate::sys::fs::read_image(Path::new(&source), self.child_job()?)?; self.image = Some((source, image)); } } @@ -258,10 +259,25 @@ impl Command { self.extra_fds.push((child_fd, given)); } - /// The job the child is endowed to run in, which its program image is - /// read for: the child keeps the image as long as it runs. - fn endowed_job(&self) -> Option> { - job_of(&self.endowments) + /// The job the child is to run in, which its program image is read for: + /// the child keeps the image as long as it runs. `None` for this + /// process's own. + fn child_job(&self) -> io::Result>> { + match &self.job { + None => Ok(None), + Some(Ok(job)) => Ok(Some(toyos::AsHandle::as_handle(job))), + Some(Err(e)) => Err(io::Error::new(e.kind(), e.to_string())), + } + } + + /// Run the child in the job a copy of `job` names, which carries `WRITE`. + pub fn job(&mut self, job: crate::os::toyos::io::BorrowedHandle<'_>) { + self.job = Some(copy(job)); + } + + /// Give a helper those of this process's services `names` names, and no other. + pub fn services_only(&mut self, names: &[&str]) { + self.services = Some(names.iter().map(|name| (*name).to_owned()).collect()); } /// Give the child `handle` under the name `label`, moved by the next spawn. @@ -291,9 +307,9 @@ impl Command { self.independent = true; } - /// Launch through the launcher `namespace` holds, which this process keeps. - pub fn launcher(&mut self, namespace: crate::os::toyos::io::BorrowedHandle<'_>) { - self.launcher = Some(copy(namespace)); + /// Launch through a copy of the launcher's connector `launcher`. + pub fn launcher(&mut self, launcher: crate::os::toyos::io::BorrowedHandle<'_>) { + self.launcher = Some(copy(launcher)); } /// Start the child as a pager, by a copy of `syscap`, which carries @@ -456,9 +472,10 @@ impl Command { io::const_error!(io::ErrorKind::InvalidInput, "the image is no memory region") } })?), - (None, None) => Some(crate::sys::fs::read_image(source, job_of(&endowments))?), + (None, None) => Some(crate::sys::fs::read_image(source, self.child_job()?)?), }; let (labels, endowed): (Vec, Vec) = endowments.into_iter().unzip(); + let only: Option> = self.services.as_ref().map(|names| names.iter().map(String::as_str).collect()); let (names, provided): (Vec, Vec) = provided.into_iter().unzip(); let request = toyos::spawn::Spawn { program: launched.as_deref().unwrap_or(program), @@ -469,6 +486,11 @@ impl Command { cwd: &cwd, fds: &fds, endow: labels.iter().map(String::as_str).zip(endowed).collect(), + inherit: match &only { + None => toyos::spawn::Inherit::All, + Some(names) => toyos::spawn::Inherit::Only(names), + }, + job: self.child_job()?, provided: names.iter().map(String::as_str).zip(provided).collect(), parent, independent: self.independent, diff --git a/sdk/toyos/Cargo.toml b/sdk/toyos/Cargo.toml index 8e3f39d26e1..e17f2fe1ab0 100644 --- a/sdk/toyos/Cargo.toml +++ b/sdk/toyos/Cargo.toml @@ -3,7 +3,7 @@ name = "toyos" version = "0.18.0" # never bumped; crates.io's is assigned on publish (build/sdkversion.rs) edition = "2021" license = "MIT OR Apache-2.0" -description = "The ToyOS userland SDK: typed handles, IPC, ports, namespaces, surfaces, net and the transport its sessions run over." +description = "The ToyOS userland SDK: typed handles, IPC, ports and the services a program was given, surfaces, net and the transport its sessions run over." repository = "https://github.com/ToyOSOrg/ToyOS" [features] diff --git a/sdk/toyos/src/audio.rs b/sdk/toyos/src/audio.rs index 47ba2f7edfc..25eda683146 100644 --- a/sdk/toyos/src/audio.rs +++ b/sdk/toyos/src/audio.rs @@ -344,7 +344,7 @@ impl AudioStream { let _ = self.control.signal(MSG_STREAM_CLOSE); } - /// One connection to soundserver, through this process's own namespace. + /// One connection to soundserver, through the connector this process was given. /// /// **There was a retry loop here and it is gone**, along with its twin in /// `net`. A `soundserver` connector is live from this process's first diff --git a/sdk/toyos/src/census.rs b/sdk/toyos/src/census.rs index 8ee21c2576d..b2d164241e2 100644 --- a/sdk/toyos/src/census.rs +++ b/sdk/toyos/src/census.rs @@ -26,7 +26,8 @@ impl Census { /// Live objects of one kind. pub fn kind(&self, kind: ObjectKind) -> u64 { - self.0[kind as usize] + let at = ObjectKind::ALL.iter().position(|listed| *listed == kind).expect("ObjectKind::ALL lists every kind"); + self.0[at] } pub fn total(&self) -> u64 { diff --git a/sdk/toyos/src/endow.rs b/sdk/toyos/src/endow.rs index e2da55d6793..d55ade9c03a 100644 --- a/sdk/toyos/src/endow.rs +++ b/sdk/toyos/src/endow.rs @@ -1,40 +1,76 @@ -//! What this process was given, and the only place a service name resolves. +//! What this process was given, and the only place a name resolves. //! //! There is no global registry and no connect-by-name. A process learns what -//! it holds from its own endowment table — `(label, handle)` pairs its parent -//! moved in at spawn — and resolves a service through the namespace in it. A -//! name it was not given resolves to nothing, and there is no second place to -//! ask. +//! it holds from its own endowment table: the handles its parent moved in at +//! spawn, and what its parent calls each, which the parent wrote into the +//! child's startup block ([`crate::startup`]). The kernel holds no name. A +//! name a process was not given resolves to nothing, and there is no second +//! place to ask. //! -//! **A label is a local name in one process's own table**, so guessing one buys +//! **A name is a local name in one process's own table**, so guessing one buys //! nothing: the table is not enumerable across processes and a name not in //! yours resolves to nothing wherever it came from. - +//! +//! **A service is a connector under [`SVC_PREFIX`] or [`PROVIDE_PREFIX`]**, +//! which stays in the table for the process's life: [`service`] connects +//! through it, and a helper this process spawns is given a duplicate +//! (`crate::spawn`). Every other name is taken, once ([`Endowments::take`]), +//! and goes to a child only by an endowment its spawn names. + +use alloc::string::String; +use alloc::vec::Vec; use core::cell::UnsafeCell; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicU32, AtomicU8, Ordering}; use toyos_abi::handle::{ObjectKind, HANDLE_INVALID}; -use toyos_abi::syscall::{ - self, DeviceRequest, DeviceType, SyscallError, MAX_ENDOWMENTS, MAX_LABELS_LEN, -}; +use toyos_abi::syscall::{DeviceRequest, DeviceType, SyscallError, MAX_SPAWN_ENDOWMENTS}; use crate::ipc::Connection; use crate::job::Job; -use crate::namespace::Namespace; use crate::port::{Acceptor, Connector}; use crate::process::Process; use crate::syscap::SysCap; -use crate::{Device, OwnedHandle, RawHandle}; +use crate::{AsHandle, BorrowedHandle, Device, OwnedHandle, RawHandle}; + +/// The name of the system capability a program's row grants it. +pub const SYSCAP_LABEL: &str = "syscap"; +/// `svc:`: a connector to a service this program's row gives it, a +/// directory capability's `fs:` among them. +pub const SVC_PREFIX: &str = "svc:"; +/// `provide:`: a connector to a service whoever launched this program +/// gave it beside its row, which receives the name, and which it passes on to +/// what it launches ([`provided`]). +pub const PROVIDE_PREFIX: &str = "provide:"; +/// `serve:`: the acceptor of a machine-wide port this program serves. +pub const SERVE_PREFIX: &str = "serve:"; +/// `dev:`: the claim for a device class this program was given. +pub const DEV_PREFIX: &str = "dev:"; +/// `fd:`: the child's descriptor `n`, which its descriptor table installs +/// at that number (`crate::fd`). +pub const FD_PREFIX: &str = "fd:"; + +/// The prefixes of a service's name, in the order [`service`] looks one up. +const SERVICE_PREFIXES: [&str; 2] = [SVC_PREFIX, PROVIDE_PREFIX]; + +/// `name` under `prefix`: the label a spawn endows a service's connector by. +pub fn service_label(prefix: &str, name: &str) -> String { + alloc::format!("{prefix}{name}") +} -pub use toyos_abi::syscall::{DEV_PREFIX, PROVIDE_PREFIX, SELF_LABEL, SERVE_PREFIX, SVC_LABEL, SYSCAP_LABEL}; +/// The service a label names, if it is a service's. +pub(crate) fn service_of(label: &str) -> Option<&str> { + SERVICE_PREFIXES.iter().find_map(|prefix| label.strip_prefix(prefix)) +} /// Why a service name did not become a connection. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum EndowError { - /// The name is not in the namespace this process was given — a fact about - /// this process, not about the machine. There is no "not yet": every port - /// exists before either end's process runs. + /// This process was not given the service, or was given its connector + /// without the right to connect: a fact about this process, not about the + /// machine. + /// There is no "not yet": every port exists before either end's process + /// runs. NotEndowed, /// The server exited. Its acceptor's last handle went with it and the port /// is closed for good. @@ -100,7 +136,6 @@ pub(crate) use from_handle; from_handle! { Acceptor: Acceptor => Acceptor, Connector: Connector => Connector, - Namespace: Namespace => Namespace, SysCap: SysCap => SysCap, Job: Job => Job, Process: Process => Process, @@ -138,27 +173,37 @@ macro_rules! class_of_claim { class_of_claim!(crate::FramebufferDev, crate::PciDev, crate::AcpiDev, crate::UartDev); -/// This process's endowment table, parsed once. +/// This process's endowment table, read once. pub struct Endowments { - labels: [u8; MAX_LABELS_LEN], - /// `(label offset, label length)` per entry, and the handle beside it. - spans: [(u32, u32); MAX_ENDOWMENTS], - /// **Taken by swap**, so a label answers exactly once: the second `take` + /// What its parent calls each handle, in the order endowed. + names: [&'static str; MAX_SPAWN_ENDOWMENTS], + /// **Taken by swap**, so a name answers exactly once: the second `take` /// finds [`HANDLE_INVALID`] and returns `None` rather than a second owner /// of one handle. - handles: [AtomicU32; MAX_ENDOWMENTS], + handles: [AtomicU32; MAX_SPAWN_ENDOWMENTS], count: usize, } impl Endowments { - /// This process's own, parsed on first use. + /// This process's own, read on first use. pub fn get() -> &'static Endowments { TABLE.get() } - /// Take the handle labelled `label`, as `T`. `None` once, for a label this - /// process was not given; `None` again for one already taken. + /// The table of a process whose parent called its `i`th endowment + /// `names[i]`: the handle is `RawHandle::endowed(i)`. + fn of(names: [&'static str; MAX_SPAWN_ENDOWMENTS], count: usize) -> Self { + let handles = core::array::from_fn(|i| AtomicU32::new(if i < count { RawHandle::endowed(i).0 } else { HANDLE_INVALID.0 })); + Self { names, handles, count } + } + + /// Take the handle named `label`, as `T`. `None` once, for a name this + /// process was not given; `None` again for one already taken; and `None` + /// for a service's, which stays ([`connector`]). pub fn take(&self, label: &str) -> Option { + if service_of(label).is_some() { + return None; + } let i = self.index_of(label)?; let raw = self.handles[i].swap(HANDLE_INVALID.0, Ordering::AcqRel); if raw == HANDLE_INVALID.0 { @@ -178,73 +223,104 @@ impl Endowments { .is_some_and(|i| self.handles[i].load(Ordering::Acquire) != HANDLE_INVALID.0) } - /// Every label in the table, taken or not: for a diagnostic, and for a - /// family of labels such as `toyos::fd`'s, each then taken by name. - pub fn labels(&self) -> impl Iterator { - (0..self.count).map(|i| self.label_at(i)) + /// Every name in the table, taken or not: for a diagnostic, and for a + /// family of names such as `toyos::fd`'s, each then taken by name. + pub fn labels(&self) -> impl Iterator + '_ { + self.names[..self.count].iter().copied() } - fn label_at(&self, i: usize) -> &str { - let (off, len) = self.spans[i]; - let bytes = &self.labels[off as usize..off as usize + len as usize]; - // The kernel copied these bytes out of what this process's own parent - // wrote, and a label that is not UTF-8 matches no lookup. - core::str::from_utf8(bytes).unwrap_or("") + fn index_of(&self, label: &str) -> Option { + self.names[..self.count].iter().position(|name| *name == label) } - fn index_of(&self, label: &str) -> Option { - (0..self.count).find(|&i| self.label_at(i) == label) + /// Every service's connector, by its whole label, in the table's order. + fn services(&self) -> impl Iterator)> + '_ { + self.labels().zip(&self.handles).filter(|(label, _)| service_of(label).is_some()).map(|(label, handle)| { + // SAFETY: `take` gives no service's handle away, so it stays in + // this process's table for its life. + (label, unsafe { BorrowedHandle::borrow_raw(RawHandle(handle.load(Ordering::Relaxed))) }) + }) } } -/// The process's namespace, or `None` for a program the manifest gives no -/// `receives`. -/// -/// Borrowed rather than taken: every `service` call resolves through it and a -/// process has exactly one for its whole life. -pub fn namespace() -> Option<&'static Namespace> { - NAMESPACE.get().as_ref() +/// The connector to the service `name` this process holds, the one its row +/// gave before one it was provided, then one it [`adopt`]ed: borrowed for the +/// process's life. A holder that wants its own duplicates it. +pub fn connector(name: &str) -> Option> { + let table = Endowments::get(); + SERVICE_PREFIXES + .iter() + .find_map(|prefix| table.services().find(|(label, _)| label.strip_prefix(prefix) == Some(name))) + .map(|(_, connector)| connector) + .or_else(|| { + let (_, connector) = adopted().iter().find(|(held, _)| held == name)?; + // SAFETY: an adopted connector is never dropped. + Some(unsafe { BorrowedHandle::borrow_raw(connector.as_handle().raw()) }) + }) +} + +/// The name of every service this process holds, as [`connector`] takes it. +pub fn services() -> impl Iterator { + let endowed = Endowments::get().services().filter_map(|(label, _)| service_of(label)); + endowed.chain(adopted().iter().map(|(name, _)| name.as_str())) +} + +/// The label and connector of every service a helper this process spawns is +/// given a duplicate of: what it was endowed, never what it adopted. +pub(crate) fn inheritable() -> impl Iterator)> { + Endowments::get().services() +} + +/// Every connector whoever launched this process provided, by name: what it +/// passes on to a program it launches in turn, whose row decides everything +/// else that program gets. Borrowed, so the caller duplicates one per child. +pub fn provided() -> impl Iterator)> { + Endowments::get().services().filter_map(|(label, connector)| Some((label.strip_prefix(PROVIDE_PREFIX)?, connector))) } -/// The namespace holding the supervisor's `launcher` this process was endowed -/// under [`crate::launch::LAUNCHER`], or `None`. Borrowed, as [`namespace`] is: -/// std opens it for every launch, and a parent that hands it on endows a -/// duplicate. -pub fn launcher() -> Option<&'static Namespace> { - LAUNCHER.get().as_ref() +/// The launcher this process was endowed under [`crate::launch::LAUNCHER`], +/// or `None`. Borrowed: std connects through it for every launch, and a +/// parent that hands it on endows a duplicate. +pub fn launcher() -> Option<&'static Connector> { + LAUNCHER.0.get_or_init(|| Endowments::get().take::(crate::launch::LAUNCHER)).as_ref() } /// This process's handle to itself, which it hands on for a child to be -/// placed under it: `WRITE`, `DUP` and `TRANSFER`, from the kernel, under -/// [`SELF_LABEL`]. Borrowed rather than taken, as the namespace is: every -/// launch duplicates it. Panics for a process whose `self` was taken before -/// this first ran: the kernel starts every process holding it. +/// placed under it: `WRITE`, `DUP` and `TRANSFER`, from the kernel, at +/// [`RawHandle::SELF`]. Borrowed: every launch duplicates it. pub fn this_process() -> &'static Process { - THIS_PROCESS.get() + // The kernel starts every process holding it there, and nothing closes + // it: the wrapper is never dropped. + THIS_PROCESS.0.get_or_init(|| Process(OwnedHandle(RawHandle::SELF))) } -/// Make `ns` this process's namespace, for the one process no parent endows -/// one: the supervisor, which builds the machine's ports itself and resolves its own -/// files through them as every program does. Refused when this process already -/// has a namespace or has asked for one. -pub fn adopt_namespace(ns: Namespace) -> Result<(), Namespace> { - let mut offered = Some(ns); - NAMESPACE.0.get_or_init(|| offered.take()); +/// Make `services` this process's own beside what it was endowed, for the +/// one process no parent endows any: the supervisor, which makes the +/// machine's ports itself and resolves its own files through them as every +/// program does. No child inherits them. Refused once this process has +/// adopted any or has looked one up. +pub fn adopt(services: Vec<(String, Connector)>) -> Result<(), Vec<(String, Connector)>> { + let mut offered = Some(services); + ADOPTED.0.get_or_init(|| offered.take().expect("built once")); match offered { None => Ok(()), - Some(ns) => Err(ns), + Some(services) => Err(services), } } -/// Open a connection to `name` in this process's namespace. +fn adopted() -> &'static [(String, Connector)] { + ADOPTED.0.get_or_init(Vec::new) +} + +/// Open a connection to the service `name`. /// /// The one place a name becomes a connection. It works from the caller's first /// instruction whether or not the server has reached `accept` or has even been /// spawned, because the port existed before either process did. pub fn service(name: &str) -> Result { - let ns = namespace().ok_or(EndowError::NotEndowed)?; - ns.open(name).map_err(|e| match e { - SyscallError::NotFound => EndowError::NotEndowed, + let connector = connector(name).ok_or(EndowError::NotEndowed)?; + crate::port::connect(connector).map_err(|e| match e { + SyscallError::PermissionDenied => EndowError::NotEndowed, SyscallError::Gone => EndowError::ServerGone, other => EndowError::Refused(other), }) @@ -259,28 +335,6 @@ pub fn acceptor(name: &str) -> Option { with_prefixed(SERVE_PREFIX, name, |label| Endowments::get().take::(label)) } -/// Every connector a launching client transferred, taken out of the table. -/// -/// **What makes a `provides` name forwardable.** A namespace hands back -/// connections; this hands back the connector itself, so a shell can give its -/// own children the surface its terminal gave it — and it is a `take`, so the -/// caller owns exactly one and duplicates for each child. -pub fn provided(labels: &mut [Option<(&'static str, Connector)>]) -> usize { - let table = Endowments::get(); - let mut n = 0; - for i in 0..table.count { - let label = table.label_at(i); - let Some(name) = label.strip_prefix(PROVIDE_PREFIX) else { continue }; - if n == labels.len() { - break; - } - let Some(connector) = table.take::(label) else { continue }; - labels[n] = Some((name, connector)); - n += 1; - } - n -} - /// The claim for a device class the manifest says this program gets. /// /// `None` is a machine that had no such device when the supervisor asked, or a program @@ -305,9 +359,8 @@ pub fn isa(set: toyos_abi::syscall::IsaId) -> Option { claim(DeviceRequest::Isa(set).write_name(&mut buf)?) } -/// Compose `` on the stack. Nothing in the SDK allocates, and a -/// name past the label bound matches nothing rather than being truncated into -/// a label that happens to exist. +/// Compose `` on the stack: a name too long for it matches +/// nothing rather than being truncated into a label that happens to exist. fn with_prefixed(prefix: &str, name: &str, f: impl FnOnce(&str) -> Option) -> Option { let mut buf = [0u8; 128]; let total = prefix.len() + name.len(); @@ -362,71 +415,87 @@ impl Once { } static TABLE: EndowTable = EndowTable(Once::new()); -static NAMESPACE: NamespaceCell = NamespaceCell(Once::new()); static LAUNCHER: LauncherCell = LauncherCell(Once::new()); static THIS_PROCESS: ProcessCell = ProcessCell(Once::new()); +static ADOPTED: AdoptedCell = AdoptedCell(Once::new()); struct EndowTable(Once); -struct NamespaceCell(Once>); -struct LauncherCell(Once>); +struct LauncherCell(Once>); struct ProcessCell(Once); +struct AdoptedCell(Once>); impl EndowTable { + /// The table its parent wrote, or the process's end: every channel a + /// process speaks on is named by the table, so a malformed one is said on + /// the `fd:2` it still names, if any, and by the exit status. Nothing + /// here panics, since a panic's own words go through this table. fn get(&'static self) -> &'static Endowments { - self.0.get_or_init(read_table) + self.0.get_or_init(|| { + let mut names = [""; MAX_SPAWN_ENDOWMENTS]; + match crate::startup::endowed(&mut names) { + Ok(count) => Endowments::of(names, count), + Err(why) => { + if let Some(stderr) = crate::startup::early(b"fd:2") { + let _ = toyos_abi::syscall::write_nonblock(RawHandle::endowed(stderr), why.line()); + } + toyos_abi::syscall::exit(crate::startup::MALFORMED_STATUS) + } + } + }) } } -impl NamespaceCell { - fn get(&'static self) -> &'static Option { - self.0.get_or_init(|| Endowments::get().take::(SVC_LABEL)) +#[cfg(test)] +mod tests { + use super::*; + + fn table(names: &[&'static str]) -> Endowments { + let mut all = [""; MAX_SPAWN_ENDOWMENTS]; + all[..names.len()].copy_from_slice(names); + Endowments::of(all, names.len()) } -} -impl LauncherCell { - fn get(&'static self) -> &'static Option { - self.0.get_or_init(|| Endowments::get().take::(crate::launch::LAUNCHER)) + /// A taken handle's number, the wrapper given up so nothing closes it. + fn taken(table: &Endowments, label: &str) -> Option { + table.take::(label).map(OwnedHandle::into_raw) } -} -impl ProcessCell { - fn get(&'static self) -> &'static Process { - self.0.get_or_init(|| { - Endowments::get().take::(SELF_LABEL).expect("this process's `self` was taken by another") - }) + /// The `i`th name is the `i`th endowed handle's, which answers once. + #[test] + fn a_name_is_the_handle_endowed_at_its_place_and_is_taken_once() { + let table = table(&["fd:0", "syscap", "serve:net"]); + assert!(table.holds("serve:net")); + assert_eq!(taken(&table, "serve:net"), Some(RawHandle::endowed(2))); + assert_eq!(taken(&table, "serve:net"), None); + assert!(!table.holds("serve:net")); + assert_eq!(taken(&table, "fd:0"), Some(RawHandle::endowed(0))); + assert_eq!(taken(&table, "fd:1"), None); + assert_eq!(taken(&table, ""), None, "a place past the table's count names nothing"); + assert_eq!(table.labels().collect::>(), ["fd:0", "syscap", "serve:net"]); } -} -/// One `SYS_ENDOWMENTS` call: a count, that many entries, then the label blob. -fn read_table() -> Endowments { - const ENTRY: usize = 16; - let mut table = Endowments { - labels: [0; MAX_LABELS_LEN], - spans: [(0, 0); MAX_ENDOWMENTS], - handles: [const { AtomicU32::new(HANDLE_INVALID.0) }; MAX_ENDOWMENTS], - count: 0, - }; - let mut buf = [0u8; 8 + MAX_ENDOWMENTS * ENTRY + MAX_LABELS_LEN]; - let n = syscall::endowments(&mut buf); - if n < 8 || n > buf.len() { - return table; + /// A service's connector stays in the table: it is listed with its + /// handle, and no `take` gives it away. + #[test] + fn a_service_stays_in_the_table() { + let table = table(&["svc:net", "launcher", "provide:surface", "svc:fs:/tmp"]); + assert_eq!(taken(&table, "svc:net"), None); + assert_eq!(taken(&table, "provide:surface"), None); + let held: Vec<_> = table.services().map(|(label, handle)| (label, handle.raw())).collect(); + assert_eq!( + held, + [("svc:net", RawHandle::endowed(0)), ("provide:surface", RawHandle::endowed(2)), ("svc:fs:/tmp", RawHandle::endowed(3))] + ); + assert_eq!(taken(&table, "launcher"), Some(RawHandle::endowed(1))); } - let count = u64::from_ne_bytes(buf[..8].try_into().unwrap()) as usize; - // The kernel bounds this at spawn; a count past it here is a kernel bug, - // and answering with an empty table would hide it. Fail-fast. - assert!(count <= MAX_ENDOWMENTS, "the kernel reported {count} endowments"); - let blob = 8 + count * ENTRY; - assert!(blob <= n, "the endowment table claims more entries than it carries"); - let labels = &buf[blob..n]; - table.labels[..labels.len()].copy_from_slice(labels); - for i in 0..count { - let at = 8 + i * ENTRY; - let off = u32::from_ne_bytes(buf[at..at + 4].try_into().unwrap()); - let len = u32::from_ne_bytes(buf[at + 4..at + 8].try_into().unwrap()); - let handle = u32::from_ne_bytes(buf[at + 8..at + 12].try_into().unwrap()); - table.spans[i] = (off, len); - table.handles[i].store(handle, Ordering::Relaxed); + + #[test] + fn a_service_is_named_under_either_prefix_and_nothing_else_is_one() { + assert_eq!(service_of("svc:fs:/home/toy"), Some("fs:/home/toy")); + assert_eq!(service_of("provide:surface"), Some("surface")); + assert_eq!(service_of(&service_label(SVC_PREFIX, "net")), Some("net")); + for label in ["serve:net", "syscap", "fd:2", "launcher", "svc", "provide"] { + assert_eq!(service_of(label), None, "{label}"); + } } - table.count = count; - table } diff --git a/sdk/toyos/src/fd.rs b/sdk/toyos/src/fd.rs index d0494f66059..0b6090d7d9f 100644 --- a/sdk/toyos/src/fd.rs +++ b/sdk/toyos/src/fd.rs @@ -365,7 +365,7 @@ pub fn set_inheritable(fd: Fd, inheritable: bool) -> Result<(), Error> { /// in canonical decimal, and its kind: `None` for any other label, and for a /// number past the table. pub fn endowed_fd(label: &str) -> Option<(Fd, Kind)> { - let rest = label.strip_prefix(toyos_abi::syscall::FD_PREFIX)?; + let rest = label.strip_prefix(crate::endow::FD_PREFIX)?; let (digits, kind) = match rest.split_once(':') { None => (rest, Kind::Handle), Some((digits, "exported")) => (digits, Kind::Exported), diff --git a/sdk/toyos/src/files.rs b/sdk/toyos/src/files.rs index c0bdd0637e7..492b7111a81 100644 --- a/sdk/toyos/src/files.rs +++ b/sdk/toyos/src/files.rs @@ -1,5 +1,5 @@ //! Files and directories through the capabilities a process holds, named -//! `fs:` in its namespace. Absolute paths and paths from the +//! `fs:` among its services. Absolute paths and paths from the //! working directory go to the deepest held directory they lie in, at most //! [`MAX_CAPABILITY_DEPTH`] components deep. A relative path from a //! descriptor stays on that directory's server. Absolute symlinks return @@ -10,7 +10,7 @@ //! Kernel objects still support generic reads, writes and metadata queries. #[cfg(any(feature = "fd-table", test))] -mod namespace; +mod routing; use alloc::string::String; use alloc::vec::Vec; diff --git a/sdk/toyos/src/files/owner.rs b/sdk/toyos/src/files/owner.rs index 63f92746ca7..e0c89c61c14 100644 --- a/sdk/toyos/src/files/owner.rs +++ b/sdk/toyos/src/files/owner.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use toyos_abi::syscall::{self, FileType, SyscallError}; -use super::namespace::Routed; +use super::routing::Routed; use super::{encode, At, Error, Open, Stat, MAX_LINKS, SEEK_CURRENT, SEEK_END, SEEK_START}; use crate::fd::{self, Fd, Object, SERVED_FILE}; use crate::fs::{self, Base, Change, Dir, Fid, Refused}; @@ -46,25 +46,19 @@ impl Drop for Served { } } -/// Namespace inspection is bounded and immutable; no service is opened to -/// discover its names. Failed inspection is kept as an error, not an empty view. -static DIRECTORIES: crate::endow::Once> = crate::endow::Once::new(); +/// The directories this process's services make, read once: no service is +/// opened to discover its name. +static DIRECTORIES: crate::endow::Once = crate::endow::Once::new(); -fn directories() -> Result<&'static super::namespace::Directories, Error> { - DIRECTORIES.get_or_init(|| { - let names = match crate::endow::namespace() { - Some(namespace) => namespace.names().map_err(Error::Kernel)?, - None => Vec::new(), - }; - Ok(super::namespace::Directories::new(names)) - }).as_ref().map_err(|e| *e) +fn directories() -> &'static super::routing::Directories { + DIRECTORIES.get_or_init(|| super::routing::Directories::new(crate::endow::services())) } fn directory_stat(path: &str) -> Result { Ok(Stat { file_type: FileType::Directory, size: 0, nlink: 1, atime: 0, mtime: 0, ctime: 0, volume: 0, - node: directories()?.node(path).expect("a routed namespace directory"), + node: directories().node(path).expect("a routed directory"), }) } @@ -229,16 +223,15 @@ fn capability(prefix: &str) -> Result, Error> { if let Some(found) = CAPABILITIES.lock().get(prefix) { return Ok(Arc::clone(found)); } - let names = crate::endow::namespace().ok_or(Error::Served(fs::Error::NotHeld))?; let name = alloc::format!("{}{prefix}", fs::CAPABILITY_PREFIX); - let dir = Dir::connect(names, &name).map_err(Error::Served)?; + let dir = Dir::named(&name).map_err(Error::Served)?; let found = Arc::new(Capability { dir: Turnstile::new(dir) }); Ok(Arc::clone(CAPABILITIES.lock().entry(String::from(prefix)).or_insert(found))) } /// Which server the absolute, lexically clean `abs` is on, and the path there. fn route(abs: &str) -> Result { - Ok(match super::namespace::route(abs, directories()?, capability)? { + Ok(match super::routing::route(abs, directories(), capability)? { Routed::Served(cap, rel) => Place::Served(cap, Base::Root, rel), Routed::Directory => Place::Directory(String::from(abs)), }) @@ -755,9 +748,9 @@ unsafe extern "C" fn toyos_fs_readdir(fd: Fd, cursor: u64, out: *mut u8, cap: us |path| { let mut out = Vec::new(); if cursor == 0 { - for (name, node) in directories()?.children(path) { + for (name, node) in directories().children(path) { fs::encode_entry(&mut out, &fs::Entry { kind: fs::KIND_DIR, size: 0, node, name }) - .expect("namespace service names fit directory entries"); + .expect("a directory capability's name fits a directory entry"); } } Ok((out, None)) diff --git a/sdk/toyos/src/files/namespace.rs b/sdk/toyos/src/files/routing.rs similarity index 93% rename from sdk/toyos/src/files/namespace.rs rename to sdk/toyos/src/files/routing.rs index 099725b8362..c837125762b 100644 --- a/sdk/toyos/src/files/namespace.rs +++ b/sdk/toyos/src/files/routing.rs @@ -44,11 +44,13 @@ pub(super) struct Directories { } impl Directories { - pub(super) fn new(names: impl IntoIterator) -> Self { + /// The directories the services named `names` make: each directory + /// capability's and its ancestors'. + pub(super) fn new>(names: impl IntoIterator) -> Self { let mut paths = BTreeSet::from([String::from("/")]); let mut held = BTreeSet::new(); for name in names { - let Some(path) = name.strip_prefix(crate::fs::CAPABILITY_PREFIX) else { continue }; + let Some(path) = name.as_ref().strip_prefix(crate::fs::CAPABILITY_PREFIX) else { continue }; if !path.starts_with('/') || super::absolute("/", path) != path { continue; } @@ -96,7 +98,7 @@ mod tests { #[test] fn only_held_filesystem_names_create_directories() { - let dirs = Directories::new(["fs:/home/jan", "fs:/tmp", "fs:/home/jan/Games", "net", "fs:relative", "fs:/bad/../name", "fs:/a/b/c/d/e"].map(String::from)); + let dirs = Directories::new(["fs:/home/jan", "fs:/tmp", "fs:/home/jan/Games", "net", "fs:relative", "fs:/bad/../name", "fs:/a/b/c/d/e"]); assert_eq!(dirs.children("/").map(|(s, _)| s).collect::>(), ["home", "tmp"]); assert_eq!(dirs.children("/home").map(|(s, _)| s).collect::>(), ["jan"]); assert!(dirs.node("/home/jane").is_none()); @@ -105,8 +107,8 @@ mod tests { } #[test] - fn an_empty_namespace_has_an_empty_root() { - let dirs = Directories::new([]); + fn a_process_with_no_service_has_an_empty_root() { + let dirs = Directories::new([""; 0]); assert_eq!(dirs.node("/"), Some(1)); assert_eq!(dirs.children("/").count(), 0); } @@ -114,7 +116,7 @@ mod tests { /// `abs` routed in a view holding exactly `names`, each capability /// answered by its own prefix. fn routed(names: &[&str], abs: &str) -> Result, Error> { - let dirs = Directories::new(names.iter().map(|n| String::from(*n))); + let dirs = Directories::new(names); route(abs, &dirs, |prefix| { let name = alloc::format!("{}{prefix}", fs::CAPABILITY_PREFIX); assert!(names.contains(&name.as_str()), "asked for {name}, which the view does not hold"); diff --git a/sdk/toyos/src/fs.rs b/sdk/toyos/src/fs.rs index 9a62c454c8a..41343b70051 100644 --- a/sdk/toyos/src/fs.rs +++ b/sdk/toyos/src/fs.rs @@ -1,8 +1,9 @@ //! Files a file server holds: the wire between a program and the server behind //! one of its directory capabilities, and the client end of it. //! -//! **A directory capability is a connector in the program's namespace**, named -//! [`CAPABILITY_PREFIX`] and the absolute directory it serves (`fs:/home`). +//! **A directory capability is a connector among the program's services** +//! (`crate::endow`), named [`CAPABILITY_PREFIX`] and the absolute directory it +//! serves (`fs:/home`). //! Each is a connector to its role's one port, which the supervisor minted with //! a [`Grant`]: the directory, whether it may be changed, and whose share of //! the server it spends. The kernel stamps that on every connection made through it and answers it to the @@ -49,15 +50,14 @@ use alloc::vec::Vec; -use toyos_abi::syscall::{SyscallError, MAX_BADGE, MAX_SERVICE_NAME}; +use toyos_abi::syscall::{SyscallError, MAX_BADGE}; use crate::ipc::{Connection, FrameRx, IpcError}; use crate::ipc_payload; -use crate::namespace::Namespace; use crate::shm::{Bytes, SharedMemory}; use crate::Pipe; -/// What a directory capability's namespace name begins with. +/// What a directory capability's service name begins with. pub const CAPABILITY_PREFIX: &str = "fs:"; /// The data window a client lends its server: one shared-memory page, the size @@ -701,7 +701,7 @@ impl From for Refused { /// Where a [`Dir`]'s connection comes from. enum Source { /// A directory capability, connected again through the same connector. - Named { names: &'static Namespace, name: [u8; MAX_SERVICE_NAME], len: usize }, + Held(crate::BorrowedHandle<'static>), /// A connection another process exported: one connection, never another. Exported, } @@ -726,19 +726,20 @@ struct Paths<'a> { } impl Dir { - /// Connect to the directory `names` calls `name`: [`Error::NotHeld`] for a - /// name this process was not given. - pub fn connect(names: &'static Namespace, name: &str) -> Result { - if name.len() > MAX_SERVICE_NAME { - return Err(Error::NotHeld); - } - let mut buf = [0u8; MAX_SERVICE_NAME]; - buf[..name.len()].copy_from_slice(name.as_bytes()); - let mut dir = Self::unconnected(Source::Named { names, name: buf, len: name.len() })?; + /// Connect to the directory capability `connector`, which this process + /// holds for its life: [`Error::NotHeld`] for one it may only pass on. + pub fn connect(connector: crate::BorrowedHandle<'static>) -> Result { + let mut dir = Self::unconnected(Source::Held(connector))?; dir.reconnect()?; Ok(dir) } + /// Connect to the directory capability among this process's services as + /// `name`: [`Error::NotHeld`] for a name it was not given. + pub fn named(name: &str) -> Result { + Self::connect(crate::endow::connector(name).ok_or(Error::NotHeld)?) + } + /// Serve the connection an [`EXPORT`] answered, whose descriptions are ids /// 1.. in the order exported. It names no path, and is not connected again /// once it has ended. @@ -773,11 +774,10 @@ impl Dir { /// exported connection has no connector, and stays gone. fn reconnect(&mut self) -> Result<(), Error> { self.conn = None; - let Source::Named { names, name, len } = &self.source else { return Err(Error::Gone) }; - let name = core::str::from_utf8(&name[..*len]).expect("copied from a str"); - let conn = match names.open(name) { + let Source::Held(connector) = self.source else { return Err(Error::Gone) }; + let conn = match crate::port::connect(connector) { Ok(conn) => conn, - Err(SyscallError::NotFound) => return Err(Error::NotHeld), + Err(SyscallError::PermissionDenied) => return Err(Error::NotHeld), Err(e) => return Err(Error::kernel("a connection", e)), }; self.attach(conn) diff --git a/sdk/toyos/src/ipc.rs b/sdk/toyos/src/ipc.rs index 21c10bd392f..9ae3d798e3e 100644 --- a/sdk/toyos/src/ipc.rs +++ b/sdk/toyos/src/ipc.rs @@ -835,8 +835,9 @@ mod tests { admit(crate::process::Process::KINDS, ObjectKind::PipeRead as u32), Err(WrongKind { wanted: &[ObjectKind::Process], got: Some(ObjectKind::PipeRead) }) ); - let unknown = ObjectKind::ALL.len() as u32; - assert_eq!(admit(Bell::KINDS, unknown), Err(WrongKind { wanted: Bell::KINDS, got: None })); + let past_the_last = ObjectKind::ALL[ObjectKind::ALL.len() - 1] as u32 + 1; + assert_eq!(admit(Bell::KINDS, past_the_last), Err(WrongKind { wanted: Bell::KINDS, got: None })); + assert_eq!(admit(Bell::KINDS, 8), Err(WrongKind { wanted: Bell::KINDS, got: None }), "the namespace's retired word"); assert_eq!(admit(Bell::KINDS, ReceivedEntry::NONE.kind), Err(WrongKind { wanted: Bell::KINDS, got: None })); } diff --git a/sdk/toyos/src/job.rs b/sdk/toyos/src/job.rs index 48cea22dbc0..e61c53c61bd 100644 --- a/sdk/toyos/src/job.rs +++ b/sdk/toyos/src/job.rs @@ -1,6 +1,6 @@ //! A job: the owner a process's threads, and its children's unless a spawn -//! names another, share CPU time under. A spawn endows one under -//! [`JOB_LABEL`] to put its child in it; [`own`] is this process's own, and +//! names another, share CPU time under. A spawn names one to put its child +//! in it (`crate::spawn::Spawn::job`); [`own`] is this process's own, and //! [`SysCap::new_job`](crate::syscap::SysCap::new_job) makes an independent //! one. //! @@ -10,8 +10,6 @@ use toyos_abi::syscall::{self, AccountInfo, SyscallError}; -pub use toyos_abi::syscall::JOB_LABEL; - use crate::{AsHandle, OwnedHandle, RawHandle}; pub struct Job(pub(crate) OwnedHandle); @@ -23,7 +21,7 @@ pub fn own() -> Result { } impl Job { - /// A second handle to the same job, for a spawn to move. + /// A second handle to the same job. pub fn duplicate(&self) -> Result { self.0.try_clone().map(Job) } diff --git a/sdk/toyos/src/launch.rs b/sdk/toyos/src/launch.rs index 5e9978a5507..5c11ccd0bfc 100644 --- a/sdk/toyos/src/launch.rs +++ b/sdk/toyos/src/launch.rs @@ -11,8 +11,8 @@ //! **The asker's authority is one connector**, and the policy is the manifest. //! A launch adds nothing to what the caller could do itself: the stdio handles //! and the extra connectors travel from the caller's own table, which is the -//! same bound `SYS_SPAWN` has. What it *adds* is that the child's namespace is -//! its own manifest row rather than a narrowing of its parent's. +//! same bound `SYS_SPAWN` has. What it *adds* is that the child's services are +//! its own manifest row's rather than a narrowing of its parent's. //! //! **A launch names the job its program joins**: the caller's own, or one the //! caller made with the authority to start an independent job. The supervisor @@ -20,7 +20,7 @@ //! its caller holds that authority and says otherwise. //! //! **A launch names its parent**, by one of two words: a place, which is a -//! handle to a process carrying `WRITE` — the caller's own `self`, duplicated — +//! handle to a process carrying `WRITE` — the caller's handle to itself, duplicated — //! that the program is spawned under, so the caller's end takes it down; or //! detached, under the launcher's owner, the one way for a program to outlive //! whoever started it. @@ -37,8 +37,8 @@ use crate::ipc::{Connection, IpcError}; use crate::process::Process; use crate::OwnedHandle; -/// The label a holder is endowed its launcher under, and the one name in that -/// namespace. **Never an entry of `svc`**, which std hands every direct spawn: +/// The label a holder is endowed its launcher under. **Never a service** +/// (`svc:` or `provide:`), which std hands every direct spawn: /// a launcher carries its holder's row and session as a badge, and a program /// passes it on only by endowing it ([`crate::endow::launcher`]). pub const LAUNCHER: &str = "launcher"; @@ -137,8 +137,8 @@ pub struct Launch<'a, H = OwnedHandle> { /// list `/`. pub env: &'a [u8], pub cwd: &'a str, - /// `(name, connector)` the caller transfers into the child's namespace, on - /// top of the manifest's. This is how a terminal gives its shell the + /// `(name, connector)` the caller transfers to the child as `provide:` + /// services, on top of the manifest's. This is how a terminal gives its shell the /// `surface` port it made — a name the supervisor cannot know, because there is one /// per terminal. pub extras: Vec<(&'a str, H)>, diff --git a/sdk/toyos/src/lib.rs b/sdk/toyos/src/lib.rs index dfedfb28fc0..4c220f05f06 100644 --- a/sdk/toyos/src/lib.rs +++ b/sdk/toyos/src/lib.rs @@ -1,8 +1,8 @@ //! ToyOS userland SDK. //! -//! Typed handles, IPC framing, ports and namespaces, shared memory, and -//! ergonomic wrappers over the kernel ABI defined in `toyos-abi`, and the -//! [`transport`] client/server sessions run over. +//! Typed handles, IPC framing, ports and the services a program was given, +//! shared memory, and ergonomic wrappers over the kernel ABI defined in +//! `toyos-abi`, and the [`transport`] client/server sessions run over. // `not(test)` so this crate's own decisions can be unit-tested on the host. // [`net::hangup`] is a total function from a kernel word to a [`net::NetError`] @@ -32,7 +32,6 @@ pub mod power; pub mod wake; pub mod ipc; pub mod job; -pub mod namespace; pub mod paged; pub mod launch; pub mod log; @@ -327,7 +326,6 @@ owned_from! { ipc::Connection => |c| c.0; port::Acceptor => |a| a.0; port::Connector => |c| c.0; - namespace::Namespace => |n| n.0; syscap::SysCap => |c| c.0; job::Job => |j| j.0; process::Process => |p| p.0; @@ -420,7 +418,6 @@ mod std_io { super::ipc::Connection, super::port::Acceptor, super::port::Connector, - super::namespace::Namespace, super::syscap::SysCap, super::job::Job, super::process::Process, diff --git a/sdk/toyos/src/namespace.rs b/sdk/toyos/src/namespace.rs deleted file mode 100644 index 25f75eb2fc4..00000000000 --- a/sdk/toyos/src/namespace.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! The set of service names one process can resolve. -//! -//! Immutable once built. A narrower namespace is a *new* one built from an -//! existing one, so a child given a subset cannot widen it back, and a name a -//! process was not given resolves to nothing — there is no second place to ask. - -use toyos_abi::handle::HANDLE_INVALID; -use toyos_abi::syscall::{ - self, NameRef, NamespaceBuild, NamespaceEntry, SyscallError, MAX_NAMESPACE_ENTRIES, - NAMESPACE_KEEP_ALL, -}; - -use crate::ipc::Connection; -use crate::port::Connector; -use crate::{AsHandle, OwnedHandle, RawHandle}; - -pub struct Namespace(pub(crate) OwnedHandle); - -impl Namespace { - /// Open a connection to `name`. - /// - /// `NotFound` is "this process was not given that name" and - /// [`SyscallError::Gone`] is "the server exited". There is no third answer, - /// and in particular there is no "not yet". - pub fn open(&self, name: &str) -> Result { - syscall::namespace_open(self.0.raw(), name).map(|h| Connection(OwnedHandle(h))) - } - - /// The names this namespace holds, sorted. Inspection opens no connector - /// and conveys no authority beyond the namespace itself. - pub fn names(&self) -> Result, SyscallError> { - let required = syscall::namespace_names(self.0.raw(), &mut [])?; - assert!(required <= MAX_NAMESPACE_ENTRIES * (2 + syscall::MAX_SERVICE_NAME), "namespace names exceed the ABI bound"); - let mut bytes = alloc::vec![0; required]; - let read = syscall::namespace_names(self.0.raw(), &mut bytes)?; - assert_eq!(required, read, "an immutable namespace changed names"); - let mut names = alloc::vec::Vec::new(); - let mut rest = &bytes[..]; - while !rest.is_empty() { - let len = u16::from_le_bytes(rest[..2].try_into().expect("namespace name length")) as usize; - names.push(alloc::string::String::from_utf8(rest[2..2 + len].to_vec()).expect("namespace name is UTF-8")); - rest = &rest[2 + len..]; - } - Ok(names) - } - - pub fn into_raw(self) -> RawHandle { - self.0.into_raw() - } - - /// A second handle to the same namespace, for a holder handing one on or - /// using one while another thread may replace its own. - pub fn duplicate(&self) -> Result { - self.0.try_clone().map(Self) - } - - /// # Safety - /// `raw` must be a live namespace handle this process owns and nothing else - /// answers for. - pub unsafe fn from_raw(raw: RawHandle) -> Self { - Self(OwnedHandle(raw)) - } -} - -impl AsHandle for Namespace { - fn as_handle(&self) -> crate::BorrowedHandle<'_> { - self.0.as_handle() - } -} - -/// Collects the names and the connectors one `SYS_NAMESPACE_BUILD` will carry. -/// -/// The connectors are **borrowed**: building a namespace does not consume them, -/// because the same connector goes into several children's namespaces and the supervisor -/// does exactly that. -pub struct Builder<'a> { - base: RawHandle, - flags: u32, - names: heapless_names::Names, - keep: heapless_names::Vec, - add: heapless_names::Vec, - connectors: core::marker::PhantomData<&'a Connector>, - overflowed: bool, -} - -/// A fixed-capacity name blob and vector, so building a namespace needs no -/// allocator — the supervisor builds one per program before anything else runs. -mod heapless_names { - use toyos_abi::syscall::{MAX_NAMESPACE_ENTRIES, MAX_SERVICE_NAME}; - - pub const BLOB: usize = MAX_NAMESPACE_ENTRIES * MAX_SERVICE_NAME; - - pub struct Names { - pub bytes: [u8; BLOB], - pub len: usize, - } - - impl Names { - pub const fn new() -> Self { - Self { bytes: [0; BLOB], len: 0 } - } - - /// The name's `(offset, length)`, or `None` when the blob is full. - pub fn push(&mut self, name: &str) -> Option<(u32, u32)> { - if name.len() > MAX_SERVICE_NAME || self.len + name.len() > BLOB { - return None; - } - let off = self.len; - self.bytes[off..off + name.len()].copy_from_slice(name.as_bytes()); - self.len += name.len(); - Some((off as u32, name.len() as u32)) - } - } - - pub struct Vec { - items: [T; N], - len: usize, - } - - impl Vec { - /// `filler` rather than `Default`: the element types live in - /// `toyos-abi` and the orphan rule puts their impls out of reach here. - pub fn new(filler: T) -> Self { - Self { items: [filler; N], len: 0 } - } - - pub fn push(&mut self, item: T) -> bool { - if self.len == N { - return false; - } - self.items[self.len] = item; - self.len += 1; - true - } - - pub fn as_slice(&self) -> &[T] { - &self.items[..self.len] - } - } -} - -/// A namespace with nothing carried over from anywhere. -/// -/// The lifetime is the caller's: `add` borrows the connectors, so a builder -/// lives no longer than they do. -pub fn build<'a>() -> Builder<'a> { - Builder { - base: HANDLE_INVALID, - flags: 0, - names: heapless_names::Names::new(), - keep: heapless_names::Vec::new(NameRef { off: 0, len: 0 }), - add: heapless_names::Vec::new(NamespaceEntry { - off: 0, - len: 0, - connector: HANDLE_INVALID, - _pad: 0, - }), - connectors: core::marker::PhantomData, - overflowed: false, - } -} - -impl<'a> Builder<'a> { - /// Carry `names` over from `base`. A name `base` does not hold is simply - /// absent from the result: narrowing is an intersection, and asking for a - /// name you do not hold grants nothing either way. - pub fn keep(mut self, base: &Namespace, names: &[&str]) -> Self { - self.base = base.0.raw(); - for name in names { - match self.names.push(name) { - Some((off, len)) => { - self.overflowed |= !self.keep.push(NameRef { off, len }); - } - None => self.overflowed = true, - } - } - self - } - - /// Carry over **every** name `base` holds, which [`keep`](Self::keep) - /// cannot spell. Not with `keep`: the kernel refuses the two together. - pub fn keep_all(mut self, base: &Namespace) -> Self { - self.base = base.0.raw(); - self.flags |= NAMESPACE_KEEP_ALL; - self - } - - pub fn add(mut self, name: &str, connector: &'a Connector) -> Self { - match self.names.push(name) { - Some((off, len)) => { - let entry = NamespaceEntry { - off, - len, - connector: connector.as_handle().raw(), - _pad: 0, - }; - self.overflowed |= !self.add.push(entry); - } - None => self.overflowed = true, - } - self - } - - pub fn finish(self) -> Result { - if self.overflowed { - return Err(SyscallError::InvalidArgument); - } - let args = NamespaceBuild { - base: self.base, - flags: self.flags, - keep_ptr: self.keep.as_slice().as_ptr() as u64, - keep_n: self.keep.as_slice().len() as u64, - add_ptr: self.add.as_slice().as_ptr() as u64, - add_n: self.add.as_slice().len() as u64, - names_ptr: self.names.bytes.as_ptr() as u64, - names_len: self.names.len as u64, - }; - // SAFETY: every pointer above names this stack frame's own storage, - // and the syscall reads it before returning. - unsafe { syscall::namespace_build(&args) }.map(|h| Namespace(OwnedHandle(h))) - } -} diff --git a/sdk/toyos/src/net.rs b/sdk/toyos/src/net.rs index 5b862fbd399..d11fc8b9744 100644 --- a/sdk/toyos/src/net.rs +++ b/sdk/toyos/src/net.rs @@ -260,7 +260,7 @@ impl TcpOptions { pub struct NetstackConn(Connection); impl NetstackConn { - /// One connection to netstack, through this process's own namespace. + /// One connection to netstack, through the connector this process was given. /// /// **There was a retry loop here and it is gone.** It spun a hundred times /// at ten milliseconds waiting for a name to appear in a global registry; @@ -343,7 +343,7 @@ pub fn adopt_exported(connection: Connection) -> Option { /// A netstack that hung up mid-exchange is a netstack that is not there. /// -/// A connector is in the namespace from a program's first instruction, so +/// A connector is in the table from a program's first instruction, so /// connecting to a netstack that has already exited *succeeds*: the /// connection queues on a port nobody will accept from, and the hang-up /// arrives at the first send or receive instead. The kernel answers a send diff --git a/sdk/toyos/src/port.rs b/sdk/toyos/src/port.rs index baedf9d4047..b90467a6a04 100644 --- a/sdk/toyos/src/port.rs +++ b/sdk/toyos/src/port.rs @@ -13,7 +13,7 @@ use toyos_abi::syscall::{self, SyscallError, MAX_BADGE}; use crate::ipc::Connection; -use crate::{AsHandle, OwnedHandle, RawHandle}; +use crate::{AsHandle, BorrowedHandle, OwnedHandle, RawHandle}; /// The server's end: connections are accepted from it, and nothing is written /// to it. @@ -23,6 +23,17 @@ pub struct Acceptor(pub(crate) OwnedHandle); /// read. pub struct Connector(pub(crate) OwnedHandle); +/// Open a connection through `connector`, a connector this process holds +/// carrying `WRITE`: one it was endowed ([`crate::endow::connector`]) or one +/// of its own. +/// +/// [`SyscallError::Gone`] is "the server exited", and `PermissionDenied` a +/// connector given without `WRITE`. There is no "not yet". `InvalidArgument` +/// is a handle that is no connector: its type is its giver's word. +pub fn connect(connector: BorrowedHandle<'_>) -> Result { + syscall::connect(connector.raw()).map(|h| Connection(OwnedHandle(h))) +} + /// Make a port. Grants nothing on its own — a port with no clients is not /// authority. pub fn create() -> Result<(Acceptor, Connector), SyscallError> { @@ -80,6 +91,11 @@ impl Acceptor { } impl Connector { + /// Open a connection to the port ([`connect`]). + pub fn connect(&self) -> Result { + connect(self.as_handle()) + } + /// A second connector to the same port, for a holder handing one on while /// keeping its own. pub fn duplicate(&self) -> Result { diff --git a/sdk/toyos/src/quit.rs b/sdk/toyos/src/quit.rs index e5dcccb21ab..e47476dcb9f 100644 --- a/sdk/toyos/src/quit.rs +++ b/sdk/toyos/src/quit.rs @@ -52,46 +52,11 @@ impl AsHandle for Notice { } } -crate::endow::from_handle! { Notice: Notice => Notice } - -/// This process's notice, from the kernel under `QUIT_LABEL`. Borrowed, as -/// [`crate::endow::this_process`] is. -/// -/// **One copy of this crate takes it**, the one that owns the descriptor -/// table (`crate::fd`): std's in a Rust program, libc's own in a C program. -/// Every copy asks that one, so a Rust program that links libc listens to one -/// notice whichever copy reads it. -pub fn notice() -> Option<&'static Notice> { - static NOTICE: Once> = Once::new(); - NOTICE - .get_or_init(|| { - // SAFETY: the owner's definition below, which answers a handle the - // process holds for good, or none. - let raw = RawHandle(unsafe { toyos_quit_notice() }); - (raw != toyos_abi::HANDLE_INVALID).then_some(Notice(OwnedHandle(raw))) - }) - .as_ref() -} - -extern "C" { - fn toyos_quit_notice() -> u32; -} - -/// The copy that owns the notice. -#[cfg(all(feature = "fd-table", not(test)))] -mod owner { - use super::Notice; - use crate::endow::{Endowments, Once}; - - /// The notice, taken once from this process's endowments and never - /// closed, or `HANDLE_INVALID` for none. - #[no_mangle] - extern "C" fn toyos_quit_notice() -> u32 { - static TAKEN: Once = Once::new(); - *TAKEN.get_or_init(|| { - Endowments::get() - .take::(toyos_abi::syscall::QUIT_LABEL) - .map_or(toyos_abi::HANDLE_INVALID.0, |notice| notice.0.into_raw().0) - }) - } +/// This process's notice, which the kernel starts it holding at +/// [`RawHandle::NOTICE`]. Borrowed, as [`crate::endow::this_process`] is: +/// every copy of this crate names the one handle, so a Rust program that +/// links libc listens to one notice whichever copy reads it. +pub fn notice() -> &'static Notice { + static NOTICE: Once = Once::new(); + NOTICE.get_or_init(|| Notice(OwnedHandle(RawHandle::NOTICE))) } diff --git a/sdk/toyos/src/relocate/mod.rs b/sdk/toyos/src/relocate/mod.rs index 08f6c793661..6a5bbcf012d 100644 --- a/sdk/toyos/src/relocate/mod.rs +++ b/sdk/toyos/src/relocate/mod.rs @@ -106,30 +106,13 @@ pub unsafe extern "C" fn relocate_self(header: *const u8) { } } -/// The handle this process was endowed as its stderr, `fd:2`, read off the -/// kernel's endowment table with nothing but the stack and the code's own -/// addresses: no word of the image is relocated yet, so no table, `str` -/// pointer or formatting is reached. +/// The handle this process was endowed as its stderr, `fd:2`, read off its +/// startup block with nothing but the stack and the code's own addresses: no +/// word of the image is relocated yet, so no table, `str` pointer or +/// formatting is reached. `None` where the block names none, or is no table +/// up to that name: the exit status is then all this process says. pub(crate) fn stderr() -> Option { - use toyos_abi::syscall::{endowments, MAX_ENDOWMENTS, MAX_LABELS_LEN}; - const ENTRY: usize = 16; - let word = |b: &[u8], at: usize| -> Option { - let bytes = b.get(at..at.checked_add(4)?)?; - Some(u32::from_ne_bytes([*bytes.first()?, *bytes.get(1)?, *bytes.get(2)?, *bytes.get(3)?]) as usize) - }; - let mut buf = [0u8; 8 + MAX_ENDOWMENTS * ENTRY + MAX_LABELS_LEN]; - let n = endowments(&mut buf); - let table = buf.get(..n)?; - let count = word(table, 0)?.min(MAX_ENDOWMENTS); - let blob = count.checked_mul(ENTRY)?.checked_add(8)?; - for i in 0..count { - let at = i.checked_mul(ENTRY)?.checked_add(8)?; - let (off, len) = (blob.checked_add(word(table, at)?)?, word(table, at.checked_add(4)?)?); - if table.get(off..off.checked_add(len)?) == Some(b"fd:2".as_slice()) { - return Some(RawHandle(word(table, at.checked_add(8)?)? as u32)); - } - } - None + crate::startup::early(b"fd:2").map(RawHandle::endowed) } /// # Safety diff --git a/sdk/toyos/src/spawn.rs b/sdk/toyos/src/spawn.rs index 2c10d830ac4..5b72e7b7594 100644 --- a/sdk/toyos/src/spawn.rs +++ b/sdk/toyos/src/spawn.rs @@ -4,10 +4,11 @@ //! [`Spawn::start`] says which the spawn is, or refuses it, and what it //! answers is the one way to start that kind. //! -//! **A helper inherits.** It holds this process's namespace under `svc`, its -//! environment as given but for its starting directory, and exactly the -//! descriptors and endowments its spawn names; never this process's launcher, -//! which only an endowment the caller names hands on. +//! **A helper inherits.** It holds a duplicate of each service this process +//! was endowed ([`inherited`]), its environment as given but for its starting +//! directory, and exactly the descriptors and endowments its spawn names; +//! never this process's launcher, which only an endowment the caller names +//! hands on. //! //! **An independent launch runs its row.** The launcher starts the program a //! manifest row declares, holding that row's authority and the connectors @@ -15,14 +16,14 @@ //! stdio and no image of the caller's choosing. A launch this process cannot //! make is refused, never spawned as a helper instead. //! -//! **The job the child joins is no endowment of that kind**: a launch carries -//! it too. The one endowed under [`JOB_LABEL`], or else this process's own. +//! **A helper's table is one list**, the handles it is endowed and what each +//! is called, in one order ([`Helper::spawn`]): the kernel moves the handles +//! and the names go in the child's startup block ([`crate::startup`]). use toyos_abi::handle::TransferEntry; -use toyos_abi::syscall::{ - self, SpawnArgs, SpawnEndow, SyscallError, FD_PREFIX, JOB_LABEL, MAX_PATH, MAX_PROCESS_NAME, - MAX_SPAWN_ENDOWMENTS, MAX_SPAWN_LABELS_LEN, SVC_LABEL, -}; +use toyos_abi::syscall::{self, SpawnArgs, SyscallError, MAX_PATH, MAX_PROCESS_NAME, MAX_SPAWN_ENDOWMENTS}; + +use crate::endow::FD_PREFIX; use crate::fd::Kind as FdKind; use crate::ipc::IpcError; @@ -71,27 +72,68 @@ pub struct Spawn<'a> { /// other child is given. A launch carries 0 to 2 alone. pub fds: &'a [(u32, BorrowedHandle<'a>, FdKind)], /// `(label, handle)` the child is endowed, each moved, with the rights - /// its [`Moved`] names: by a spawn that - /// happens to the child, and closed by one refused. One under - /// [`JOB_LABEL`] is the job the child joins, which a launch carries too; - /// any other only a helper takes. + /// its [`Moved`] names: by a spawn that happens to the child, and closed + /// by one refused. Only a helper takes any; one labelled as a service + /// (`crate::endow::SVC_PREFIX`) is the child's in place of this + /// process's of that label. pub endow: Vec<(&'a str, Moved)>, - /// `(name, connector)` put in a launched child's namespace beside its - /// row's, each moved as `endow`'s are. + /// Which of this process's services a helper is given: a launch runs its + /// row, and names [`Inherit::All`]. + pub inherit: Inherit<'a>, + /// The job the child joins, a handle carrying `WRITE` this process keeps, + /// or else this process's own. + pub job: Option>, + /// `(name, connector)` a launched child is given beside its row's + /// services, each moved as `endow`'s are. pub provided: Vec<(&'a str, OwnedHandle)>, pub parent: Parent<'a>, /// An independent launch through this process's launcher, where a helper /// is not. pub independent: bool, - /// A launcher this process holds, a namespace holding [`launch::LAUNCHER`], - /// which a launch goes through in place of the one it was endowed: - /// borrowed, and refused a helper. + /// A launcher's connector this process holds, which a launch goes through + /// in place of the one it was endowed: borrowed, and refused a helper. pub launcher: Option>, /// A `SysCap` carrying `Rights::PAGER`, borrowed, which starts the child /// as a pager (`SpawnArgs::pager`): a helper's alone. pub pager: Option>, } +/// Which of the services this process was endowed a helper is given a +/// duplicate of. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum Inherit<'a> { + /// Every one. + #[default] + All, + /// Those of these names (`crate::endow::connector`'s) it holds, and no other. + Only(&'a [&'a str]), +} + +/// The labels of `held`, this process's services, a helper is given: those +/// `inherit` admits, but one the spawn endows itself. The one place a +/// helper's inheritance is decided. +fn inherited<'t>(held: impl Iterator, inherit: Inherit<'_>, endowed: &[&str]) -> Vec<&'t str> { + held.filter(|label| !endowed.contains(label)) + .filter(|label| match inherit { + Inherit::All => true, + Inherit::Only(names) => crate::endow::service_of(label).is_some_and(|name| names.contains(&name)), + }) + .collect() +} + +/// The label a helper's descriptor `fd` of `kind` arrives under: a file's +/// names the first descriptor of `fds` that shares its exported connection, +/// which the child adopts once and aliases. +fn fd_label(fd: u32, handle: RawHandle, kind: FdKind, fds: &[(u32, BorrowedHandle<'_>, FdKind)]) -> String { + match kind { + FdKind::File => { + let group = fds.iter().find(|(_, h, k)| *k == FdKind::File && h.raw() == handle).map_or(fd, |(first, _, _)| *first); + alloc::format!("{FD_PREFIX}{fd}:file:{group}") + } + kind => alloc::format!("{FD_PREFIX}{fd}{}", kind.suffix()), + } +} + /// Why nothing was started. #[derive(Debug, PartialEq, Eq)] pub enum Refused { @@ -131,6 +173,7 @@ struct Shape { independent: bool, detached: bool, endowed: bool, + narrowed: bool, extra_fds: bool, image_named: bool, provided: bool, @@ -155,8 +198,11 @@ fn kind(shape: Shape) -> Result { _ => Ok(Kind::Helper), }; } + if shape.narrowed { + return Err("a launch runs its row, which no inheritance narrows"); + } match (shape.endowed, shape.extra_fds, shape.image_named) { - (true, _, _) => Err("a launch carries no endowment but its job"), + (true, _, _) => Err("a launch carries no endowment"), (_, true, _) => Err("a launch carries descriptors 0 to 2 alone"), _ if shape.pager => Err("a launch starts its row as its row declares, never as a pager the caller names"), (_, _, true) => Err("a launch runs its row's own program, so it takes no image the caller names"), @@ -172,7 +218,8 @@ impl<'a> Spawn<'a> { let shape = Shape { independent: self.independent, detached: self.parent == Parent::Detached, - endowed: self.endow.iter().any(|(label, _)| *label != JOB_LABEL), + endowed: !self.endow.is_empty(), + narrowed: self.inherit != Inherit::All, extra_fds: self.fds.iter().any(|&(fd, _, _)| fd > 2), image_named: self.image.is_some() || self.image_held, provided: !self.provided.is_empty(), @@ -211,10 +258,40 @@ impl Helper<'_> { let name = name(s.argv.split(|&b| b == 0).find(|arg| !arg.is_empty()).unwrap_or(s.program.as_bytes())); let env = crate::files::starting_in(s.env, s.cwd) .ok_or(Refused::Invalid("a working directory that is not absolute, is too long or holds a NUL"))?; + // What the child is endowed and what it calls each, in the order the + // kernel installs them. Duplicates rather than this process's own + // handles: an endowment is a move, and a parent that gave its + // services or its stdout away could not spawn a second child. Every + // handle is held here until the spawn has moved it: closed by a + // refusal, given up by a spawn. + let mut table: Vec<(String, Moved)> = Vec::new(); + let named: Vec<&str> = s.endow.iter().map(|(label, _)| *label).collect(); + let services: Vec<(&str, BorrowedHandle<'static>)> = crate::endow::inheritable().collect(); + let inherited = inherited(services.iter().map(|(label, _)| *label), s.inherit, &named); + for (label, handle) in s.endow { + table.push((String::from(label), handle)); + } + for (label, connector) in services.iter().filter(|(label, _)| inherited.contains(label)) { + table.push((String::from(*label), connector.try_clone_to_owned().map_err(Refused::Syscall)?.into())); + } + for &(fd, handle, kind) in s.fds { + let copy = handle.try_clone_to_owned().map_err(Refused::Syscall)?; + table.push((fd_label(fd, handle.raw(), kind, s.fds), copy.into())); + } + if table.len() > MAX_SPAWN_ENDOWMENTS { + return Err(Refused::Invalid("more endowments and descriptors than a spawn carries")); + } + let names: Vec<&str> = table.iter().map(|(label, _)| label.as_str()).collect(); + if names.iter().enumerate().any(|(i, label)| names[..i].contains(label)) { + return Err(Refused::Invalid("two endowments under one name")); + } + if names.contains(&"") { + return Err(Refused::Invalid("an endowment under no name")); + } + let entries: Vec = table.iter().map(|(_, moved)| moved.entry()).collect(); // Held until the spawn answers: the child copies its pages from it. - let job = s.endow.iter().find(|(label, _)| *label == JOB_LABEL).map(|(_, job)| job.as_handle()); - let block = crate::startup::Block::new(s.argv, &env, job).map_err(|why| match why { - crate::startup::Refused::TooLarge => Refused::Invalid("arguments and an environment past what a child starts with"), + let block = crate::startup::Block::new(s.argv, &env, &names, s.job).map_err(|why| match why { + crate::startup::Refused::TooLarge => Refused::Invalid("arguments, an environment and names past what a child starts with"), crate::startup::Refused::Memory(e) => Refused::Syscall(e), })?; let program = crate::elf::program::lay_out(image.bytes(), crate::elf::Machine::RUNNING, image.source()) @@ -222,68 +299,14 @@ impl Helper<'_> { .starting_with(&block); let maps = program.maps(); let build_id = program.build_id(); - let names_svc = s.endow.iter().any(|(label, _)| *label == SVC_LABEL); - if s.endow.len() + s.fds.len() + usize::from(!names_svc) > MAX_SPAWN_ENDOWMENTS { - return Err(Refused::Invalid("more endowments and descriptors than a spawn carries")); - } - let mut entries = [SpawnEndow { label_off: 0, label_len: 0, moved: TransferEntry::unchanged(RawHandle(0)) }; MAX_SPAWN_ENDOWMENTS]; - let mut labels = [0u8; MAX_SPAWN_LABELS_LEN]; - let (mut count, mut used) = (0, 0); - // Duplicates rather than the handles themselves: an endowment is a - // move, and a parent that gave its namespace or its stdout away could - // not spawn a second child. Every handle the spawn moves is held here - // until it has: closed by a refusal, given up by a spawn. - let mut moved: Vec = Vec::new(); - let svc = match crate::endow::namespace().filter(|_| !names_svc) { - Some(ns) => Some(ns.as_handle().try_clone_to_owned().map_err(Refused::Syscall)?), - None => None, - }; - let mut number = [0u8; 10]; - let mut group_number = [0u8; 10]; - let mut put = |label: &[&[u8]], moved: TransferEntry, count: &mut usize, used: &mut usize| { - let len: usize = label.iter().map(|part| part.len()).sum(); - let room = labels.get_mut(*used..*used + len).ok_or(Refused::Invalid("endowment labels past what a spawn carries"))?; - let mut at = 0; - for part in label { - room[at..at + part.len()].copy_from_slice(part); - at += part.len(); - } - entries[*count] = SpawnEndow { label_off: *used as u32, label_len: len as u32, moved }; - (*count, *used) = (*count + 1, *used + len); - Ok(()) - }; - for (label, handle) in s.endow { - put(&[label.as_bytes()], handle.entry(), &mut count, &mut used)?; - moved.push(handle); - } - if let Some(svc) = svc { - let svc = Moved::from(svc); - put(&[SVC_LABEL.as_bytes()], svc.entry(), &mut count, &mut used)?; - moved.push(svc); - } - for &(fd, handle, kind) in s.fds { - let copy = Moved::from(handle.try_clone_to_owned().map_err(Refused::Syscall)?); - let digits = decimal(fd, &mut number); - if kind == FdKind::File { - // Duplicated file actions may name one exported connection - // several times. The child adopts it once and aliases its fd. - let group = s.fds.iter().find(|(_, h, k)| *k == FdKind::File && h.raw() == handle.raw()) - .expect("this descriptor is in the spawn").0; - put(&[FD_PREFIX.as_bytes(), digits, b":file:", decimal(group, &mut group_number)], copy.entry(), &mut count, &mut used)?; - } else { - put(&[FD_PREFIX.as_bytes(), digits, kind.suffix().as_bytes()], copy.entry(), &mut count, &mut used)?; - } - moved.push(copy); - } + let none = u64::from(toyos_abi::HANDLE_INVALID.0); let args = SpawnArgs { path_ptr: path.as_ptr() as u64, path_len: path.len() as u64, name_ptr: name.as_ptr() as u64, name_len: name.len() as u64, endow_ptr: entries.as_ptr() as u64, - endow_count: count as u64, - labels_ptr: labels.as_ptr() as u64, - labels_len: used as u64, + endow_count: entries.len() as u64, maps_ptr: maps.as_ptr() as u64, maps_count: maps.len() as u64, entry: program.entry, @@ -291,22 +314,23 @@ impl Helper<'_> { build_id_ptr: build_id.as_ptr() as u64, build_id_len: build_id.len() as u64, place: match s.parent { - Parent::Caller => u64::from(toyos_abi::HANDLE_INVALID.0), + Parent::Caller => none, Parent::Place(place) => u64::from(place.raw().0), Parent::Detached => unreachable!("a detached child is refused a helper's spawn"), }, - pager: s.pager.map_or(u64::from(toyos_abi::HANDLE_INVALID.0), |h| u64::from(h.raw().0)), + job: s.job.map_or(none, |job| u64::from(job.raw().0)), + pager: s.pager.map_or(none, |h| u64::from(h.raw().0)), }; // SAFETY: every pointer is to a buffer above that outlives the call, - // and every handle the endowment names is one `moved` owns. + // and every handle the endowment names is one `table` owns. match unsafe { syscall::spawn(&args) } { Ok(handle) => { // The spawn moved them into the child. - moved.into_iter().for_each(|moved| { moved.handle.into_raw(); }); + table.into_iter().for_each(|(_, moved)| { moved.handle.into_raw(); }); // SAFETY: the kernel installed this handle for this call and no other. Ok(unsafe { Process::from_raw(handle) }) } - // An endowment moves only on a spawn that happened, so `moved` + // An endowment moves only on a spawn that happened, so `table` // still owns them, and closes them as it drops. Err(e) => Err(Refused::Syscall(e)), } @@ -319,23 +343,21 @@ impl Independent<'_> { /// process holds no launcher, the launcher will not open, or no row /// declares the program or the launcher's row may not start it. /// - /// The stdio handles and the place are **duplicated** before they go, and - /// a handle to this process's own job made: a launch moves what it - /// carries. A job the spawn endowed goes itself, moved as an endowment is. + /// The stdio handles, the place and the job are **duplicated** before + /// they go, or a handle to this process's own job made: a launch moves + /// what it carries. pub fn launch(self) -> Result { - let mut s = self.0; + let s = self.0; for (i, &(_, handle, kind)) in s.fds.iter().enumerate() { if kind == FdKind::File && s.fds[..i].iter().any(|(_, prior, kind)| *kind == FdKind::File && prior.raw() == handle.raw()) { return Err(Refused::Invalid("a launch needs a distinct exported connection for each file descriptor")); } } let held = match s.launcher { - Some(named) => named.raw(), - None => crate::endow::launcher().ok_or(Refused::Permission("this process holds no launcher"))?.as_handle().raw(), + Some(named) => named, + None => crate::endow::launcher().ok_or(Refused::Permission("this process holds no launcher"))?.as_handle(), }; - let conn = syscall::namespace_open(held, launch::LAUNCHER) - .map(|h| crate::ipc::Connection(crate::OwnedHandle(h))) - .map_err(|_| Refused::Permission("the launcher this process holds will not open"))?; + let conn = crate::port::connect(held).map_err(|_| Refused::Permission("the launcher this process holds will not open"))?; let place = match s.parent { Parent::Detached => None, Parent::Place(place) => Some(place), @@ -347,11 +369,8 @@ impl Independent<'_> { if s.fds.len() > MAX_LAUNCH_FDS { return Err(Refused::Invalid("a launch carries descriptors 0 to 2 alone")); } - let job = match s.endow.iter().position(|(label, _)| *label == JOB_LABEL) { - Some(at) => match s.endow.swap_remove(at).1 { - Moved { handle, rights: None } => handle, - Moved { rights: Some(_), .. } => return Err(Refused::Invalid("a launch's job goes with the rights it has")), - }, + let job = match s.job { + Some(job) => job.try_clone_to_owned().map_err(Refused::Syscall)?, None => crate::job::own().map_err(Refused::Syscall)?.0, }; let parent = match place { @@ -388,19 +407,6 @@ impl Independent<'_> { } } -/// `n` in decimal, in `buf`. -fn decimal(mut n: u32, buf: &mut [u8; 10]) -> &[u8] { - let mut at = buf.len(); - loop { - at -= 1; - buf[at] = b'0' + (n % 10) as u8; - n /= 10; - if n == 0 { - return &buf[at..]; - } - } -} - /// The name a child run as `program`, its `argv[0]`, goes by in the kernel's /// records (`SpawnArgs::name_ptr`): its last component, cut at a character to /// [`MAX_PROCESS_NAME`] bytes, with `_` for a character a name does not hold, @@ -455,6 +461,7 @@ mod tests { for shape in [ Shape::default(), Shape { endowed: true, ..Shape::default() }, + Shape { narrowed: true, ..Shape::default() }, Shape { extra_fds: true, ..Shape::default() }, Shape { image_named: true, ..Shape::default() }, Shape { pager: true, ..Shape::default() }, @@ -468,7 +475,7 @@ mod tests { #[test] fn each_kind_refuses_what_the_other_alone_takes() { const LAUNCH: Shape = - Shape { independent: true, detached: false, endowed: false, extra_fds: false, image_named: false, provided: false, launcher_named: false, pager: false }; + Shape { independent: true, detached: false, endowed: false, narrowed: false, extra_fds: false, image_named: false, provided: false, launcher_named: false, pager: false }; assert_eq!(kind(LAUNCH), Ok(Kind::Launch)); assert_eq!(kind(Shape { detached: true, provided: true, launcher_named: true, ..LAUNCH }), Ok(Kind::Launch)); for shape in [ @@ -480,6 +487,7 @@ mod tests { } for shape in [ Shape { endowed: true, ..LAUNCH }, + Shape { narrowed: true, ..LAUNCH }, Shape { extra_fds: true, ..LAUNCH }, Shape { image_named: true, ..LAUNCH }, Shape { pager: true, ..LAUNCH }, @@ -488,6 +496,36 @@ mod tests { } } + /// A helper is given every service its parent holds, those its spawn + /// names alone, or none; and one the spawn endows itself is the spawn's. + #[test] + fn a_helper_inherits_the_services_its_spawn_admits() { + let held = ["svc:fs:/tmp", "svc:net", "provide:surface"]; + let given = |inherit, endowed: &[&str]| inherited(held.into_iter(), inherit, endowed); + assert_eq!(given(Inherit::All, &[]), held); + assert_eq!(given(Inherit::All, &["fd:0", "launcher"]), held); + assert_eq!(given(Inherit::All, &["svc:net"]), ["svc:fs:/tmp", "provide:surface"]); + assert_eq!(given(Inherit::Only(&["fs:/tmp", "surface", "absent"]), &[]), ["svc:fs:/tmp", "provide:surface"]); + assert_eq!(given(Inherit::Only(&["net"]), &["svc:net"]), [""; 0]); + assert_eq!(given(Inherit::Only(&[]), &[]), [""; 0]); + // A name is a service's, never a label's: `svc:net` names no service. + assert_eq!(given(Inherit::Only(&["svc:net"]), &[]), [""; 0]); + } + + /// A descriptor's label is its number and kind, and each file sharing one + /// exported connection names the first descriptor that does. + #[test] + fn a_descriptor_is_labelled_by_its_number_and_kind() { + // SAFETY: the borrows are numbers this test compares and never uses. + let handle = |raw| unsafe { BorrowedHandle::borrow_raw(RawHandle(raw)) }; + let fds = [(0, handle(7), FdKind::Handle), (1, handle(9), FdKind::File), (2, handle(9), FdKind::File), (5, handle(11), FdKind::File), (6, handle(7), FdKind::Exported)]; + let labels: Vec = fds.iter().map(|&(fd, h, kind)| fd_label(fd, h.raw(), kind, &fds)).collect(); + assert_eq!(labels, ["fd:0", "fd:1:file:1", "fd:2:file:1", "fd:5:file:5", "fd:6:exported"]); + for (label, &(fd, _, kind)) in labels.iter().zip(&fds) { + assert_eq!(crate::fd::endowed_fd(label), Some((fd as i32, kind)), "{label}"); + } + } + /// Whatever a program is called, its name is one the kernel takes, and an /// applet's is the applet's. #[test] diff --git a/sdk/toyos/src/startup.rs b/sdk/toyos/src/startup.rs index f46d76145e1..6fe04a6fd05 100644 --- a/sdk/toyos/src/startup.rs +++ b/sdk/toyos/src/startup.rs @@ -1,25 +1,35 @@ -//! What a parent tells the child it starts: its arguments and its -//! environment, a block the parent writes into memory of its own and lays out -//! in the child at [`AT`], directly below the program -//! (`crate::elf::program`). The kernel copies its pages as it copies the -//! program's and reads none of it; every copy of this crate in the child +//! What a parent tells the child it starts: its arguments, its environment +//! and what it calls each handle it endowed, a block the parent writes into +//! memory of its own and lays out in the child at [`AT`], directly below the +//! program (`crate::elf::program`). The kernel copies its pages as it copies +//! the program's and reads none of it; every copy of this crate in the child //! reads it where it lies. //! //! ```text -//! AT argc | argv[0] .. argv[argc - 1] | 0 | envp[0] .. | 0 | the strings +//! AT names | argc | argv[0] .. argv[argc - 1] | 0 | envp[0] .. | 0 | the strings | the names +//! +//! names bytes u32 | count u32 | count x { len u16 | name } //! ``` //! //! Eight-byte words, each pointer the address in the child of a string ended -//! by a NUL, an environment's `KEY=VALUE`. A page of zeros is a block that -//! says nothing, which a program laid out and given no block starts with. +//! by a NUL, an environment's `KEY=VALUE`; the first word is the address of +//! the names, or 0 for none. A page of zeros is a block that says nothing, +//! which a program laid out and given no block starts with. +//! +//! **The names are the child's endowment table**: entry `i` is what its +//! parent calls the handle at `RawHandle::endowed(i)`, in the order the spawn +//! endowed them (`toyos_abi::syscall::SpawnArgs`). `bytes` counts the whole +//! table, and nothing aligns an entry. //! -//! **The child trusts it as it trusts its own program**, which the same -//! parent laid out: nothing here checks a pointer. +//! **The child trusts the arguments and environment as it trusts its own +//! program**, which the same parent laid out: nothing checks a pointer of +//! theirs. The names it checks once, whole ([`names`]), before anything looks +//! one up: a table that is not one ends the child ([`MALFORMED_STATUS`]). use alloc::vec::Vec; use core::ffi::CStr; -use toyos_abi::syscall::{SyscallError, PAGE_SIZE}; +use toyos_abi::syscall::{SyscallError, MAX_SPAWN_ENDOWMENTS, PAGE_SIZE}; use crate::elf::program::BASE; use crate::shm::SharedMemory; @@ -35,22 +45,48 @@ pub const AT: u64 = BASE - RESERVED; /// The most bytes of a block: a reserved page is left below the program. pub const MAX_BLOCK: usize = (RESERVED - PAGE_SIZE as u64) as usize; +/// A table's two counts. +const TABLE_HEADER: usize = 8; + +/// The status a process whose endowment table is malformed exits with, +/// having said so on its `fd:2` where the table still names one. +pub const MALFORMED_STATUS: i32 = 125; + fn strings(run: &[u8]) -> impl Iterator { run.split(|&b| b == 0).filter(|s| !s.is_empty()) } -/// The bytes of the block that says `args` and `env`. `None` past [`MAX_BLOCK`]. -fn bytes(args: &[u8], env: &[u8]) -> Option> { +/// The table that calls the `i`th endowment `names[i]`. `None` for more +/// names than a spawn endows, or a name past what its length word holds. +fn table(names: &[&str]) -> Option> { + if names.len() > MAX_SPAWN_ENDOWMENTS { + return None; + } + let bytes = TABLE_HEADER + names.iter().map(|name| 2 + name.len()).sum::(); + let mut table = Vec::with_capacity(bytes); + table.extend_from_slice(&u32::try_from(bytes).ok()?.to_ne_bytes()); + table.extend_from_slice(&(names.len() as u32).to_ne_bytes()); + for name in names { + table.extend_from_slice(&u16::try_from(name.len()).ok()?.to_ne_bytes()); + table.extend_from_slice(name.as_bytes()); + } + Some(table) +} + +/// The bytes of the block that says `args`, `env` and `names`. `None` past +/// [`MAX_BLOCK`], or for names [`table`] makes none of. +fn bytes(args: &[u8], env: &[u8], names: &[&str]) -> Option> { + let table = if names.is_empty() { Vec::new() } else { table(names)? }; let argc = strings(args).count(); - let words = argc + strings(env).count() + 3; + let words = argc + strings(env).count() + 4; let text: usize = strings(args).chain(strings(env)).map(|s| s.len() + 1).sum(); - if words * 8 + text > MAX_BLOCK { + if words * 8 + text + table.len() > MAX_BLOCK { return None; } let mut block = alloc::vec![0u8; words * 8]; - block.reserve_exact(text); - block[..8].copy_from_slice(&(argc as u64).to_ne_bytes()); - let mut word = 1; + block.reserve_exact(text + table.len()); + block[8..16].copy_from_slice(&(argc as u64).to_ne_bytes()); + let mut word = 2; for run in [args, env] { for string in strings(run) { let at = AT + block.len() as u64; @@ -62,13 +98,19 @@ fn bytes(args: &[u8], env: &[u8]) -> Option> { // The null that ends the run's pointers. word += 1; } + if !table.is_empty() { + let at = AT + block.len() as u64; + block[..8].copy_from_slice(&at.to_ne_bytes()); + block.extend_from_slice(&table); + } Some(block) } /// Why no block was made. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Refused { - /// The arguments and environment are past [`MAX_BLOCK`]. + /// The arguments, environment and names are past [`MAX_BLOCK`], or the + /// names more than a spawn endows. TooLarge, /// The kernel made no memory for it. Memory(SyscallError), @@ -84,11 +126,11 @@ pub struct Block { impl Block { /// The block that says `args` and `env`, each a run of strings a NUL - /// separates, an empty one skipped, in memory counted to the job `job` is - /// a handle to carrying `WRITE`, the one the child runs in, or else to - /// this process's own. - pub fn new(args: &[u8], env: &[u8], job: Option>) -> Result { - let bytes = bytes(args, env).ok_or(Refused::TooLarge)?; + /// separates, an empty one skipped, and calls the spawn's `i`th endowment + /// `names[i]`, in memory counted to the job `job` is a handle to carrying + /// `WRITE`, the one the child runs in, or else to this process's own. + pub fn new(args: &[u8], env: &[u8], names: &[&str], job: Option>) -> Result { + let bytes = bytes(args, env, names).ok_or(Refused::TooLarge)?; let memory = match job { Some(job) => SharedMemory::create_for(job, bytes.len()), None => SharedMemory::create(bytes.len()), @@ -119,7 +161,7 @@ pub fn args() -> (usize, *const *const u8) { // SAFETY: a program `crate::elf::program::lay_out` laid out starts with // the block's first page mapped at `AT`, and a block's pointers follow // its count. - unsafe { (at.read() as usize, at.add(1).cast()) } + unsafe { (at.add(1).read() as usize, at.add(2).cast()) } } /// The environment this process started with, each entry without its NUL. @@ -135,6 +177,141 @@ pub fn env() -> impl Iterator { .map(|entry| unsafe { CStr::from_ptr(entry.cast()) }.to_bytes()) } +/// Why a table is none. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Malformed { + /// It does not lie inside the block's reservation. + Outside, + /// Its length word is not its length, or is less than its two counts. + Length, + /// It counts more names than a spawn endows. + Count, + /// A name runs past its end. + Truncated, + /// Bytes follow its last name. + Trailing, + /// A name is not UTF-8. + NotUtf8, + /// A name is empty. + Empty, + /// A name is an earlier one's. + Twice, +} + +impl Malformed { + /// What a process whose table is malformed says on its `fd:2`. + pub fn line(self) -> &'static [u8] { + match self { + Self::Outside => b"this program cannot start: its endowment table lies outside its startup block\n", + Self::Length => b"this program cannot start: its endowment table's length word is not its length\n", + Self::Count => b"this program cannot start: its endowment table names more handles than a spawn endows\n", + Self::Truncated => b"this program cannot start: a name in its endowment table runs past the table\n", + Self::Trailing => b"this program cannot start: bytes follow the last name of its endowment table\n", + Self::NotUtf8 => b"this program cannot start: a name in its endowment table is not UTF-8\n", + Self::Empty => b"this program cannot start: a name in its endowment table is empty\n", + Self::Twice => b"this program cannot start: its endowment table gives one name twice\n", + } + } +} + +fn word(table: &[u8], at: usize) -> Option { + let bytes = table.get(at..at.checked_add(4)?)?; + Some(u32::from_ne_bytes([*bytes.first()?, *bytes.get(1)?, *bytes.get(2)?, *bytes.get(3)?])) +} + +/// A table's entries, each a name's bytes, ending with the first that runs +/// past the table. Unchecked beyond that, and reached before this image is +/// relocated ([`early`]): nothing here panics, formats or names a static. +fn entries(table: &[u8]) -> impl Iterator> { + let count = word(table, 4).unwrap_or(0) as usize; + let mut next = Some(TABLE_HEADER); + (0..count).map_while(move |_| { + let at = next?; + let name = table + .get(at..at.saturating_add(2)) + .and_then(|len| Some(usize::from(u16::from_ne_bytes([*len.first()?, *len.get(1)?])))) + .and_then(|len| table.get(at.saturating_add(2)..at.saturating_add(2).saturating_add(len))); + next = name.map(|name| at.saturating_add(2).saturating_add(name.len())); + Some(name.ok_or(Malformed::Truncated)) + }) +} + +/// The names `table` holds, written to `into` in order: how many, or why it +/// is no table. +pub fn names<'a>(table: &'a [u8], into: &mut [&'a str; MAX_SPAWN_ENDOWMENTS]) -> Result { + if word(table, 0).map(|bytes| bytes as usize) != Some(table.len()) || table.len() < TABLE_HEADER { + return Err(Malformed::Length); + } + let count = word(table, 4).ok_or(Malformed::Length)? as usize; + if count > MAX_SPAWN_ENDOWMENTS { + return Err(Malformed::Count); + } + let mut end = TABLE_HEADER; + for (i, name) in entries(table).enumerate() { + let name = name?; + let text = core::str::from_utf8(name).map_err(|_| Malformed::NotUtf8)?; + if text.is_empty() { + return Err(Malformed::Empty); + } + if into[..i].contains(&text) { + return Err(Malformed::Twice); + } + into[i] = text; + end += 2 + name.len(); + } + if end != table.len() { + return Err(Malformed::Trailing); + } + Ok(count) +} + +/// Whether `bytes` from `at` lie inside the block's reservation and hold a +/// table's two counts. +fn inside(at: u64, bytes: u64) -> bool { + bytes >= TABLE_HEADER as u64 && at >= AT && at.checked_add(bytes).is_some_and(|end| end <= AT + MAX_BLOCK as u64) +} + +/// This process's table where it lies: empty for a block that names none. +fn own() -> Result<&'static [u8], Malformed> { + // SAFETY: the block's first page is mapped at `AT` ([`args`]). + let at = unsafe { (AT as *const u64).read() }; + if at == 0 { + return Ok(&[]); + } + if !inside(at, TABLE_HEADER as u64) { + return Err(Malformed::Outside); + } + // SAFETY: inside the reservation, which a parent that wrote this address + // mapped through the table's end; one that did not is a fault there, the + // child's own. Nothing writes the table, so the borrow lasts. + let bytes = u64::from(unsafe { (at as *const u32).read_unaligned() }); + if !inside(at, bytes) { + return Err(Malformed::Outside); + } + Ok(unsafe { core::slice::from_raw_parts(at as *const u8, bytes as usize) }) +} + +/// The names this process's parent gave its endowments, in order: how many. +pub fn endowed(into: &mut [&'static str; MAX_SPAWN_ENDOWMENTS]) -> Result { + match own()? { + [] => Ok(0), + table => names(table, into), + } +} + +/// Which endowment this process's table calls `name`, by its bytes alone: +/// for a caller that runs before the image is relocated or after the table +/// was found malformed, where no checked name exists. `None` for a table +/// that names none or is no table up to that name. +pub(crate) fn early(name: &[u8]) -> Option { + unchecked_position(own().ok()?, name) +} + +/// [`early`]'s walk over `table`. +fn unchecked_position(table: &[u8], name: &[u8]) -> Option { + entries(table).take(MAX_SPAWN_ENDOWMENTS).position(|entry| entry == Ok(name)) +} + #[cfg(test)] mod tests { use super::*; @@ -149,33 +326,136 @@ mod tests { Some(&rest[..rest.iter().position(|&b| b == 0).expect("a string ends inside the block")]) } - /// The count, then each run's pointers in order and a null after each, - /// every pointer the address its string lies at once the block is at `AT`. + /// The names a table holds, or why it is none. + fn read(table: &[u8]) -> Result, Malformed> { + let mut into = [""; MAX_SPAWN_ENDOWMENTS]; + names(table, &mut into).map(|count| into[..count].iter().map(|name| (*name).into()).collect()) + } + + /// The names' address, then the count, then each run's pointers in order + /// and a null after each, every pointer the address its string lies at + /// once the block is at `AT`. #[test] fn a_block_s_pointers_name_its_strings_in_order() { - let block = bytes(b"/bin/echo\0-n\0hello world", b"HOME=/home/toy\0PWD=/\0").unwrap(); - assert_eq!(u64::from_ne_bytes(block[..8].try_into().unwrap()), 3); - let words: Vec<_> = (1..8).map(|word| string(&block, word)).collect(); + let block = bytes(b"/bin/echo\0-n\0hello world", b"HOME=/home/toy\0PWD=/\0", &[]).unwrap(); + assert_eq!(u64::from_ne_bytes(block[..8].try_into().unwrap()), 0); + assert_eq!(u64::from_ne_bytes(block[8..16].try_into().unwrap()), 3); + let words: Vec<_> = (2..9).map(|word| string(&block, word)).collect(); assert_eq!( words, [Some(&b"/bin/echo"[..]), Some(b"-n"), Some(b"hello world"), None, Some(b"HOME=/home/toy"), Some(b"PWD=/"), None] ); - assert_eq!(block.len(), 8 * 8 + 10 + 3 + 12 + 15 + 6); + assert_eq!(block.len(), 9 * 8 + 10 + 3 + 12 + 15 + 6); } - /// Nothing said is a count of zero and two nulls: what a page of zeros reads as. + /// Nothing said is no table, a count of zero and two nulls: what a page + /// of zeros reads as. #[test] fn a_block_that_says_nothing_is_zeros() { - assert_eq!(bytes(b"", b"\0\0").unwrap(), [0u8; 24]); + assert_eq!(bytes(b"", b"\0\0", &[]).unwrap(), [0u8; 32]); } /// A block ends below the program with a page to spare, and one byte more is refused. #[test] fn a_block_past_its_reservation_is_refused() { - let fits = alloc::vec![b'a'; MAX_BLOCK - 4 * 8 - 1]; - assert_eq!(bytes(&fits, b"").map(|block| block.len()), Some(MAX_BLOCK)); + let fits = alloc::vec![b'a'; MAX_BLOCK - 5 * 8 - 1]; + assert_eq!(bytes(&fits, b"", &[]).map(|block| block.len()), Some(MAX_BLOCK)); let mut past = fits; past.push(b'a'); - assert_eq!(bytes(&past, b""), None); + assert_eq!(bytes(&past, b"", &[]), None); + // The names count against the same reservation. + let fits = alloc::vec![b'a'; MAX_BLOCK - 5 * 8 - 1 - TABLE_HEADER - 2 - 1]; + assert_eq!(bytes(&fits, b"", &["n"]).map(|block| block.len()), Some(MAX_BLOCK)); + assert_eq!(bytes(&fits, b"", &["nn"]), None); + } + + /// A block's first word is where its names lie once it is at `AT`, after + /// the strings, and the names read back in the order written, whatever + /// bytes a name holds. + #[test] + fn a_block_s_names_read_back_in_the_order_endowed() { + let given = ["fd:0", "svc:fs:/home/toy", "dev:pci:0c:03:30", "na\u{ef}ve name"]; + let block = bytes(b"prog", b"", &given).unwrap(); + let at = (u64::from_ne_bytes(block[..8].try_into().unwrap()) - AT) as usize; + assert_eq!(at, 5 * 8 + 5); + assert_eq!(read(&block[at..]), Ok(given.map(Into::into).to_vec())); + assert_eq!(string(&block, 2), Some(&b"prog"[..])); + } + + /// As many names as a spawn endows are a table, and one more is none. + #[test] + fn a_table_holds_as_many_names_as_a_spawn_endows() { + let names: Vec = (0..=MAX_SPAWN_ENDOWMENTS).map(|i| alloc::format!("n{i}")).collect(); + let names: Vec<&str> = names.iter().map(|name| name.as_str()).collect(); + let most = &names[..MAX_SPAWN_ENDOWMENTS]; + assert_eq!(read(&table(most).unwrap()).map(|read| read.len()), Ok(MAX_SPAWN_ENDOWMENTS)); + assert_eq!(table(&names), None); + let long = "n".repeat(usize::from(u16::MAX) + 1); + assert_eq!(table(&[&long]), None); + } + + /// Every way bytes are no table is refused by its own reason, and none + /// reads past them. + #[test] + fn a_malformed_table_is_refused_by_its_reason() { + let good = table(&["fd:1", "svc:net"]).unwrap(); + assert_eq!(read(&good), Ok(alloc::vec!["fd:1".into(), "svc:net".into()])); + let with = |change: &dyn Fn(&mut Vec)| { + let mut table = good.clone(); + change(&mut table); + read(&table) + }; + let count = |n: u32| move |t: &mut Vec| t[4..8].copy_from_slice(&n.to_ne_bytes()); + let length = |n: u32| move |t: &mut Vec| t[..4].copy_from_slice(&n.to_ne_bytes()); + + assert_eq!(read(&[]), Err(Malformed::Length)); + assert_eq!(read(&good[..7]), Err(Malformed::Length)); + assert_eq!(with(&length(good.len() as u32 + 1)), Err(Malformed::Length)); + assert_eq!(with(&length(good.len() as u32 - 1)), Err(Malformed::Length)); + assert_eq!(with(&|t| t.truncate(t.len() - 1)), Err(Malformed::Length)); + assert_eq!(with(&count(MAX_SPAWN_ENDOWMENTS as u32 + 1)), Err(Malformed::Count)); + assert_eq!(with(&count(u32::MAX)), Err(Malformed::Count)); + // A third name where the table ends, and a second whose length runs past it. + assert_eq!(with(&count(3)), Err(Malformed::Truncated)); + assert_eq!(with(&|t| t[14..16].copy_from_slice(&u16::MAX.to_ne_bytes())), Err(Malformed::Truncated)); + // One name counted where two lie. + assert_eq!(with(&count(1)), Err(Malformed::Trailing)); + assert_eq!(with(&count(0)), Err(Malformed::Trailing)); + assert_eq!(with(&|t| t[10] = 0xff), Err(Malformed::NotUtf8)); + assert_eq!(read(&table(&["fd:1", ""]).unwrap()), Err(Malformed::Empty)); + assert_eq!(read(&table(&["fd:1", "svc:net", "fd:1"]).unwrap()), Err(Malformed::Twice)); + // The table of no names is its two counts. + assert_eq!(read(&table(&[]).unwrap()), Ok(Vec::new())); + } + + /// The unchecked walk finds a name by its bytes up to the first entry + /// that is none, and nothing past it. + #[test] + fn the_early_walk_stops_at_the_first_entry_that_is_none() { + let mut table = table(&["fd:0", "fd:1", "fd:2"]).unwrap(); + assert_eq!(unchecked_position(&table, b"fd:2"), Some(2)); + assert_eq!(unchecked_position(&table, b"fd:3"), None); + // The second name's length runs past the table: the first is still found. + table[14..16].copy_from_slice(&u16::MAX.to_ne_bytes()); + assert_eq!(unchecked_position(&table, b"fd:0"), Some(0)); + assert_eq!(unchecked_position(&table, b"fd:2"), None); + // A count of every `u32` walks no further than a spawn endows. + table[4..8].copy_from_slice(&u32::MAX.to_ne_bytes()); + assert_eq!(unchecked_position(&table, b"fd:2"), None); + assert_eq!(unchecked_position(&[], b"fd:2"), None); + } + + /// A table lies inside the reservation whole, or is refused before a byte + /// of it is read. + #[test] + fn a_table_outside_the_reservation_is_refused() { + let end = AT + MAX_BLOCK as u64; + assert!(inside(AT, 8)); + assert!(inside(end - 8, 8)); + assert!(!inside(end - 7, 8)); + assert!(!inside(AT - 1, 8)); + assert!(!inside(AT, 7)); + assert!(!inside(AT, MAX_BLOCK as u64 + 1)); + assert!(!inside(u64::MAX - 3, 8)); } } diff --git a/sdk/toyos/src/surface.rs b/sdk/toyos/src/surface.rs index 1236a151bdb..0d6d0d5f129 100644 --- a/sdk/toyos/src/surface.rs +++ b/sdk/toyos/src/surface.rs @@ -31,11 +31,11 @@ //! another's line is asked only while it runs nothing of its own. //! //! **The channel is a port, not a name.** A surface makes its own — one per -//! terminal, one per console — and puts the connector in the namespace it -//! builds for the shell it spawns. A wizard three processes below a terminal -//! still finds it, because the namespace is inherited down the same chain the -//! environment used to be; nothing outside that chain can name it, and nothing -//! in it can name a service its parent did not pass. +//! terminal, one per console — and endows the connector to the shell it +//! spawns. A wizard three processes below a terminal still finds it, because +//! services are inherited down the same chain the environment used to be; +//! nothing outside that chain can name it, and nothing in it can name a +//! service its parent did not pass. use alloc::vec::Vec; @@ -51,10 +51,10 @@ use crate::poller::{Poller, READABLE}; use crate::port::Acceptor; use crate::AsHandle; -/// What a surface is called in the namespace of everything below it. +/// What a surface is called among the services of everything below it. /// -/// One name, not one per host: a name in a private namespace does not have to -/// be unique machine-wide, and making it so is what needed a pid in it. +/// One name, not one per host: a name in one process's own table does not +/// have to be unique machine-wide, and making it so is what needed a pid in it. pub const SERVICE: &str = "surface"; /// The file that says which keyboard layout this machine uses. @@ -206,8 +206,8 @@ pub struct Host { } impl Host { - /// Serve from a port the caller made, whose connector it puts in the - /// namespace of every child that is to reach this surface. + /// Serve from a port the caller made, whose connector it gives every + /// child that is to reach this surface. pub fn serve(acceptor: Acceptor) -> Self { Self { acceptor, diff --git a/sdk/toyos/src/syscap.rs b/sdk/toyos/src/syscap.rs index dee2d6940cb..4dcf802bc1d 100644 --- a/sdk/toyos/src/syscap.rs +++ b/sdk/toyos/src/syscap.rs @@ -142,7 +142,9 @@ impl SysCap { } /// The machine's header, then one entry per live thread for as much of - /// `buf` as is left. Answers the bytes written, and `0` if it was refused. + /// `buf` as is left. Answers the bytes written, and `0` if it was refused: + /// to a capability without `ROSTER`, or to a job without the room to hold + /// the roster while it is copied out. /// /// Needs [`Rights::ROSTER`], which is what makes the entries — a pid, a /// size, a CPU time and a **name** for every process in the machine — the diff --git a/sdk/window/src/lib.rs b/sdk/window/src/lib.rs index d7e23c0ea4e..dd19f3ac532 100644 --- a/sdk/window/src/lib.rs +++ b/sdk/window/src/lib.rs @@ -586,8 +586,8 @@ impl Window { Self::create_with_flags(COMPOSITOR, size, title, WINDOW_FLAG_TOPMOST) } - /// A window asked for on the compositor's port named `port` in this - /// program's namespace rather than on `compositor`: which port a window + /// A window asked for on the compositor's port this program was given as + /// the service `port` rather than on `compositor`: which port a window /// came in on is what the compositor places it by. pub fn create_on(port: &str, size: Size, title: &str) -> Result { Self::create_with_flags(port, size, title, 0) diff --git a/sdk/window/src/watcher.rs b/sdk/window/src/watcher.rs index 1dbcef289e3..8d7c0e2cf51 100644 --- a/sdk/window/src/watcher.rs +++ b/sdk/window/src/watcher.rs @@ -93,8 +93,8 @@ pub struct Watcher { } impl Watcher { - /// Watch the clipboard through the compositor's port named `port` in this - /// program's namespace. + /// Watch the clipboard through the compositor's port this program was + /// given as the service `port`. pub fn connect(port: &str) -> Result { let conn = endow::service(port).map_err(CreateError::from)?; conn.try_signal(MSG_CLIPBOARD_WATCH)?; diff --git a/system/compositor/src/signout.rs b/system/compositor/src/signout.rs index 849548d3aca..813273850c3 100644 --- a/system/compositor/src/signout.rs +++ b/system/compositor/src/signout.rs @@ -22,12 +22,11 @@ use std::sync::mpsc; use toyos::endow::{self, Endowments}; use toyos::ipc::{self, Connection, RxStep}; -use toyos::job::JOB_LABEL; -use toyos::namespace::Namespace; +use toyos::port::Connector; use toyos::syscap::SysCap; use toyos::wake::{self, Bell, Waker}; use toyos::AsHandle; -use toyos_abi::syscall::SYSCAP_LABEL; +use toyos::endow::SYSCAP_LABEL; use toyos_desktop::Opening; use toyos_session::{self as wire, Names, SignOut}; @@ -68,14 +67,14 @@ impl Desktop { let (asks, queue) = mpsc::channel(); let (sent, opened) = mpsc::channel(); let (woken, wake) = wake::pair().expect("compositor: no event for its session's opens"); - let login = endowed.take::(wire::LOGIN_PORT); + let login = endowed.take::(wire::LOGIN_PORT); std::thread::Builder::new() .name("launcher".into()) .spawn(move || launcher(queue, login, sent, woken)) .expect("compositor: no thread to launch on"); // Connected here, so the control connection is the compositor's own. - let control = endowed.take::(wire::CONTROL_LABEL).and_then(|controls| { - controls.open(wire::CONTROL).inspect_err(|why| eprintln!("compositor: its session's controls refused it: {why}")).ok() + let control = endowed.take::(wire::CONTROL_LABEL).and_then(|controls| { + controls.connect().inspect_err(|why| eprintln!("compositor: its session's controls refused it: {why}")).ok() }); let mut desktop = Self { control, @@ -220,10 +219,10 @@ impl Desktop { /// **Everything the desktop starts is a job of its own**, made with the /// `jobs` right this row's system capability carries: a program started from /// the taskbar is no helper of the compositor's, and must not share its CPU. -fn launcher(queue: mpsc::Receiver, login: Option, opened: mpsc::Sender>, wake: Waker) { +fn launcher(queue: mpsc::Receiver, login: Option, opened: mpsc::Sender>, wake: Waker) { let jobs = endow::Endowments::get().take::(SYSCAP_LABEL); // A session this thread opened, whose launcher replaces the endowed one. - let mut session: Option = None; + let mut session: Option = None; for ask in queue { let program = match ask { // Answered either way, so the loop asks again after a refusal. @@ -264,7 +263,7 @@ fn launcher(queue: mpsc::Receiver, login: Option, opened: mpsc:: } }; let mut command = Command::new(&program); - command.independent().detached().endow(JOB_LABEL, job.into()); + command.independent().detached().job(job.as_handle().into()); if let Some(launcher) = &session { command.launcher(launcher.as_handle().into()); } diff --git a/system/console/src/main.rs b/system/console/src/main.rs index 76633b8cc2c..1f18e102678 100644 --- a/system/console/src/main.rs +++ b/system/console/src/main.rs @@ -153,8 +153,8 @@ fn main() { // This console *is* the root of its surface tree — there is no compositor // in this image — so it both owns the translator and serves the channel a // child asks for raw keys on. - // Its own port, one per instance, whose connector goes into the namespace - // of the shell it spawns and nowhere else. + // Its own port, one per instance, whose connector it provides to the shell + // it spawns and nowhere else. let (acceptor, connector) = port::create().expect("console: the kernel refused a port of its own"); let mut host = Host::serve(acceptor); diff --git a/system/disks/src/main.rs b/system/disks/src/main.rs index 07d86055b12..0c7aec272e3 100644 --- a/system/disks/src/main.rs +++ b/system/disks/src/main.rs @@ -18,6 +18,7 @@ use toyos_blockring::wire::{Designated, DiskHead, SlotLine}; use toyos_gpt::Guid; use toyos_manifest::authorize::Authorization; +use toyos::AsHandle; mod format; @@ -65,11 +66,11 @@ fn unauthorized(authorization: Authorization) -> i32 { } fn list() -> i32 { - let Some(names) = toyos::endow::namespace() else { + let Ok(service) = diskserver::held(Authorization::Tables.name()) else { return unauthorized(Authorization::Tables); }; - let service = Authorization::Tables.name(); - let listed = match diskserver::list(names, service) { + let service = service.as_handle(); + let listed = match diskserver::list(service) { Ok(listed) => listed, Err(diskserver::Error::NotHeld) => return unauthorized(Authorization::Tables), Err(why) => { @@ -86,7 +87,7 @@ fn list() -> i32 { } let mut code = 0; for disk in disks { - match diskserver::tables::describe(names, service, disk) { + match diskserver::tables::describe(service, disk) { Ok((head, slots)) => print!("{}", describe(disk, &head, &slots)), Err(why) => { println!("disk {disk}: not described: {why:?}"); @@ -98,10 +99,10 @@ fn list() -> i32 { } fn designate(commit: bool) -> i32 { - let Some(names) = toyos::endow::namespace() else { + let Ok(service) = diskserver::held(Authorization::DesignateData.name()) else { return unauthorized(Authorization::DesignateData); }; - match diskserver::tables::designate(names, Authorization::DesignateData.name(), commit) { + match diskserver::tables::designate(service.as_handle(), commit) { Err(diskserver::Error::NotHeld) => unauthorized(Authorization::DesignateData), Ok(done) => { println!("{}", designated(&done)); @@ -126,11 +127,10 @@ impl toyos_crypto::Random for OsRandom { fn format(commit: bool) -> i32 { use toyos_blockdev::{BlockDev, block_on}; let authorization = Authorization::FormatData; - let Some(names) = toyos::endow::namespace() else { + let Ok(service) = diskserver::held(authorization.name()) else { return unauthorized(authorization); }; - let service = authorization.name(); - let listed = match diskserver::list(names, service) { + let listed = match diskserver::list(service.as_handle()) { Ok(listed) => listed, Err(diskserver::Error::NotHeld) => return unauthorized(authorization), Err(why) => return refuse(&format!("disks: DATA was not listed: {why:?}")), @@ -139,16 +139,8 @@ fn format(commit: bool) -> i32 { Ok(target) => target, Err(why) => return refuse(&format!("disks: {why}; nothing was written")), }; - let own = match toyos::namespace::build().keep(names, &[service]).finish() { - Ok(own) => own, - Err(why) => { - return refuse(&format!( - "disks: the formatting capability could not be kept: {why:?}" - )); - } - }; // The block service holds the partition exclusively for this session. - let session = match diskserver::Session::open(own, service, target.unique) { + let session = match diskserver::Session::open(service, target.unique) { Ok(session) => session, Err(why) => { return refuse(&format!( diff --git a/system/diskserver/src/lib.rs b/system/diskserver/src/lib.rs index de343129040..dcd1d301e29 100644 --- a/system/diskserver/src/lib.rs +++ b/system/diskserver/src/lib.rs @@ -7,5 +7,5 @@ pub mod disk; pub mod session; pub mod tables; -pub use session::{list, Answer, Error, Session, Unsent, Waited}; +pub use session::{held, list, Answer, Error, Session, Unsent, Waited}; pub use toyos_blockring::client::{Outcome, Ticket}; diff --git a/system/diskserver/src/session.rs b/system/diskserver/src/session.rs index a2187e53bc0..a0c43990507 100644 --- a/system/diskserver/src/session.rs +++ b/system/diskserver/src/session.rs @@ -13,7 +13,7 @@ //! reads every completion the server posted before it hung up, answers what //! was still on the wire [`Outcome::Refused`], and says the session ended; the //! caller decides when to [`Session::reconnect`], which reaches the service -//! through the same name, opens the same partition, and issues every +//! through the same connector, opens the same partition, and issues every //! acknowledged write no flush had covered again before anything else. //! Nothing here restarts a service: whoever holds its claim does. //! @@ -33,8 +33,10 @@ use std::collections::BTreeMap; use toyos::ipc::{Connection, Frame, FrameRx}; -use toyos::namespace::Namespace; +use toyos::port::Connector; +use toyos::{AsHandle, BorrowedHandle}; use toyos::poller::READABLE; +use toyos_gpt::Guid; use toyos::wake::{Bell, Waker}; use toyos_abi::syscall::SyscallError; use toyos_blockdev::SharedBlock; @@ -136,8 +138,7 @@ enum Pending { } pub struct Session { - names: Namespace, - service: String, + service: Connector, guid: [u8; wire::GUID_BYTES], region: Region, /// The open session's doorbells, `None` once it ended. @@ -153,17 +154,17 @@ pub struct Session { } impl Session { - /// Open the partition whose unique GUID is `guid` through the service - /// `names` calls `service`. - pub fn open(names: Namespace, service: &str, guid: [u8; wire::GUID_BYTES]) -> Result { + /// Open the partition whose unique GUID is `guid` through `service`, a + /// connector to the block service, which the session connects through + /// again after the service ends. + pub fn open(service: Connector, guid: [u8; wire::GUID_BYTES]) -> Result { let region = Region::create().map_err(Error::Kernel)?; let ends = ClientEnds::new(region.words()); - let (bells, opened) = handshake(&names, service, guid, ®ion)?; + let (bells, opened) = handshake(service.as_handle(), guid, ®ion)?; let mut client = Client::new(KEPT_BLOCKS); client.session_started(); Ok(Self { - names, - service: service.to_string(), + service, guid, region, bells: Some(bells), @@ -273,10 +274,10 @@ impl Session { let now = toyos_abi::clock::nanos_since_boot(); match self.exchange(now, &mut waited.answers) { Next::End(Ending::Violation(violation)) => { - println!("{}: the session's server broke the protocol ({violation:?})", self.service); + println!("diskserver: the session on {}: its server broke the protocol ({violation:?})", Guid(self.guid)); } Next::End(Ending::Silence) => { - println!("{}: the session's server answered nothing in {} s", self.service, SILENCE_BOUND_NS / 1_000_000_000); + println!("diskserver: the session on {}: its server answered nothing in {} s", Guid(self.guid), SILENCE_BOUND_NS / 1_000_000_000); } Next::Wait(patience) if block => { let bells = self.bells.as_ref().expect("diskserver: the session is open"); @@ -328,19 +329,19 @@ impl Session { waited.ended = true; } - /// Open the same partition through the same name over a fresh region, + /// Open the same partition through the same connector over a fresh region, /// after the session ended; at the next [`Self::wait`] the writes the /// client holds go out again, first. Refused [`Refusal::Held`] while the /// server still holds a request of the session that ended. /// /// Blocks until the service answers, within [`wire::ANSWER_BOUND_NS`]: a - /// name whose server is being restarted queues the connection until the - /// new one accepts it. + /// connector whose server is being restarted queues the connection until + /// the new one accepts it. pub fn reconnect(&mut self) -> Result<(), Error> { assert!(self.bells.is_none(), "diskserver: reconnect while a session is open"); let region = Region::create().map_err(Error::Kernel)?; let ends = ClientEnds::new(region.words()); - let (bells, opened) = handshake(&self.names, &self.service, self.guid, ®ion)?; + let (bells, opened) = handshake(self.service.as_handle(), self.guid, ®ion)?; if opened != self.opened { return Err(Error::Protocol); } @@ -355,9 +356,18 @@ fn transfer_blocks(blocks: usize) -> u32 { blocks as u32 } -pub(crate) fn connect(names: &Namespace, service: &str) -> Result { - names.open(service).map_err(|e| match e { - SyscallError::NotFound => Error::NotHeld, +/// A connector of the caller's own to the block service this process holds +/// as the service `name`: [`Error::NotHeld`] for a name it was not given. +pub fn held(name: &str) -> Result { + let copy = toyos::endow::connector(name).ok_or(Error::NotHeld)?.try_clone_to_owned().map_err(Error::Kernel)?; + // SAFETY: a duplicate of a connector this process was endowed or adopted + // as a service, which is its parent's word or its own. + Ok(unsafe { ::from_handle(copy) }) +} + +pub(crate) fn connect(service: BorrowedHandle<'_>) -> Result { + toyos::port::connect(service).map_err(|e| match e { + SyscallError::PermissionDenied => Error::NotHeld, e => Error::Kernel(e), }) } @@ -385,13 +395,8 @@ struct Bells { /// Connect, send the region with an open, and wait for the answer, which /// carries the session's doorbells: the session is theirs from here, and the /// connection goes. -fn handshake( - names: &Namespace, - service: &str, - guid: [u8; wire::GUID_BYTES], - region: &Region, -) -> Result<(Bells, Opened), Error> { - let conn = connect(names, service)?; +fn handshake(service: BorrowedHandle<'_>, guid: [u8; wire::GUID_BYTES], region: &Region) -> Result<(Bells, Opened), Error> { + let conn = connect(service)?; conn.send_bytes_with(wire::MSG_OPEN, &guid, [region.share().map_err(Error::Kernel)?]).map_err(|_| Error::Ended)?; let mut rx = Box::new(FrameRx::<{ toyos::ipc::MAX_FRAME_LEN as usize }>::new()); let frame = answer(&conn, &mut rx)?; @@ -408,10 +413,11 @@ fn handshake( } } -/// Every partition the service `names` calls `service` serves: what a client -/// that finds its partition by type asks before it opens one. -pub fn list(names: &Namespace, service: &str) -> Result, Error> { - let conn = connect(names, service)?; +/// Every partition the block service serves through `service`, a connector +/// to it: what a client that finds its partition by type asks before it +/// opens one. +pub fn list(service: BorrowedHandle<'_>) -> Result, Error> { + let conn = connect(service)?; conn.signal(wire::MSG_LIST).map_err(|_| Error::Ended)?; let mut rx = Box::new(FrameRx::<{ toyos::ipc::MAX_FRAME_LEN as usize }>::new()); let frame = answer(&conn, &mut rx)?; diff --git a/system/diskserver/src/tables.rs b/system/diskserver/src/tables.rs index a199b3e96e0..b4121d57bcd 100644 --- a/system/diskserver/src/tables.rs +++ b/system/diskserver/src/tables.rs @@ -2,7 +2,7 @@ //! the block service through a tables or a designation badge //! (`toyos_blockring::wire::Badge`). -use toyos::namespace::Namespace; +use toyos::BorrowedHandle; use toyos_blockring::wire::{self, Designated, DiskHead, Refusal, SlotLine}; use crate::session::{answer, connect}; @@ -24,8 +24,8 @@ fn refused(payload: &[u8]) -> Error { /// What disk `disk` (the service's number, from a listing) holds: its head /// and every used entry of its table in force. -pub fn describe(names: &Namespace, service: &str, disk: u32) -> Result<(DiskHead, Vec), Error> { - let conn = connect(names, service)?; +pub fn describe(service: BorrowedHandle<'_>, disk: u32) -> Result<(DiskHead, Vec), Error> { + let conn = connect(service)?; conn.send_bytes(wire::MSG_DESCRIBE, &disk.to_le_bytes()).map_err(|_| Error::Ended)?; let head = match frame(&conn)? { (wire::MSG_DISK, payload) => DiskHead::decode(&payload).ok_or(Error::Protocol)?, @@ -44,8 +44,8 @@ pub fn describe(names: &Namespace, service: &str, disk: u32) -> Result<(DiskHead /// Carry out the designation the connector `service` names was minted for: /// write the stamp when `commit`, judge only otherwise. -pub fn designate(names: &Namespace, service: &str, commit: bool) -> Result { - let conn = connect(names, service)?; +pub fn designate(service: BorrowedHandle<'_>, commit: bool) -> Result { + let conn = connect(service)?; conn.send_bytes(wire::MSG_DESIGNATE, &[u8::from(commit)]).map_err(|_| Error::Ended)?; match frame(&conn)? { (wire::MSG_DESIGNATED, payload) => Designated::decode(&payload).ok_or(Error::Protocol), diff --git a/system/fileserver/src/main.rs b/system/fileserver/src/main.rs index 90a14e5cfda..f0d42728fea 100644 --- a/system/fileserver/src/main.rs +++ b/system/fileserver/src/main.rs @@ -3,7 +3,7 @@ //! //! **What it holds**: the acceptor of its role's one port, endowed by the //! supervisor under `serve:fs:`; for its volume, SYSTEM's ROOT image -//! (`ROOT_IMAGE_LABEL`), MEMORY's anonymous objects, or `block` in its namespace, a +//! (`ROOT_IMAGE_LABEL`), MEMORY's anonymous objects, or `block` among its services, a //! connector to the block service minted for its role's partition alone — //! every DATA partition, or the one the loader named, read-only for the boot //! volume; and nothing else of the machine. Two DATA partitions are refused by @@ -71,12 +71,12 @@ use fileserver::writeback::WriteBack; use toyos::endow::{self, Endowments}; use toyos::fs::*; use toyos::ipc::{self, Connection, RxStep}; -use toyos::namespace::Namespace; use toyos::poller::{Poller, READABLE}; -use toyos::port::{self, Acceptor}; +use toyos::port::{self, Acceptor, Connector}; use toyos::shm::{Bytes, SharedMemory}; use toyos::Pipe; -use toyos_abi::syscall::{SyscallError, MAX_BADGE, PIPE_CAPACITY, SERVE_PREFIX}; +use toyos_abi::syscall::{SyscallError, MAX_BADGE, PIPE_CAPACITY}; +use toyos::endow::SERVE_PREFIX; /// What one turn of a stream appends at most. const STREAM_READ: usize = 64 * 1024; @@ -86,9 +86,6 @@ const STREAM_READ: usize = 64 * 1024; /// hold the loop. const STREAM_PRESENT: usize = PIPE_CAPACITY / STREAM_READ; -/// The name the private export port has in this server's own namespace. -const EXPORT_PORT: &str = "export"; - // One wait watches the acceptor, every connection and every stream at once. const _: () = assert!(1 + MAX_SERVED + HANDSHAKES.slots + MAX_STREAMS <= Poller::MAX_HANDLES as usize); @@ -186,27 +183,23 @@ struct Stream { armed: bool, } -/// The private port exports are made on: its acceptor, and the namespace -/// holding this server's one connector to it. +/// The private port exports are made on: its acceptor, and this server's +/// one connector to it. struct Exports { acceptor: Acceptor, - names: Namespace, + connector: Connector, } impl Exports { fn new() -> Self { let (acceptor, connector) = port::create().unwrap_or_else(|e| panic!("fileserver: no export port ({e:?})")); - let names = toyos::namespace::build() - .add(EXPORT_PORT, &connector) - .finish() - .unwrap_or_else(|e| panic!("fileserver: no namespace for the export port ({e:?})")); - Self { acceptor, names } + Self { acceptor, connector } } /// A connection to this server: the end to hand away and this server's. /// The exported end keeps `DUP` for spawning from a borrowed handle. fn connect(&self) -> Result<(Connection, Connection), Error> { - let theirs = self.names.open(EXPORT_PORT).map_err(|e| Error::kernel("an export's connection", e))?; + let theirs = self.connector.connect().map_err(|e| Error::kernel("an export's connection", e))?; let ours = self.acceptor.accept().unwrap_or_else(|e| panic!("fileserver: the export port refused an accept ({e:?})")); Ok((theirs, ours)) } @@ -296,9 +289,8 @@ fn pager(role: Role, anonymous: bool) -> (Option, Option Option { - let names = endow::namespace()?; - let own = toyos::namespace::build().keep(names, &[toyos_blockring::PORT]).finish().ok()?; - match diskserver::Session::open(own, toyos_blockring::PORT, guid) { + let own = diskserver::held(toyos_blockring::PORT).ok()?; + match diskserver::Session::open(own, guid) { Ok(session) => Some(Served::new(session)), Err(why) => { println!("fileserver: diskserver would not open the partition: {why:?}"); @@ -310,13 +302,11 @@ fn served(guid: [u8; 16]) -> Option { /// The TOYOS-DATA partitions diskserver serves, or why it would not say: a disk /// that failed is not a machine without one, and is never answered with memory. fn data_partitions() -> Result, String> { - let Some(names) = endow::namespace() else { return Ok(Vec::new()) }; - match diskserver::list(names, toyos_blockring::PORT) { + let Some(block) = endow::connector(toyos_blockring::PORT) else { return Ok(Vec::new()) }; + match diskserver::list(block) { Ok(listed) => { Ok(listed.into_iter().filter(|l| l.kind == toyos_gpt::Guid::TOYOS_DATA.0).map(|l| l.unique).collect()) } - // No block service in this image: the namespace has no `block`. - Err(diskserver::Error::NotHeld) => Ok(Vec::new()), Err(why) => Err(format!("the block service would not list its partitions ({why:?})")), } } @@ -361,7 +351,7 @@ fn open_volume(role: Role, guid: Option<&str>, roots: &[&str]) -> (Box (RootVolume, &'static toyos::shm::Immutable) { - let label = toyos_abi::syscall::ROOT_IMAGE_LABEL; + let label = toyos_manifest::ROOT_IMAGE_LABEL; let handle = Endowments::get() .take::(label) .unwrap_or_else(|| panic!("fileserver: the system role was started without `{label}`")); diff --git a/system/fileserver/src/root.rs b/system/fileserver/src/root.rs index eb11f851b64..ed1a9a1a411 100644 --- a/system/fileserver/src/root.rs +++ b/system/fileserver/src/root.rs @@ -1,6 +1,6 @@ //! The SYSTEM role's volume: ROOT, the bcachefs volume of the slot's signed //! image, served read-only out of the memory the kernel handed the supervisor -//! (`toyos_abi::syscall::ROOT_IMAGE_LABEL`). No disk and no cache: the bytes +//! (`toyos_manifest::ROOT_IMAGE_LABEL`). No disk and no cache: the bytes //! are already in memory, and nobody writes them. //! //! **ROOT crossed the loader's hash and nothing else.** The engine's reader diff --git a/system/grants/src/main.rs b/system/grants/src/main.rs index 4699632e8a9..06e18c65799 100644 --- a/system/grants/src/main.rs +++ b/system/grants/src/main.rs @@ -17,7 +17,7 @@ use std::io::Write; use toyos::endow::Endowments; -use toyos::namespace::Namespace; +use toyos::port::Connector; use toyos_manifest::grants::{self, Access, Folder, Request}; const USAGE: &str = "usage: grants list | add [read-only] | revoke "; @@ -53,10 +53,10 @@ fn request(args: &[String]) -> Result { /// The supervisor's answer: whether it was done, and its text. fn ask(request: &Request) -> (bool, String) { - let Some(held) = Endowments::get().take::(grants::PORT) else { + let Some(held) = Endowments::get().take::(grants::PORT) else { return (false, "this program holds no grants connector".to_string()); }; - let supervisor = match held.open(grants::PORT) { + let supervisor = match held.connect() { Ok(conn) => conn, Err(e) => return (false, format!("the supervisor's grants port did not answer: {e:?}")), }; diff --git a/system/hda/src/main.rs b/system/hda/src/main.rs index 00d16ddc5d2..0a06e24bc89 100644 --- a/system/hda/src/main.rs +++ b/system/hda/src/main.rs @@ -474,7 +474,7 @@ fn soundserver_gone(at: &str, why: impl core::fmt::Debug) -> ! { /// a period's end, and what it reports soundserver mixes the next one by. fn rt() { let rt: SysCap = Endowments::get() - .take(toyos_abi::syscall::SYSCAP_LABEL) + .take(toyos::endow::SYSCAP_LABEL) .expect("the manifest declares this program `syscap = [\"rt\"]`"); rt.enter_rt().expect("an RT capability refused the band it names"); } diff --git a/system/logout/src/main.rs b/system/logout/src/main.rs index 5224809d441..1e7b93e84cd 100644 --- a/system/logout/src/main.rs +++ b/system/logout/src/main.rs @@ -15,7 +15,7 @@ use std::io::Write; use toyos::endow::Endowments; -use toyos::namespace::Namespace; +use toyos::port::Connector; use toyos_session::{self as wire, SignOut, SignOutSession}; const USAGE: &str = "usage: logout [ [--force]]"; @@ -50,10 +50,10 @@ fn request(args: &[String]) -> Result<(u32, Vec), &'static str> { /// The supervisor's answer: whether it was done, and its text. fn ask(msg: u32, payload: &[u8]) -> (bool, String) { - let Some(held) = Endowments::get().take::(wire::ADMIN_PORT) else { + let Some(held) = Endowments::get().take::(wire::ADMIN_PORT) else { return (false, "logout: this process holds no `session-admin` connector; run it with `launch logout`".into()); }; - let supervisor = match held.open(wire::ADMIN_PORT) { + let supervisor = match held.connect() { Ok(conn) => conn, Err(e) => return (false, format!("logout: the supervisor's port refused: {e:?}")), }; diff --git a/system/netstack/src/serve.rs b/system/netstack/src/serve.rs index 4b8fe9aa34b..400d7fcf16c 100644 --- a/system/netstack/src/serve.rs +++ b/system/netstack/src/serve.rs @@ -127,33 +127,25 @@ struct Stream { /// always has another cannot keep the rest from being served. const FRAMES_A_WAKE: usize = 64; -/// The name the private port holders are made on has in netstack's own -/// namespace. -const EXPORT_PORT: &str = "export"; - /// The private port every holder netstack makes is a connection of, but the /// first of a connect, a listen and a bind, which is the request's own: its -/// acceptor, and the namespace holding netstack's one connector to it. It +/// acceptor, and netstack's one connector to it. It /// dies with netstack, so nothing pending reaches a restarted one. struct Exports { acceptor: Acceptor, - names: toyos::namespace::Namespace, + connector: toyos::port::Connector, } impl Exports { fn new() -> Self { let (acceptor, connector) = toyos::port::create().unwrap_or_else(|e| panic!("netstack: no export port ({e:?})")); - let names = toyos::namespace::build() - .add(EXPORT_PORT, &connector) - .finish() - .unwrap_or_else(|e| panic!("netstack: no namespace for the export port ({e:?})")); - Self { acceptor, names } + Self { acceptor, connector } } /// A connection to netstack: the end to hand away, which keeps every right /// a connection is made with, `TRANSFER` among them, and netstack's. fn connect(&self) -> Result<(Connection, Connection), SyscallError> { - let theirs = self.names.open(EXPORT_PORT)?; + let theirs = self.connector.connect()?; let ours = self.acceptor.accept().unwrap_or_else(|e| panic!("netstack: the export port refused an accept ({e:?})")); Ok((theirs, ours)) } diff --git a/system/nvme/src/main.rs b/system/nvme/src/main.rs index da5129113a0..43cb35cd54c 100644 --- a/system/nvme/src/main.rs +++ b/system/nvme/src/main.rs @@ -19,7 +19,8 @@ use std::time::{Duration, Instant}; use toyos::endow::Endowments; use toyos::poller::Poller; use toyos::PciDev; -use toyos_abi::syscall::{SyscallError, DEV_PREFIX}; +use toyos_abi::syscall::SyscallError; +use toyos::endow::DEV_PREFIX; use toyos_blockring::disk::Settled; use toyos_blockring::BLOCK_BYTES; use toyos_blockserve::link::{Handed, Link}; diff --git a/system/pkg/src/main.rs b/system/pkg/src/main.rs index a1d2634d4ab..28918a71411 100644 --- a/system/pkg/src/main.rs +++ b/system/pkg/src/main.rs @@ -105,10 +105,8 @@ fn install(file: &Path, assume_yes: bool) -> Result<(), String> { /// Refused where it is read-only, which it is to every row but the /// installer's. fn writable_apps() -> Result { - let names = toyos::endow::namespace() - .ok_or_else(|| String::from("pkg: this process holds no directory capabilities"))?; let name = format!("{}{}", toyos::fs::CAPABILITY_PREFIX, package::DIR); - let apps = toyos::fs::Dir::connect(names, &name).map_err(|e| format!("pkg: cannot reach {}: {e:?}", package::DIR))?; + let apps = toyos::fs::Dir::named(&name).map_err(|e| format!("pkg: cannot reach {}: {e:?}", package::DIR))?; match apps.writable() { true => Ok(apps), false => Err(format!("pkg: {} is read-only to this process; run it with `launch pkg`", package::DIR)), diff --git a/system/session/src/main.rs b/system/session/src/main.rs index e4c444ea245..81fd054d997 100644 --- a/system/session/src/main.rs +++ b/system/session/src/main.rs @@ -38,9 +38,9 @@ fn main() { let endowed = Endowments::get(); // Connected here, so the link is this job's and not the supervisor's. let link: Connection = endowed - .take::(wire::LINK_LABEL) + .take::(wire::LINK_LABEL) .expect("session: started without its link to the supervisor") - .open(wire::LINK) + .connect() .expect("session: the supervisor's link refused it"); let launcher: Acceptor = endowed.take(wire::LAUNCHER_ACCEPTOR_LABEL).expect("session: started without its launcher's acceptor"); diff --git a/system/shell/Cargo.toml b/system/shell/Cargo.toml index 7f4c5284521..f7ca16aa497 100644 --- a/system/shell/Cargo.toml +++ b/system/shell/Cargo.toml @@ -7,7 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] toyos = { path = "../../sdk/toyos", features = ["std"] } -# The quit a `-c` shell asks of a child spawned past its own interrupt. +# A quit's reasons, and the quit a `-c` shell asks of a child that landed past +# its interrupt. toyos-abi = { path = "../../sdk/abi" } [package.metadata.toyos.host] diff --git a/system/shell/copies/Cargo.toml b/system/shell/copies/Cargo.toml new file mode 100644 index 00000000000..77d74aeb61c --- /dev/null +++ b/system/shell/copies/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "shell-copies" +description = "A host test of the shell's quit handling against the kernel's quit and spawn, in every order: its module that names nothing of ToyOS's SDK, compiled as it is." +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false + +# A package of its own because the shell links std's ToyOS extensions, which a +# host build has not: this compiles only its quit handling. + +[lib] +doctest = false + +[dev-dependencies] +toyos-abi = { path = "../../../sdk/abi" } diff --git a/system/shell/copies/src/lib.rs b/system/shell/copies/src/lib.rs new file mode 100644 index 00000000000..0aa0c2793ca --- /dev/null +++ b/system/shell/copies/src/lib.rs @@ -0,0 +1,10 @@ +//! The shell's quit handling on the host: a `-c` shell's interrupt against +//! the landing of the child it starts, in every order the kernel and the +//! shell's two threads can take. + +#[cfg(test)] +#[path = "../../src/quit.rs"] +pub mod quit; + +#[cfg(test)] +mod quitting; diff --git a/system/shell/copies/src/quitting.rs b/system/shell/copies/src/quitting.rs new file mode 100644 index 00000000000..b3afc17b0fd --- /dev/null +++ b/system/shell/copies/src/quitting.rs @@ -0,0 +1,177 @@ +//! One interrupt of a `-c` shell and one child it starts, in every order. +//! +//! The kernel's side as `toyos_proclife::quit` decides it: a quit ends a +//! process that never listened, and otherwise puts the reason in its notice +//! and asks the children there; a child that lands starts holding what its +//! parent's notice holds unread. The shell's side is [`Asked`] itself, its +//! notice read only through the methods that take `read`. The child never +//! listens, as `cat`, or listens at some step and reads each reason at once. + +use toyos_abi::quit::Reason; + +use crate::quit::Asked; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Step { + /// The interrupt reaches the shell. + Quit, + /// The child the shell started lands. + Land, + /// The shell's main thread looks at the child ([`Asked::landed`]). + Look, + /// The shell's listener thread takes what the notice holds. + Heard, + /// The child listens, reading what its notice holds. + Listens, +} + +#[derive(Default)] +struct Child { + landed: bool, + listened: bool, + /// A reason it inherited and has not read. + holds: Option, + ended: bool, + /// Reasons it read. + read: u8, +} + +struct World { + shell: Asked, + /// The shell's notice: the shell listened at its start. + notice: Option, + child: Child, +} + +impl World { + fn new() -> Self { + let mut shell = Asked::new(); + shell.one_command(); + Self { shell, notice: None, child: Child::default() } + } + + /// A quit reaching the child, which ends one that has not listened. + fn ask_child(&mut self) { + let child = &mut self.child; + match (child.landed && !child.ended, child.listened) { + (false, _) => {} + (true, false) => child.ended = true, + (true, true) => child.read += 1, + } + } + + fn step(&mut self, step: Step) { + let notice = &mut self.notice; + match step { + Step::Quit => { + assert_eq!(notice.replace(Reason::Interrupt), None, "one interrupt"); + self.ask_child(); + } + Step::Land => { + self.child.landed = true; + self.child.holds = *notice; + } + Step::Look => { + if self.shell.landed(|| notice.take()) { + self.ask_child(); + } + } + Step::Heard => assert_eq!(self.shell.take(|| notice.take()), None, "a -c shell waits from its start"), + Step::Listens => { + self.child.listened = true; + self.child.read += u8::from(self.child.holds.take().is_some()); + } + } + } +} + +/// Every order of `steps` in which the child lands before the shell looks at +/// it and before it listens. +fn orders(steps: &[Step]) -> Vec> { + fn permute(rest: &mut Vec, order: &mut Vec, out: &mut Vec>) { + if rest.is_empty() { + out.push(order.clone()); + } + for i in 0..rest.len() { + let step = rest.remove(i); + order.push(step); + permute(rest, order, out); + order.pop(); + rest.insert(i, step); + } + } + let mut out = Vec::new(); + permute(&mut steps.to_vec(), &mut Vec::new(), &mut out); + out.retain(|order| { + let land = at(order, Step::Land); + [Step::Look, Step::Listens].iter().all(|step| !order.contains(step) || at(order, *step) > land) + }); + out +} + +fn at(order: &[Step], step: Step) -> usize { + order.iter().position(|s| *s == step).expect("a step of the order") +} + +/// The world after `order` and the listener's last take, which a reason in +/// the notice always wakes. +fn run(order: &[Step]) -> World { + let mut world = World::new(); + for step in order { + world.step(*step); + } + world.step(Step::Heard); + world +} + +/// The race this shell had: an interrupt that came while it started its +/// child, which the kernel's walk did not reach, left the child running. +#[test] +fn a_child_that_never_listens_ends_with_the_interrupt_in_every_order() { + let orders = orders(&[Step::Quit, Step::Land, Step::Look, Step::Heard]); + assert_eq!(orders.len(), 12); + for order in orders { + assert!(run(&order).child.ended, "{order:?}: the child outlived the interrupt"); + } +} + +/// A child that listens reads the interrupt or ends with it, and reads it +/// twice only where it listened between its landing and the shell's look, +/// which nothing the shell does tells from a child the walk asked. +#[test] +fn a_child_that_listens_reads_the_interrupt_once_but_where_the_shell_cannot_tell() { + let orders = orders(&[Step::Quit, Step::Land, Step::Look, Step::Heard, Step::Listens]); + assert_eq!(orders.len(), 40); + for order in orders { + let child = run(&order).child; + assert!(child.ended || child.read >= 1, "{order:?}: the child neither read the interrupt nor ended"); + let unseen = at(&order, Step::Listens) < at(&order, Step::Look); + assert!(child.read <= 1 || (child.read == 2 && unseen), "{order:?}: the child read {} interrupts", child.read); + } +} + +/// At its prompt an interactive shell takes an interrupt as nothing and ends +/// at once on a hang-up; while it waits, a hang-up ends it once the line has, +/// and an interrupt ends a `-c` shell alone. +#[test] +fn a_reason_ends_the_shell_by_whether_it_waits_and_runs_one_command() { + let reasons = |reasons: &[Reason]| { + let mut left = reasons.to_vec(); + move || (!left.is_empty()).then(|| left.remove(0)) + }; + let mut interactive = Asked::new(); + assert_eq!(interactive.take(reasons(&[Reason::Interrupt])), None); + assert_eq!(interactive.take(reasons(&[Reason::HangUp])), Some(129)); + interactive.waiting(); + assert_eq!(interactive.take(reasons(&[Reason::Interrupt, Reason::Terminate])), None); + assert_eq!(interactive.waited(0, reasons(&[])), Some(143)); + let mut prompt = Asked::new(); + prompt.waiting(); + assert_eq!(prompt.waited(7, reasons(&[Reason::Interrupt])), None, "an interactive shell runs on past an interrupt"); + assert!(!prompt.landed(reasons(&[Reason::Interrupt])), "an interactive shell's line is quit child by child"); + + let mut one = Asked::new(); + one.one_command(); + assert_eq!(one.waited(3, reasons(&[])), None); + assert_eq!(one.waited(3, reasons(&[Reason::Interrupt])), Some(3), "a -c shell ends with its line's status"); +} diff --git a/system/shell/src/main.rs b/system/shell/src/main.rs index d110cb662d9..342851bcc6a 100644 --- a/system/shell/src/main.rs +++ b/system/shell/src/main.rs @@ -4,12 +4,15 @@ use std::io::{self, Write}; use std::path::Path; use std::os::toyos::process::{CommandExt, ExitStatusExt}; use std::process::{Child, Command, ExitStatus, Stdio}; -use std::sync::{Mutex, OnceLock}; +use std::sync::Mutex; -use toyos::port::Connector; use toyos::quit::Reason; use toyos::surface::Foreground; +mod quit; + +use quit::Asked; + /// This program's own folder, whose `State` keeps the history: `$HOME` itself /// once the supervisor makes that folder the shell's `HOME`, and `$HOME/Apps/shell` /// while `HOME` is the session's. Never a dotfile in the user's home. @@ -18,78 +21,57 @@ const HISTORY_MAX: usize = 200; static mut LAST_STATUS: i32 = 0; -/// What a quit asked of this shell. **A shell relays nothing**: a quit of it +/// What a quit asked this shell. **A shell relays nothing**: a quit of it /// reaches what it runs as well, so an interrupt it reads is only noted, and a -/// hang-up or a terminate ends it once the line it waits for has ended. -struct Asked { - /// Whether it waits for a line now. - waiting: bool, - /// An interrupt read while it waited. - interrupted: bool, - /// A hang-up or a terminate read while it waited. - ended_by: Option, -} - -static ASKED: Mutex = Mutex::new(Asked { waiting: false, interrupted: false, ended_by: None }); +/// hang-up or a terminate ends it once the line it waits for has ended. Its +/// notice is read only while this is held ([`reason`]). +static ASKED: Mutex = Mutex::new(Asked::new()); /// The line it waits for, handed to the surface above it for Ctrl+C. static FOREGROUND: Mutex> = Mutex::new(None); -/// Whether it runs one `-c` command and ends, which an interrupt of its line -/// ends once the line has. -static mut ONE_COMMAND: bool = false; - -/// The exit code a quit's reason ends it with: 128 plus the signal's number, -/// as a shell reads one. -fn code_of(reason: Reason) -> i32 { - 128 + match reason { - Reason::Interrupt => 2, - Reason::HangUp => 1, - Reason::Terminate => 15, - } +/// The next reason this shell's notice holds, or none: read under [`ASKED`] +/// alone. +fn reason() -> Option { + toyos::quit::notice().try_read().expect("shell: its own notice refused a read") } /// Listen to this shell's notice, before anything is spawned, on a thread of -/// its own. +/// its own, which a reason wakes and which takes it under [`ASKED`]. fn listen() { - let Some(notice) = toyos::quit::notice() else { return }; + let notice = toyos::quit::notice(); if notice.listen().is_err() { return; } - std::thread::spawn(move || { - while let Ok(reason) = notice.read() { - let mut asked = ASKED.lock().unwrap(); - match reason { - Reason::Interrupt => asked.interrupted |= asked.waiting, - _ if !asked.waiting => std::process::exit(code_of(reason)), - _ => asked.ended_by = Some(reason), - } + std::thread::spawn(move || loop { + let woken = toyos::poller::wait_one(notice, toyos::poller::READABLE, u64::MAX); + woken.expect("shell: its own notice refused a wait"); + if let Some(code) = ASKED.lock().unwrap().take(reason) { + std::process::exit(code); } }); } +/// `child` has landed: a `-c` shell asks it to quit when an interrupt reached +/// the shell first, which a child that landed after it never heard. +fn landed(child: &Child) { + if ASKED.lock().unwrap().landed(reason) { + // `Ok` for one already ended; a spawn's handle carries `MANAGE`. + let quit = toyos_abi::syscall::process_quit(toyos::AsHandle::as_handle(child).raw(), Reason::Interrupt); + quit.expect("shell: a child's handle refused the quit its spawn gave it"); + } +} + /// It waits for `line` from now: an interactive shell hands it to the /// surface. A `-c` shell is itself a process of the line that started it, so /// a quit of that line reaches what it runs already, and it hands nothing. -/// -/// A `-c` shell interrupted before its line was whole ends that line itself: -/// a child spawned after the quit read its tree was not asked. fn waiting_for(line: &[&Child]) { - if unsafe { ONE_COMMAND } { - if ASKED.lock().unwrap().interrupted { - for child in line { - // `Ok` for one already ended; a spawn's handle carries `MANAGE`. - let quit = toyos_abi::syscall::process_quit(toyos::AsHandle::as_handle(child).raw(), Reason::Interrupt); - quit.expect("shell: a child's handle refused the quit its spawn gave it"); - } - } + if ASKED.lock().unwrap().is_one_command() { return; } let handles: Vec> = line.iter().map(toyos::AsHandle::as_handle).collect(); FOREGROUND.lock().unwrap().get_or_insert_with(Foreground::default).set(&handles); - let mut asked = ASKED.lock().unwrap(); - asked.waiting = true; - asked.interrupted = false; + ASKED.lock().unwrap().waiting(); } /// The line it waited for has ended: the surface is told, and a quit read @@ -99,13 +81,8 @@ fn waited(status: i32) { if let Some(foreground) = FOREGROUND.lock().unwrap().as_mut() { foreground.clear(); } - let mut asked = ASKED.lock().unwrap(); - asked.waiting = unsafe { ONE_COMMAND }; - if let Some(reason) = asked.ended_by { - std::process::exit(code_of(reason)); - } - if core::mem::take(&mut asked.interrupted) && unsafe { ONE_COMMAND } { - std::process::exit(status); + if let Some(code) = ASKED.lock().unwrap().waited(status, reason) { + std::process::exit(code); } } @@ -113,14 +90,13 @@ fn main() { if env::var_os("PATH").is_none() { env::set_var("PATH", "/system/bin"); } - listen(); - let args: Vec = env::args().collect(); - if args.len() >= 3 && args[1] == "-c" { - unsafe { ONE_COMMAND = true }; - // Its command is its line from the start: an interrupt is noted - // whenever it comes, and ends the shell once what runs has. - ASKED.lock().unwrap().waiting = true; + let one_command = args.len() >= 3 && args[1] == "-c"; + if one_command { + ASKED.lock().unwrap().one_command(); + } + listen(); + if one_command { let input = args[2..].join(" "); execute_line(&input); std::process::exit(unsafe { LAST_STATUS }); @@ -680,6 +656,7 @@ fn execute_pipeline(pipeline: &[SimpleCommand]) -> bool { match command.spawn() { Ok(mut child) => { + landed(&child); // Write heredoc data to first command's stdin if is_first { if let Some(ref data) = cmd.heredoc { @@ -776,6 +753,7 @@ fn execute_simple(cmd: &SimpleCommand, piped_stdin: Option { + landed(&child); waiting_for(&[&child]); if let Some(mut stdin) = child.stdin.take() { let _ = stdin.write_all(data.as_bytes()); @@ -804,6 +782,7 @@ fn execute_simple(cmd: &SimpleCommand, piped_stdin: Option { @@ -819,24 +798,6 @@ fn execute_simple(cmd: &SimpleCommand, piped_stdin: Option &'static [(&'static str, Connector)] { - static PROVIDED: OnceLock> = OnceLock::new(); - PROVIDED.get_or_init(|| { - let mut slots: [Option<(&'static str, Connector)>; toyos::launch::MAX_LAUNCH_EXTRAS] = - [const { None }; toyos::launch::MAX_LAUNCH_EXTRAS]; - let n = toyos::endow::provided(&mut slots); - slots.into_iter().take(n).flatten().collect() - }) -} - /// `detach [args...]`: launch it under the launcher's owner, so it /// outlives this shell, and do not wait for it. Nothing this shell was /// provided goes with it: that belongs to the session it outlives. @@ -870,7 +831,7 @@ fn detach(cmd: &SimpleCommand) -> bool { } /// The program a simple command starts: a helper, which inherits this shell's -/// namespace and so the surface it was provided; or after `launch`, an +/// services and so the surface it was provided; or after `launch`, an /// independent launch of the program's own row, provided what this shell was. /// The status, said, for a `launch` this shell cannot make, as `detach`'s. fn build_command(cmd: &SimpleCommand) -> Result { @@ -890,9 +851,11 @@ fn build_command(cmd: &SimpleCommand) -> Result { command.args(&args[1..]); if launched { command.independent(); - for (name, connector) in provided() { - // A duplicate per child: this shell keeps its own for the next one. - if let Ok(handed) = connector.duplicate() { + // What whoever started this shell provided, forwarded without knowing + // what it is: the terminal's `surface` reaches `locale` this way. A + // duplicate per child, so this shell keeps its own for the next one. + for (name, connector) in toyos::endow::provided() { + if let Ok(handed) = connector.try_clone_to_owned() { command.provide(name, handed.into()); } } diff --git a/system/shell/src/quit.rs b/system/shell/src/quit.rs new file mode 100644 index 00000000000..5b69b300f56 --- /dev/null +++ b/system/shell/src/quit.rs @@ -0,0 +1,109 @@ +//! What a quit asked this shell, and which child it asks to quit in turn. +//! +//! **A quit reaches what the shell runs, but not a child that lands after +//! it**: the kernel asks or ends each child there when the quit arrives, and +//! one that lands later starts holding the reason unread if this shell had not +//! read it, or none if it had (`toyos_proclife::quit`). An interactive shell's +//! line is quit child by child, so only a `-c` shell, which is quit itself, +//! asks a child it started ([`Asked::landed`]). +//! +//! **Only the holder of [`Asked`] reads the notice**, through the `read` each +//! method takes: a reason is never read and not yet recorded where another +//! thread looks. + +use toyos_abi::quit::Reason; + +/// What a quit asked this shell. +#[derive(Default)] +pub struct Asked { + /// It runs one `-c` command and ends, which an interrupt of its line ends + /// once the line has. + one_command: bool, + /// It waits for a line now: a `-c` shell from its start. + waiting: bool, + /// An interrupt read while it waited. + interrupted: bool, + /// A hang-up or a terminate read while it waited. + ended_by: Option, +} + +impl Asked { + /// [`Default`]'s, for a static. + pub const fn new() -> Self { + Self { one_command: false, waiting: false, interrupted: false, ended_by: None } + } + + /// Make this a `-c` shell, whose command is its line from its start: done + /// before it listens, so no interrupt finds it waiting for nothing. + pub fn one_command(&mut self) { + self.one_command = true; + self.waiting = true; + } + + pub fn is_one_command(&self) -> bool { + self.one_command + } + + /// Record every reason `read` answers, until it answers none: an interrupt + /// is noted while the shell waits and is nothing at its prompt; a hang-up + /// or a terminate is noted while it waits, and otherwise ends it now, with + /// the code this answers. + pub fn take(&mut self, mut read: impl FnMut() -> Option) -> Option { + while let Some(reason) = read() { + match reason { + Reason::Interrupt => self.interrupted |= self.waiting, + _ if !self.waiting => return Some(code_of(reason)), + _ => self.ended_by = Some(reason), + } + } + None + } + + /// A child this shell started has landed: whether to ask it to quit, + /// having taken what the notice holds first. A `-c` shell asks it when an + /// interrupt reached the shell before this look, which may have come + /// before the child landed and so not reached it. + /// + /// No order of this shell's own steps tells such a child from one the + /// quit's walk already asked, so a child that listens between its landing + /// and this look is asked twice by one interrupt. + pub fn landed(&mut self, read: impl FnMut() -> Option) -> bool { + if !self.one_command { + return false; + } + let ended = self.take(read); + assert_eq!(ended, None, "a -c shell waits from its start"); + self.interrupted + } + + /// An interactive shell hands its line to the surface and waits for it. + pub fn waiting(&mut self) { + self.waiting = true; + self.interrupted = false; + } + + /// The line it waited for has ended with `status`: the code the shell + /// ends with now, having taken what the notice holds, or `None` to run on. + /// A hang-up or a terminate read meanwhile ends it, and an interrupt ends + /// a `-c` shell, with `status`. + pub fn waited(&mut self, status: i32, read: impl FnMut() -> Option) -> Option { + if let Some(code) = self.take(read) { + return Some(code); + } + self.waiting = self.one_command; + if let Some(reason) = self.ended_by { + return Some(code_of(reason)); + } + (core::mem::take(&mut self.interrupted) && self.one_command).then_some(status) + } +} + +/// The exit code a quit's reason ends a shell with: 128 plus the signal's +/// number, as a shell reads one. +pub fn code_of(reason: Reason) -> i32 { + 128 + match reason { + Reason::Interrupt => 2, + Reason::HangUp => 1, + Reason::Terminate => 15, + } +} diff --git a/system/soundserver/mixer/src/shape.rs b/system/soundserver/mixer/src/shape.rs index 85edef46acd..2eac70415bf 100644 --- a/system/soundserver/mixer/src/shape.rs +++ b/system/soundserver/mixer/src/shape.rs @@ -10,7 +10,7 @@ /// Of a pipeline's periods, how many soundserver keeps in reserve rather than /// spending on a client that is still filling. /// -/// Policy, not physics, with the same standing as the kernel's `MAX_USER_STR`: +/// Policy, not physics, with the same standing as the kernel's `MAX_HANDLES`: /// of the shipped pipeline's 8 periods, soundserver waits on a client for at most 3 /// and always keeps 5 unplayed. It cannot be derived from worst-case wake /// lateness — the recorded worst exceeds two whole pipelines, so no floor diff --git a/system/soundserver/src/mix.rs b/system/soundserver/src/mix.rs index bd18f40f70f..35687619e34 100644 --- a/system/soundserver/src/mix.rs +++ b/system/soundserver/src/mix.rs @@ -303,7 +303,7 @@ pub(crate) fn mix_thread( static CAP: std::sync::OnceLock = std::sync::OnceLock::new(); let cap = CAP.get_or_init(|| { let cap: SysCap = Endowments::get() - .take(toyos_abi::syscall::SYSCAP_LABEL) + .take(toyos::endow::SYSCAP_LABEL) .expect("the manifest declares this program `syscap = [\"rt\", \"wake-latency\"]`"); cap.enter_rt().expect("an RT capability refused the band it names"); cap diff --git a/system/sshserver/src/spawn.rs b/system/sshserver/src/spawn.rs index 5cf83747fd9..090e552d6f5 100644 --- a/system/sshserver/src/spawn.rs +++ b/system/sshserver/src/spawn.rs @@ -14,13 +14,13 @@ use std::sync::Mutex; /// The login session one connection's programs run in. #[cfg(target_os = "toyos")] pub struct Login { - launcher: toyos::namespace::Namespace, + launcher: toyos::port::Connector, control: toyos::ipc::Connection, } /// What this daemon was endowed to open sessions with, taken once. #[cfg(target_os = "toyos")] -static LOGIN: std::sync::OnceLock> = std::sync::OnceLock::new(); +static LOGIN: std::sync::OnceLock> = std::sync::OnceLock::new(); #[cfg(target_os = "toyos")] impl Login { diff --git a/system/supervisor/Cargo.toml b/system/supervisor/Cargo.toml index e58e97be4d1..2ee46936804 100644 --- a/system/supervisor/Cargo.toml +++ b/system/supervisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "supervisor" -description = "The one program the kernel starts: it builds every program's namespace and device claims from /system/etc/system.manifest and starts it." +description = "The one program the kernel starts: it gives every program its services and device claims from /system/etc/system.manifest and starts it." version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" @@ -37,4 +37,4 @@ toyos-pci-claim = { path = "../../lib/pci-claim" } toyos-quiesce = { path = "../../lib/quiesce" } [package.metadata.toyos.host] -exempt.owns = "the machine's system capability: the kernel starts it, and it builds every program's namespace" +exempt.owns = "the machine's system capability: the kernel starts it, and it gives every program its services" diff --git a/system/supervisor/src/main.rs b/system/supervisor/src/main.rs index 6cf86e33146..658c4460143 100644 --- a/system/supervisor/src/main.rs +++ b/system/supervisor/src/main.rs @@ -4,8 +4,8 @@ //! Everything else is started from here, holding exactly what //! `/system/etc/system.manifest` says it holds. **Every port exists before any server //! runs**: the supervisor creates one per `serves` name in the whole manifest, then -//! builds each program's namespace out of the connectors and spawns it with the -//! acceptor moved in. So a client's connection works from its first +//! endows each program a copy of the connectors its row receives and the +//! acceptors it serves. So a client's connection works from its first //! instruction whether or not the server has reached `accept` or has even been //! spawned, there is no instant at which a name is not bound yet, and there is //! nothing anywhere to retry. @@ -126,11 +126,10 @@ use toyos_abi::devset::Declaration; use toyos_supervisor::drives::{self, Act, Drives}; use toyos_supervisor::restart::{self, AfterEnd, Budget}; use toyos::admission::{self, Admission, Event, Policy, Refused}; -use toyos::endow::{Endowments, FromHandle}; +use toyos::endow::{service_label, FromHandle, DEV_PREFIX, PROVIDE_PREFIX, SERVE_PREFIX, SVC_PREFIX, SYSCAP_LABEL}; use toyos::fs::{Access, Grant, CAPABILITY_PREFIX}; use toyos::ipc::{self, Connection, RxStep}; use toyos::launch::{self, Parent, Request, LAUNCHER}; -use toyos::namespace::{self, Namespace}; use toyos::poller::{Poller, READABLE, WRITABLE}; use toyos::port::{self, Acceptor, Connector}; use toyos::log::region::{Ring, RING_BYTES}; @@ -146,10 +145,8 @@ use toyos_logstream::{ Registration, Tag, Unanswered, ALIVE, CONSOLE_INPUT, FLUSH, MARK, MAX_TAG, ORIGINS, REGISTER, RESUME, STOPPING, }; use toyos_abi::inventory::{Arrival, Pci, PciAddr}; -use toyos_abi::syscall::{ - DeviceRequest, FileType, SyscallError, DEV_PREFIX, FILESERVER_IMAGE_LABEL, JOB_LABEL, MAX_BADGE, PROVIDE_PREFIX, - BOOT_NAMES_LABEL, ROOT_IMAGE_LABEL, SERVE_PREFIX, SVC_LABEL, SYSCAP_LABEL, -}; +use toyos_abi::syscall::{DeviceRequest, FileType, SyscallError, MAX_BADGE}; +use toyos_manifest::ROOT_IMAGE_LABEL; /// netstack's port, whose every connector the supervisor mints for one row and /// session (`Grants::netstack`). @@ -353,11 +350,7 @@ fn view(region: &SharedMemory) -> Ring { impl Log { fn open() -> Self { let (acceptor, connector) = port::create().expect("supervisor: no port for the log's origins"); - let names = namespace::build() - .add(ORIGINS, &connector) - .finish() - .expect("supervisor: no namespace for the log's origins"); - let conn = names.open(ORIGINS).expect("supervisor: the log's origins port refused the supervisor"); + let conn = connector.connect().expect("supervisor: the log's origins port refused the supervisor"); let (input, console_input) = toyos::pipe_pair().expect("supervisor: no pipe for the console's input"); let stdin = input.as_handle().try_clone_narrowed(STDIN_RIGHTS).expect("supervisor: the console's input would not narrow"); let log = Self { @@ -504,11 +497,10 @@ fn make_dir(path: &str) -> std::io::Result<()> { } fn main() { - let syscap: &'static SysCap = Box::leak(Box::new( - Endowments::get() - .take(SYSCAP_LABEL) - .expect("supervisor: the kernel spawns this program holding the system capability"), - )); + // SAFETY: the kernel starts the supervisor holding its system capability + // at this handle (`toyos_abi::boot::supervisor`). + let syscap = unsafe { SysCap::from_handle(kernel_gave(toyos_abi::boot::supervisor::SYSCAP)) }; + let syscap: &'static SysCap = Box::leak(Box::new(syscap)); say_last_words_on_panic(syscap); // Before anything is started, and before the supervisor says anything: from here // the supervisor's own lines are records in its ring. @@ -535,27 +527,24 @@ fn main() { } // The supervisor's own files are resolved through grants like every // program's, under a share no session is given, and it is the one - // process nobody endows a namespace: std resolves through this one, and - // the stop's syncs through the second. - let files: &'static Namespace = { + // process nobody endows a service: std resolves through a copy of each, + // and the stop's syncs through these. + let files: &'static [(String, Connector)] = { let whole = toyos_manifest::whole_tree(); let own: Vec<(String, Connector)> = whole .iter() .map(|view| (&role_acceptors[view.role], view)) .map(|(acceptor, view)| mint(acceptor, authority::SUPERVISOR_SHARE, view)) .collect(); - let build = || { - let mut builder = namespace::build(); - for (name, connector) in &own { - builder = builder.add(name, connector); - } - builder.finish().expect("supervisor: no namespace for its own files") - }; - // SAFETY: the namespace built just above. - if let Err(e) = unsafe { std::os::toyos::fs::adopt_namespace(build().into()) } { - panic!("supervisor: std would not take its namespace ({e}), and the supervisor is endowed none"); + let copies = own + .iter() + .map(|(name, connector)| (name.clone(), connector.duplicate().expect("supervisor: no copy of a grant of its own").into())) + .collect(); + // SAFETY: each is a duplicate of a connector minted just above. + if let Err(e) = unsafe { std::os::toyos::fs::adopt_services(copies) } { + panic!("supervisor: std would not take its files ({e}), and the supervisor is endowed none"); } - Box::leak(Box::new(build())) + Box::leak(own.into_boxed_slice()) }; // Its connector goes at once: every launcher a program holds is one minted // with that program's row. @@ -642,12 +631,7 @@ fn main() { // The supervisor alone holds a connector to it, so a connection on it is // the supervisor's question and nobody else's. - let consent = connectors.remove(consent::PORT).map(|connector| { - namespace::build() - .add(consent::PORT, &connector) - .finish() - .expect("supervisor: no namespace for the consent port") - }); + let consent = connectors.remove(consent::PORT); let mut grants = no_grants; grants.roles.push((toyos_manifest::SYSTEM, Rc::clone(&system_service.ports))); let mut supervisor = Supervisor { @@ -770,7 +754,7 @@ struct Supervisor<'a> { store: Store, /// [`consent::PORT`], where its server runs: what a launch asks the person at /// the screen on. - consent: Option, + consent: Option, syscap: &'a SysCap, /// The images the kernel handed the supervisor. boot: &'static Boot, @@ -782,8 +766,8 @@ struct Supervisor<'a> { grants: Grants<'a>, /// The shares its services and the login sessions launches opened spend. sessions: Sessions, - /// The same directories as a namespace of the supervisor's own, for the stop's syncs. - files: &'static Namespace, + /// The supervisor's own directory capabilities, by name, for the stop's syncs. + files: &'static [(String, Connector)], /// What `[boot] start` named, a file server once per role. services: Vec>, /// Every `drives` matcher, with its row. @@ -958,7 +942,6 @@ impl<'a> Service<'a> { Some(job) => job, None => self.job.insert(job_for(syscap, self.program)?), }; - let endowed = job.duplicate().map_err(memory_refused)?; let ports = self.ports.borrow(); let served = Served::Keep { acceptors: &ports, owed }; let command = match source { @@ -982,10 +965,10 @@ impl<'a> Service<'a> { served, connectors, grants, - &[], + Vec::new(), storage, Minting::machine(minted, self.program, self.session), - Output::Boot { log, job: endowed }, + Output::Boot { log, job }, )?; drop(ports); let pid = child.id(); @@ -1383,9 +1366,8 @@ impl<'a> Supervisor<'a> { let served = Served::Keep { acceptors: &[], owed: &[] }; let path = row.source.path().ok_or_else(|| format!("{} runs {}, and a `drives` row runs a file", row.name, row.source))?; let job = job_for(self.syscap, row).map_err(|e| e.to_string())?; - let endowed = job.duplicate().map_err(|e| format!("its job would not duplicate: {e:?}"))?; - let output = Output::Boot { log: &mut self.log, job: endowed }; - start(Command::new(path), row, self.system, self.syscap, served, &self.connectors, &self.grants, &[], extra, launcher, output) + let output = Output::Boot { log: &mut self.log, job: &job }; + start(Command::new(path), row, self.system, self.syscap, served, &self.connectors, &self.grants, Vec::new(), extra, launcher, output) .map(|(child, _)| (child, stop, job)) .map_err(|e| e.to_string()) } @@ -2084,9 +2066,9 @@ impl<'a> Supervisor<'a> { let Some(dir) = toyos_manifest::role_dirs(role).and_then(|dirs| dirs.first()) else { continue }; let name = format!("{CAPABILITY_PREFIX}{}", dir.dir); let (done_tx, done_rx) = std::sync::mpsc::channel(); - let (asked, names) = (name.clone(), self.files); + let Some((_, held)) = self.files.iter().find(|(held, _)| *held == name) else { continue }; let spawned = std::thread::Builder::new().name(format!("sync-{role}")).spawn(move || { - let answer = toyos::fs::Dir::connect(names, &asked).and_then(|mut dir| dir.sync()); + let answer = toyos::fs::Dir::connect(held.as_handle()).and_then(|mut dir| dir.sync()); let _ = done_tx.send(answer); }); match spawned { @@ -2380,16 +2362,15 @@ impl Supervisor<'_> { // descriptors, the extras, the place if it names one, and the job. let mut carried = carried; let fds: Vec = carried.by_ref().take(request.fd_count()).map(Into::into).collect(); - // `SYS_NAMESPACE_BUILD` copies a connector into the namespace and - // leaves the caller's handle, and the supervisor's copy of a client's - // connector has no life beyond this launch. let extras: Result, ipc::WrongKind> = names.into_iter().zip(carried.by_ref()).map(|(name, handle)| Ok((name.to_string(), handle.typed()?))).collect(); let mut rest: Vec = carried.collect(); let job = rest.pop().expect("a launch's batch ends with its job").typed::(); let place = rest.pop().map(ipc::ReceivedHandle::typed::).transpose(); // A client sends what it likes: what its frame calls a connector, a - // place or a job and the kernel does not is a refused launch. + // place or a job and the kernel does not is a refused launch, and a + // connector without the rights a provided one carries (`PROVIDED`) + // refuses it at its spawn. let (extras, job, place) = match (extras, job, place) { (Ok(extras), Ok(job), Ok(place)) => (extras, job, place), (Err(why), ..) | (_, Err(why), _) | (.., Err(why)) => { @@ -2489,9 +2470,10 @@ impl Supervisor<'_> { false => None, }; - // Endowed before `prepare` reads the program, so its image is read for the - // job the child runs in, which keeps it as long as the child runs. - command.endow(JOB_LABEL, job.into()); + // Named before `prepare` reads the program, so its image is read for the + // job the child runs in, which keeps it as long as the child runs. The + // command keeps a copy, and the caller's goes with this call. + command.job(job.as_handle().into()); // On the worker, every file the launch reads: a path under `/apps` is // read off its package, and the program and its working directory may @@ -2617,7 +2599,7 @@ impl Supervisor<'_> { .consent .as_ref() .ok_or_else(|| format!("this image serves no `{}`", consent::PORT)) - .and_then(|names| names.open(consent::PORT).map_err(|e| format!("its server is gone ({e:?})"))) + .and_then(|connector| connector.connect().map_err(|e| format!("its server is gone ({e:?})"))) .and_then(|conn| { let ask = consent::Ask { package: name.clone(), access }; conn.try_send_bytes(consent::MSG_ASK, &ask.encode()) @@ -2731,7 +2713,7 @@ impl Supervisor<'_> { Served::Move(&mut self.acceptors), &self.connectors, &self.grants, - &extras, + extras, storage, Minting { minted: &self.minted, session, launches }, Output::Launch { log: &mut self.log, fds: &fds, ring }, @@ -2829,7 +2811,7 @@ fn said(reply: &consent::Reply) -> String { /// /// **The row's own path has to match, and matching it is the whole check.** The /// path is a client's claim and the row is authority: keyed on the basename -/// alone, a caller writing `/tmp/toybox` would be handed `toybox`'s namespace +/// alone, a caller writing `/tmp/toybox` would be handed `toybox`'s services /// for a binary it wrote itself. So the answer is a row only where the caller /// named that row's binary — directly, or through a link that lands on it. fn declared<'a>(system: &'a Manifest, path: &str) -> Option<&'a Program> { @@ -2904,14 +2886,8 @@ fn served_slots( roots: Connector, ) -> Result<(toyos_update::slots::Table, [u8; 16], Vec), String> { use toyos_blockdev::{BlockDev as _, BLOCK}; - let names = |connector: &Connector| { - namespace::build() - .add(toyos_blockring::PORT, connector) - .finish() - .map_err(|e| format!("no namespace for the block service: {e:?}")) - }; let list = |connector: &Connector| { - diskserver::list(&names(connector)?, toyos_blockring::PORT) + diskserver::list(connector.as_handle()) .map_err(|why| format!("the block service would not list the slots' partitions: {why:?}")) }; let root_parts = list(&roots)?; @@ -2925,7 +2901,7 @@ fn served_slots( on_its_disk.len() )); }; - let session = diskserver::Session::open(names(&tables)?, toyos_blockring::PORT, table_part.unique) + let session = diskserver::Session::open(tables, table_part.unique) .map_err(|why| format!("the slot table would not open: {why:?}"))?; let table_unique = table_part.unique; let disk = diskserver::disk::Served::new(session); @@ -3045,7 +3021,7 @@ fn start<'a>( served: Served<'_, 'a>, connectors: &BTreeMap<&str, Connector>, grants: &Grants<'_>, - extras: &[(String, Connector)], + extras: Vec<(String, Connector)>, extra: Extra, minting: Minting<'_>, output: Output<'_>, @@ -3075,43 +3051,51 @@ fn start<'a>( for (label, handle) in endow { command.endow(label, handle.into()); } - let mut view = grants.view(program, minting.session); - view.extend(ports); - view.extend(grants.authorizations(program).map_err(StartError::Refused)?); - view.extend(grants.netstack(program, minting.session)); - if let Some(ns) = build_namespace(program, system, connectors, view, extras)? { - command.endow(SVC_LABEL, ns.into()); - } - if let Some(ns) = swap_namespace(program, connectors) { - command.endow(toyos_swap::LABEL, ns.into()); - } - if let Some(ns) = minting.launches.and_then(|launches| launcher_namespace(program, launches)) { - command.endow(LAUNCHER, ns.into()); + let mut services = received(program, system, connectors)?; + services.extend(grants.view(program, minting.session)); + services.extend(ports); + services.extend(grants.authorizations(program).map_err(StartError::Refused)?); + services.extend(grants.netstack(program, minting.session)); + for (name, connector) in services { + command.endow(&service_label(SVC_PREFIX, &name), connector.into()); + } + // Each under a name of its own and never as a service: a helper is given + // a duplicate of every service its parent holds (`toyos::spawn`), so + // these stay with the one process the supervisor started. + if program.receives.iter().any(|name| name == toyos_swap::PORT) { + let connector = connectors + .get(toyos_swap::PORT) + .expect("supervisor: the manifest declares the supervisor serves `swap`") + .duplicate() + .map_err(|e| std::io::Error::other(format!("the swap connector did not duplicate: {e:?}")))?; + // Its one holder connects through it and hands it to nobody. + command.endow_narrowed(toyos_swap::LABEL, connector.into(), Rights::WRITE.bits()); + } + if let Some((launcher, session)) = minting.launches.filter(|_| !program.starts.is_empty()) { + command.endow(LAUNCHER, badged(program, LAUNCHER, launcher, session).into()); } let minted = minting.minted; - let badged = [ - (grants::PORT, program.receives.iter().any(|name| name == grants::PORT), &minted.grants), - (toyos_session::ADMIN_PORT, program.receives.iter().any(|name| name == toyos_session::ADMIN_PORT), &minted.admin), - ]; - for (name, _, acceptor) in badged.into_iter().filter(|(_, receives, _)| *receives) { - command.endow(name, badged_namespace(program, name, acceptor, minting.session).into()); + for (name, acceptor) in [(grants::PORT, &minted.grants), (toyos_session::ADMIN_PORT, &minted.admin)] { + if program.receives.iter().any(|received| received == name) { + command.endow(name, badged(program, name, acceptor, minting.session).into()); + } } if program.login { - command.endow(toyos_session::LOGIN_PORT, login_namespace(program, &minted.login).into()); + let connector = minted + .login + .mint(program.name.as_bytes()) + .unwrap_or_else(|e| panic!("supervisor: no `login` connector for {}: {e:?}", program.name)); + command.endow(toyos_session::LOGIN_PORT, connector.into()); } - // The namespace answers with connections, so a child holding `surface` - // only there could never hand `surface` to a child of its own — which is - // the terminal → shell → `locale` chain. The connector travels labelled as - // well, and a duplicate because the namespace above took one of its own. + // A `provides` name reaches its holder from whoever made the port, never + // from the supervisor, and only a row that receives the name. A client's + // handle that does not carry `PROVIDED` refuses the launch, never the + // supervisor. for (name, connector) in extras { - // **Not an `expect`.** The connector is a client's, and a client may - // send one it narrowed `DUP` away from. That is a refused launch, not - // the supervisor's bug — and the supervisor is the one process the machine cannot lose. - let Ok(handed) = connector.duplicate() else { - return Err(std::io::Error::other("a provided connector cannot be duplicated").into()); - }; - command.endow(&format!("{PROVIDE_PREFIX}{name}"), handed.into()); + if program.receives.contains(&name) { + command.endow_narrowed(&service_label(PROVIDE_PREFIX, &name), connector.into(), PROVIDED.bits()); + } } // A `serves` port whose acceptor is gone can never be served again, so @@ -3229,7 +3213,7 @@ fn start<'a>( // **Every service the supervisor starts is a job of its own**, // which its caller made ([`job_for`]). let ring = ring_for(job.as_handle())?; - command.endow(JOB_LABEL, job.into()); + command.job(job.as_handle().into()); command.inherit_handle(0, log.stdin.as_handle().into()); command.inherit_handle(1, ring.as_handle().into()); command.inherit_handle(2, ring.as_handle().into()); @@ -3324,7 +3308,7 @@ fn endow_served(command: &mut Command, name: &str, acceptor: &Acceptor) -> Resul /// job it joins, or a launch's caller's descriptors with any ring among them /// replaced by the program's. enum Output<'l> { - Boot { log: &'l mut Log, job: toyos::job::Job }, + Boot { log: &'l mut Log, job: &'l toyos::job::Job }, Launch { log: &'l mut Log, fds: &'l [(u32, LaunchFd)], ring: Option }, } @@ -3336,44 +3320,37 @@ enum LaunchFd { Ring, } -/// The namespace this program's `receives` names, [`toyos_swap::PORT`] and -/// [`toyos_blockring::PORT`] apart, with `view`'s. +/// What a provided connector arrives carrying: a copy for each helper and +/// launch of the program, and its connect. +const PROVIDED: Rights = Rights::DUP.union(Rights::TRANSFER).union(Rights::WRITE); + +/// A copy of the connector to each service this program's `receives` names +/// that the supervisor's own map holds, by name. /// -/// A name some program *provides* rather than serves is not the supervisor's to give: it -/// is one port per instance, made by whoever spawns the holder, and it reaches -/// this program from its own parent. A name that is neither is a config the -/// build-time gate should have refused, so it is a panic here. -/// A refusal is a launch that does not happen, never a panic: `extras` are a -/// client's handles and the kernel judges their type, so `finish` answers -/// `InvalidArgument` for a client that sent a pipe where a connector belongs. -fn build_namespace( - program: &Program, - system: &Manifest, - connectors: &BTreeMap<&str, Connector>, - view: Vec<(String, Connector)>, - extras: &[(String, Connector)], -) -> std::io::Result> { - // Never the swap port nor the grants port: [`swap_namespace`] and - // [`badged_namespace`] say why. Never the block port's own connector, - // which reaches nothing: a holder of `block` holds one minted for what it - // reaches, in `view`, and a disk driver one of `disk` minted for who it - // is; nor netstack's, which nothing holds: a holder of `netstack` holds - // one minted for its row and session, in `view` too. - let receives: Vec<&String> = program - .receives - .iter() - .filter(|name| { - ![toyos_swap::PORT, grants::PORT, toyos_session::ADMIN_PORT, toyos_blockring::PORT, toyos_blockring::disk::PORT, NETSTACK] - .contains(&name.as_str()) - }) - .collect(); - if receives.is_empty() && extras.is_empty() && view.is_empty() { - return Ok(None); - } - let mut builder = namespace::build(); - for name in receives { +/// Never the swap, grants or admin port, which go under names of their own +/// ([`start`]). Never the block port's own connector, which reaches nothing: +/// a holder of `block` holds one minted for what it reaches, and a disk +/// driver one of `disk` minted for who it is; nor netstack's, which nothing +/// holds: a holder of `netstack` holds one minted for its row and session. +/// +/// A name some program *provides* rather than serves is not the supervisor's +/// to give: it is one port per instance, made by whoever spawns the holder, +/// and it reaches this program from its own parent. A name that is neither +/// is a config the build-time gate should have refused, so it is a panic +/// here. +fn received(program: &Program, system: &Manifest, connectors: &BTreeMap<&str, Connector>) -> std::io::Result> { + let mut services = Vec::new(); + for name in program.receives.iter().filter(|name| { + ![toyos_swap::PORT, grants::PORT, toyos_session::ADMIN_PORT, toyos_blockring::PORT, toyos_blockring::disk::PORT, NETSTACK] + .contains(&name.as_str()) + }) { match connectors.get(name.as_str()) { - Some(connector) => builder = builder.add(name, connector), + Some(connector) => { + let copy = connector + .duplicate() + .map_err(|e| std::io::Error::other(format!("the `{name}` connector did not duplicate: {e:?}")))?; + services.push((name.clone(), copy)); + } None => assert!( system.programs.iter().any(|p| p.provides.contains(name)), "supervisor: {} receives `{name}`, which nothing in this image serves or provides", @@ -3381,21 +3358,7 @@ fn build_namespace( ), } } - for (name, connector) in &view { - builder = builder.add(name, connector); - } - // A `provides` name reaches its holder from whoever made the port, never - // from the supervisor, and this is where it arrives. - for (name, connector) in extras { - builder = builder.add(name, connector); - } - match builder.finish() { - Ok(ns) => Ok(Some(ns)), - Err(e) if extras.is_empty() => { - panic!("supervisor: no namespace for {}: {e:?}", program.name) - } - Err(e) => Err(std::io::Error::other(format!("a provided connector was refused: {e:?}"))), - } + Ok(services) } /// What each program's directory capabilities are minted from: each @@ -3504,7 +3467,7 @@ impl Grants<'_> { } /// The directory capabilities `program` is endowed for one start in - /// `session`, by namespace name: its row's view (`Program::view`), but + /// `session`, by service name: its row's view (`Program::view`), but /// that a storage row sees none, since a file server resolving a path of /// its own through itself waits for ever. fn view(&self, program: &Program, session: Session) -> Vec<(String, Connector)> { @@ -3538,7 +3501,7 @@ const _: () = assert!( ); /// A grant on `acceptor`, `dir`'s role's port, naming `share`, by the -/// namespace name a program opens it under. +/// service name a program opens it under. fn mint(acceptor: &Acceptor, share: u64, dir: &View) -> (String, Connector) { let access = if dir.write { Access::ReadWrite } else { Access::ReadOnly }; let mut badge = [0u8; MAX_BADGE]; @@ -3551,64 +3514,15 @@ fn mint(acceptor: &Acceptor, share: u64, dir: &View) -> (String, Connector) { (format!("{CAPABILITY_PREFIX}{}", dir.dir), connector) } -/// [`toyos_swap::PORT`] in a namespace of its own, for a program whose row -/// receives it, endowed under [`toyos_swap::LABEL`]. -/// -/// **Not an entry of `svc`**, because std gives a duplicate of `svc` to every -/// program its holder spawns directly — sshserver running one the manifest does not -/// declare — and to their descendants. A label other than `svc` is not -/// inherited, so the port stays with the one process the supervisor endowed. -fn swap_namespace(program: &Program, connectors: &BTreeMap<&str, Connector>) -> Option { - if !program.receives.iter().any(|name| name == toyos_swap::PORT) { - return None; - } - let connector = connectors - .get(toyos_swap::PORT) - .expect("supervisor: the manifest declares the supervisor serves `swap`"); - let ns = namespace::build() - .add(toyos_swap::PORT, connector) - .finish() - .unwrap_or_else(|e| panic!("supervisor: no swap namespace for {}: {e:?}", program.name)); - Some(ns) -} - -/// A `launcher` minted with `program`'s row and the session its launches run -/// in, on that session's launcher port, in a namespace of its own, for a row -/// whose `starts` lists anything; endowed under [`LAUNCHER`] and never an -/// entry of `svc`, for [`swap_namespace`]'s reason. -fn launcher_namespace(program: &Program, (launcher, session): (&Acceptor, Session)) -> Option { - if program.starts.is_empty() { - return None; - } - Some(badged_namespace(program, LAUNCHER, launcher, session)) -} - -/// `name`, minted on `acceptor` with `program`'s row and `session`, in a -/// namespace of its own: endowed under that name and never an entry of `svc`, -/// for [`swap_namespace`]'s reason. The badge is who asks: the supervisor -/// honours a login session's alone on [`grants::PORT`] and -/// [`toyos_session::ADMIN_PORT`]. -fn badged_namespace(program: &Program, name: &str, acceptor: &Acceptor, session: Session) -> Namespace { +/// `name`, minted on `acceptor` with `program`'s row and `session`. The badge +/// is who asks: the supervisor honours a login session's alone on +/// [`grants::PORT`] and [`toyos_session::ADMIN_PORT`], and a launcher's +/// launches run in its session. +fn badged(program: &Program, name: &str, acceptor: &Acceptor, session: Session) -> Connector { let badge = Authority { row: program.name.clone(), session }.encode(); - let connector = acceptor + acceptor .mint(&badge) - .unwrap_or_else(|e| panic!("supervisor: no `{name}` connector for {}: {e:?}", program.name)); - namespace::build() - .add(name, &connector) - .finish() - .unwrap_or_else(|e| panic!("supervisor: no `{name}` namespace for {}: {e:?}", program.name)) -} - -/// [`toyos_session::LOGIN_PORT`] minted with a `login` row's key, the login -/// authority whose sessions it opens. -fn login_namespace(program: &Program, login: &Acceptor) -> Namespace { - let connector = login - .mint(program.name.as_bytes()) - .unwrap_or_else(|e| panic!("supervisor: no `login` connector for {}: {e:?}", program.name)); - namespace::build() - .add(toyos_session::LOGIN_PORT, &connector) - .finish() - .unwrap_or_else(|e| panic!("supervisor: no `login` namespace for {}: {e:?}", program.name)) + .unwrap_or_else(|e| panic!("supervisor: no `{name}` connector for {}: {e:?}", program.name)) } /// One of the supervisor's words on a swap: its line in the log. @@ -3617,8 +3531,8 @@ fn swapped(service: &str, word: Word, detail: &str) { } /// What a process is started with beside its row: arguments, a `drives` -/// process's claim on its function endowed by label, and connectors in its -/// namespace by name, each minted for what it reaches: the partitions the +/// process's claim on its function endowed by label, and services by name, +/// each minted for what it reaches: the partitions the /// block service serves, or a disk driver's `disk`. #[derive(Default)] struct Extra { @@ -3780,26 +3694,30 @@ impl Boot { /// Where the loader's record is not one this supervisor reads: the two /// are not one build's, and no partition it names can be trusted. fn take() -> Self { - let handle = |label: &str| -> toyos::OwnedHandle { - Endowments::get() - .take(label) - .unwrap_or_else(|| panic!("supervisor: the kernel spawns this program holding `{label}`")) - }; - let image = |label: &str| { - let handle = handle(label); + use toyos_abi::boot::supervisor as at; + let image = |at: toyos::RawHandle, what: &str| { + let handle = kernel_gave(at); let kind = toyos_abi::syscall::fstat(handle.as_handle().raw()).map(|stat| stat.file_type); - assert_eq!(kind, Ok(FileType::ImmutableMemory), "supervisor: `{label}` is no image nobody writes"); + assert_eq!(kind, Ok(FileType::ImmutableMemory), "supervisor: {what} is no image nobody writes"); handle }; - let record = toyos::shm::Immutable::adopt(handle(BOOT_NAMES_LABEL)) - .unwrap_or_else(|why| panic!("supervisor: `{BOOT_NAMES_LABEL}` is no image nobody writes: {why}")); + let record = toyos::shm::Immutable::adopt(kernel_gave(at::BOOT_NAMES)) + .unwrap_or_else(|why| panic!("supervisor: the loader's boot names are no image nobody writes: {why}")); let record: &'static toyos::shm::Immutable = Box::leak(Box::new(record)); let names = toyos_bootnames::Names::parse(record.as_slice()) .unwrap_or_else(|why| panic!("supervisor: the loader's boot names are refused: {why}")); - Self { fileserver: image(FILESERVER_IMAGE_LABEL), root: image(ROOT_IMAGE_LABEL), names } + Self { fileserver: image(at::FILESERVER_IMAGE, "the file server's image"), root: image(at::ROOT_IMAGE, "ROOT"), names } } } +/// The handle the kernel starts the supervisor holding at `at`, one of +/// `toyos_abi::boot::supervisor`'s, owned from here. +fn kernel_gave(at: toyos::RawHandle) -> toyos::OwnedHandle { + // SAFETY: the kernel installs each of those in this process's table + // before it runs, and `main` and `Boot::take` each take one once. + unsafe { toyos::OwnedHandle::from_raw(at) } +} + /// The row SYSTEM is started under, which no manifest declares since it /// serves the manifest: the file server's boot image, serving ROOT, started /// again when it ends, and holding nothing but its role's port and ROOT. diff --git a/system/supervisor/src/sessions.rs b/system/supervisor/src/sessions.rs index e1656112a91..5c1e837d8d7 100644 --- a/system/supervisor/src/sessions.rs +++ b/system/supervisor/src/sessions.rs @@ -24,8 +24,6 @@ use std::sync::{mpsc, Arc}; use std::time::Instant; use toyos::ipc::{self, Connection, RxStep}; -use toyos::launch::LAUNCHER; -use toyos::namespace::{self, Namespace}; use toyos::poller::{Poller, READABLE}; use toyos::port::{self, Acceptor, Connector}; use toyos::wake::{self, Bell, Waker}; @@ -60,7 +58,7 @@ pub(crate) struct Login { /// Held here alone, so the session's end closes it and a launch connected /// to it reads its end. pub launcher: Acceptor, - /// The session's control port, a namespace of which each login + /// The session's control port, a connector to which each login /// authority is handed to connect its own control connection through. controls: Connector, /// Whether the screen's row opened it: the desktop's. @@ -72,8 +70,8 @@ pub(crate) struct Login { /// The desktop session's launcher and its controls, minted for one start of /// the screen's row. pub(crate) struct Desktop { - pub launcher: Namespace, - pub control: Namespace, + pub launcher: Connector, + pub control: Connector, } /// A session owner's link: the port the owner connects, until it has, then @@ -122,11 +120,10 @@ impl Link { impl Login { /// `opener`'s launcher in this session, and its controls to connect to. - fn handed_to(&self, opener: &Program) -> Result<(Namespace, Namespace), String> { + fn handed_to(&self, opener: &Program) -> Result<(Connector, Connector), String> { let badge = Authority { row: opener.name.clone(), session: self.session }.encode(); - let connector = self.launcher.mint(&badge).map_err(|e| format!("no launcher was minted ({e:?})"))?; - let launcher = namespace::build().add(LAUNCHER, &connector).finish().map_err(|e| format!("{e:?}"))?; - let controls = namespace::build().add(wire::CONTROL, &self.controls).finish().map_err(|e| format!("{e:?}"))?; + let launcher = self.launcher.mint(&badge).map_err(|e| format!("no launcher was minted ({e:?})"))?; + let controls = self.controls.duplicate().map_err(|e| format!("no copy of its controls ({e:?})"))?; Ok((launcher, controls)) } } @@ -306,11 +303,10 @@ impl Supervisor<'_> { let (launcher, _) = port::create().map_err(|e| format!("no launcher port ({e:?})"))?; let (controls_acceptor, controls_connector) = port::create().map_err(|e| format!("no control port ({e:?})"))?; let (link_acceptor, link_connector) = port::create().map_err(|e| format!("no link ({e:?})"))?; - let link = namespace::build().add(wire::LINK, &link_connector).finish().map_err(|e| format!("{e:?}"))?; let accepting = launcher.as_handle().try_clone_to_owned().map_err(|e| format!("no copy of its launcher ({e:?})"))?; let extra = Extra { endow: vec![ - (wire::LINK_LABEL, link.into()), + (wire::LINK_LABEL, link_connector.into()), (wire::LAUNCHER_ACCEPTOR_LABEL, accepting), (wire::CONTROL_ACCEPTOR_LABEL, controls_acceptor.into()), ], @@ -328,10 +324,10 @@ impl Supervisor<'_> { served, &self.connectors, &self.grants, - &[], + Vec::new(), extra, minting, - Output::Boot { log: &mut self.log, job }, + Output::Boot { log: &mut self.log, job: &job }, ) .map_err(|e| format!("its owner did not start: {e}"))?; say!("supervisor: session {} opened for {}, owned by pid {}", session.share(), opener.name, owner.id()); @@ -406,8 +402,8 @@ impl Supervisor<'_> { .map_or_else(|| self.open_session(opener), Ok) .and_then(|at| self.logins.open[at].handed_to(opener)); match answered { - Ok((ns, controls)) => { - let handles: [OwnedHandle; 2] = [ns.into(), controls.into()]; + Ok((launcher, controls)) => { + let handles: [OwnedHandle; 2] = [launcher.into(), controls.into()]; if let Err(e) = conn.try_signal_with(wire::MSG_OPENED, handles) { say!("supervisor: login: {}'s session was opened and not answered ({e:?})", opener.name); } diff --git a/system/swap/src/main.rs b/system/swap/src/main.rs index 0f3cd428d36..1a7112514b9 100644 --- a/system/swap/src/main.rs +++ b/system/swap/src/main.rs @@ -29,7 +29,7 @@ use std::io::{Read, Write}; use std::time::Duration; use toyos::endow::Endowments; -use toyos::namespace::Namespace; +use toyos::port::Connector; use toyos_swap::{Refusal, Request}; fn main() { @@ -103,10 +103,10 @@ fn take(args: &[String]) -> Result { /// program holds until the go where the supervisor accepted. fn ask(asked: &Asked) -> (Answer, Option) { let unasked = |why: String| (Answer::Unasked(why), None); - let Some(held) = Endowments::get().take::(toyos_swap::LABEL) else { + let Some(held) = Endowments::get().take::(toyos_swap::LABEL) else { return unasked(format!("{}; run it with `launch swap`", Refusal::NoAuthority)); }; - let supervisor = match held.open(toyos_swap::PORT) { + let supervisor = match held.connect() { Ok(conn) => conn, Err(e) => return unasked(format!("the supervisor's swap port did not answer: {e:?}")), }; diff --git a/system/terminal/src/main.rs b/system/terminal/src/main.rs index e64416b77e1..1bac30ad954 100644 --- a/system/terminal/src/main.rs +++ b/system/terminal/src/main.rs @@ -68,8 +68,8 @@ fn copy(text: &str) { fn main() { // **This terminal's surface is a port it makes, not a name it registers.** - // One per instance: the connector goes into the namespace of the shell it - // spawns and nowhere else, so nothing outside that subtree can reach this + // One per instance: the connector goes to the shell it spawns and + // nowhere else, so nothing outside that subtree can reach this // terminal's keyboard and nothing inside it can name a service this // process was not given. `TOYOS_SURFACE` — an ambient string naming a // machine-wide service — is what that replaces. @@ -108,9 +108,7 @@ fn main() { for client in host.client_handles() { poller.watch(&client, READABLE, TOKEN_CLIENT); } - if let Some(notice) = notice { - poller.watch(notice, READABLE, TOKEN_NOTICE); - } + poller.watch(notice, READABLE, TOKEN_NOTICE); let mut ready = [false; 6]; poller.wait(1, u64::MAX, |token| { @@ -151,7 +149,7 @@ fn main() { // An interrupt is the foreground's, which the quit that asked it // reached already; a hang-up or a terminate closes this terminal. if ready[TOKEN_NOTICE as usize] { - let asked = notice.and_then(|n| n.try_read().ok().flatten()); + let asked = notice.try_read().ok().flatten(); if let Some(reason @ (toyos::quit::Reason::HangUp | toyos::quit::Reason::Terminate)) = asked { break Leaving::Asked(reason); } diff --git a/system/toybox/src/ps.rs b/system/toybox/src/ps.rs index c1ab5cb96c9..876ebf466ba 100644 --- a/system/toybox/src/ps.rs +++ b/system/toybox/src/ps.rs @@ -69,12 +69,12 @@ pub fn main(_args: Vec) { }; let mut buf = vec![0u8; HEADER + ENTRY * 128]; let Some((first_uptime, _, first)) = sample(&cap, &mut buf) else { - eprintln!("ps: refused — this capability carries no ROSTER"); + eprintln!("ps: refused — this capability carries no ROSTER, or this program's job has no room for the roster"); std::process::exit(1); }; std::thread::sleep(std::time::Duration::from_nanos(SAMPLE_NS)); let Some((uptime_ns, total_cpu_ns, rows)) = sample(&cap, &mut buf) else { - eprintln!("ps: refused — this capability carries no ROSTER"); + eprintln!("ps: refused — this capability carries no ROSTER, or this program's job has no room for the roster"); std::process::exit(1); }; let window_ns = uptime_ns.saturating_sub(first_uptime); diff --git a/system/toybox/src/roster.rs b/system/toybox/src/roster.rs index 1ab2ea5bc86..ef81358ddc1 100644 --- a/system/toybox/src/roster.rs +++ b/system/toybox/src/roster.rs @@ -1,6 +1,5 @@ //! The roster `ps` prints, as the kernel answers it: the machine's header, -//! then one entry per live thread, or nothing to a capability carrying no -//! `ROSTER`. +//! then one entry per live thread, or nothing where it refused the roster. use toyos_abi::syscall::{SYSINFO_ENTRY_SIZE as ENTRY, SYSINFO_HEADER_SIZE as HEADER}; @@ -17,8 +16,9 @@ pub struct Row { } /// The machine's uptime, the CPU nanoseconds it has ever run, and every row of -/// `answer`, a roster the kernel wrote. `None` is a capability carrying no -/// `ROSTER`, which the kernel answers with nothing. +/// `answer`, a roster the kernel wrote. `None` is a roster the kernel refused, +/// which it answers with nothing: to a capability carrying no `ROSTER`, or to +/// a job without the room to hold the roster while it is copied out. pub fn rows(answer: &[u8]) -> Option<(u64, u64, Vec)> { let (buf, n) = (answer, answer.len()); if n < HEADER { diff --git a/system/update/src/main.rs b/system/update/src/main.rs index 8a0aca33489..10d7f319705 100644 --- a/system/update/src/main.rs +++ b/system/update/src/main.rs @@ -8,7 +8,7 @@ //! //! **What it holds is the whole of what it can write** (`slots` in its //! `system.toml` row): the slot table's partition and the idle slot's FAT -//! volume and ROOT, as connectors in its namespace under their labels, each +//! volume and ROOT, as connectors endowed under their labels, each //! minted for its one partition on the block service's port; the //! slot this boot runs is never among them (`toyos_update::slots::idle`). What //! it writes there, and in what order, is [`install`]'s. @@ -18,7 +18,7 @@ mod install; use std::time::Instant; use diskserver::disk::Served; -use toyos::endow; +use toyos::AsHandle; use toyos_update::sig; use toyos_update::slots; use install::Partition; @@ -50,13 +50,12 @@ struct Held { /// that name, which reaches one partition and lists it. fn held(label: &str) -> Result { let none = || format!("this process holds no `{label}`: the supervisor grants the idle slot to one update at a time, and says why where it grants none; run it with `launch update`"); - let names = endow::namespace().ok_or_else(none)?; - let own = toyos::namespace::build().keep(names, &[label]).finish().map_err(|_| none())?; - let listed = diskserver::list(&own, label).map_err(|why| format!("`{label}` would not list its partition: {why:?}"))?; + let own = diskserver::held(label).map_err(|_| none())?; + let listed = diskserver::list(own.as_handle()).map_err(|why| format!("`{label}` would not list its partition: {why:?}"))?; let [one] = listed[..] else { return Err(format!("`{label}` lists {} partitions, and is minted for one", listed.len())); }; - let session = diskserver::Session::open(own, label, one.unique) + let session = diskserver::Session::open(own, one.unique) .map_err(|why| format!("`{label}`'s partition would not open: {why:?}"))?; Ok(Held { disk: Served::new(session), unique: one.unique }) } diff --git a/system/usbd/src/main.rs b/system/usbd/src/main.rs index 7aeef936702..650f34b1968 100644 --- a/system/usbd/src/main.rs +++ b/system/usbd/src/main.rs @@ -23,12 +23,12 @@ use std::collections::BTreeMap; use std::time::{Duration, Instant}; -use toyos::endow::{self, EndowError, Endowments}; +use toyos::endow::{self, EndowError, Endowments, DEV_PREFIX}; use toyos::admission::{self, Admission, Client, Policy}; use toyos::ipc::{Connection, TrySendError}; use toyos::poller::{Poller, READABLE, WRITABLE}; use toyos::{AsHandle, PciDev}; -use toyos_abi::syscall::{SyscallError, DEV_PREFIX}; +use toyos_abi::syscall::SyscallError; use toyos_blockring::disk::Settled; use toyos_blockserve::link::{Handed, Link}; use toyos_blockserve::sessions::{Owner as Request, MAX_SESSIONS}; @@ -185,7 +185,7 @@ fn main() { Err(why) => not_serving(&name, &format!("the controller would not run: {why}"), link, stop), }; println!("{name}: running, {} ports, a {} KiB grant", hc.max_ports, bytes / 1024); - let config = Config { input: endow::namespace().is_some(), disks: link.is_some() }; + let config = Config { input: endow::connector(wire::PORT).is_some(), disks: link.is_some() }; let usb: Driver = Usb::new(hc, host, config, table::read); serve(&name, &dev, &usb, acceptor, link, stop) } diff --git a/tests/guest/hangupholder/src/main.rs b/tests/guest/hangupholder/src/main.rs index 5297b5cbcbb..bada6f7b293 100644 --- a/tests/guest/hangupholder/src/main.rs +++ b/tests/guest/hangupholder/src/main.rs @@ -6,7 +6,7 @@ use std::io::Write; fn main() { - let notice = toyos::quit::notice().expect("hangupholder: every process holds its notice"); + let notice = toyos::quit::notice(); notice.listen().expect("hangupholder: its notice listens"); let _ = writeln!(std::io::stdout(), "hangupholder: holding"); loop { diff --git a/tests/guest/metalprobe/src/fb.rs b/tests/guest/metalprobe/src/fb.rs index b27abfe75e4..3d95e6846c9 100644 --- a/tests/guest/metalprobe/src/fb.rs +++ b/tests/guest/metalprobe/src/fb.rs @@ -13,7 +13,8 @@ use std::time::Instant; use toyos::device::FramebufferDev; use toyos::endow::Endowments; use toyos::syscap::SysCap; -use toyos_abi::syscall::{DeviceType, SYSCAP_LABEL}; +use toyos_abi::syscall::DeviceType; +use toyos::endow::SYSCAP_LABEL; use crate::{span, Measured, Refusal}; diff --git a/tests/guest/osbench/src/fair.rs b/tests/guest/osbench/src/fair.rs index 831c8b63673..33862a5b11c 100644 --- a/tests/guest/osbench/src/fair.rs +++ b/tests/guest/osbench/src/fair.rs @@ -241,7 +241,7 @@ impl Grouping { let cap = crate::sys::syscap().ok_or("no capability was endowed, so no job can be made")?; let job = cap.new_job().map_err(|e| format!("a new job was refused: {e:?}"))?; let mut command = Command::new(exe); - command.endow(toyos::job::JOB_LABEL, job.into()); + command.job(toyos::AsHandle::as_handle(&job).into()); Ok(command) } #[cfg(target_os = "linux")] diff --git a/tests/guest/osbench/src/ipc.rs b/tests/guest/osbench/src/ipc.rs index 05d89dd95c4..189483a99de 100644 --- a/tests/guest/osbench/src/ipc.rs +++ b/tests/guest/osbench/src/ipc.rs @@ -155,7 +155,7 @@ pub fn suite() { } // A connection opened and accepted by this one process, both ends then - // closed: ToyOS through a port in a namespace this process built, Linux + // closed: ToyOS through the connector of a port this process made, Linux // to a listening socket in the abstract namespace. 1,000 after 50. match ipc::Rendezvous::new() { Ok(rendezvous) => { diff --git a/tests/guest/osbench/src/sys/toyos.rs b/tests/guest/osbench/src/sys/toyos.rs index 1475b942fb4..b562f789d00 100644 --- a/tests/guest/osbench/src/sys/toyos.rs +++ b/tests/guest/osbench/src/sys/toyos.rs @@ -143,7 +143,6 @@ pub mod ipc { use toyos::{OwnedHandle, Pipe}; const LABEL: &str = "osbench-ipc"; - const NAME: &str = "osbench"; const FRAME: usize = MAX_FRAME_LEN as usize; /// One end of a connection. A message longer than one frame @@ -212,8 +211,7 @@ pub mod ipc { /// connection to it. pub fn start_server(exe: &std::path::Path) -> io::Result<(Child, Peer)> { let (acceptor, connector) = port::create().map_err(|e| failed("port", e))?; - let names = toyos::namespace::build().add(NAME, &connector).finish().map_err(|e| failed("namespace", e))?; - let conn = names.open(NAME).map_err(|e| failed("connect", e))?; + let conn = connector.connect().map_err(|e| failed("connect", e))?; let child = Command::new(exe) .arg("ipc-serve") .endow(LABEL, acceptor.into()) @@ -233,19 +231,18 @@ pub mod ipc { /// connection's setup alone. pub struct Rendezvous { acceptor: Acceptor, - names: toyos::namespace::Namespace, + connector: port::Connector, } impl Rendezvous { pub fn new() -> io::Result { let (acceptor, connector) = port::create().map_err(|e| failed("port", e))?; - let names = toyos::namespace::build().add(NAME, &connector).finish().map_err(|e| failed("namespace", e))?; - Ok(Self { acceptor, names }) + Ok(Self { acceptor, connector }) } /// Open a connection and accept it; both ends close on return. pub fn connect_accept(&self) -> io::Result<()> { - let client = self.names.open(NAME).map_err(|e| failed("connect", e))?; + let client = self.connector.connect().map_err(|e| failed("connect", e))?; let server = self.acceptor.accept().map_err(|e| failed("accept", e))?; drop((client, server)); Ok(()) @@ -641,8 +638,7 @@ pub mod direct { /// `dir`'s capability, connected: `None` where no file server serves it, /// and why where its server would not take this second connection. pub fn connect(dir: &str) -> Result, String> { - let Some(names) = toyos::endow::namespace() else { return Ok(None) }; - match Dir::connect(names, &format!("{}{dir}", toyos::fs::CAPABILITY_PREFIX)) { + match Dir::named(&format!("{}{dir}", toyos::fs::CAPABILITY_PREFIX)) { Ok(d) => Ok(Some(d)), Err(Error::NotHeld) => Ok(None), Err(e) => Err(format!("{dir}'s file server would not take a second connection: {e:?}")), diff --git a/tests/guest/rust-tests/src/bin/a_served_file_reads_through_its_paged_object.rs b/tests/guest/rust-tests/src/bin/a_served_file_reads_through_its_paged_object.rs index 17e7d08990c..350e1250eff 100644 --- a/tests/guest/rust-tests/src/bin/a_served_file_reads_through_its_paged_object.rs +++ b/tests/guest/rust-tests/src/bin/a_served_file_reads_through_its_paged_object.rs @@ -33,8 +33,7 @@ fn read(holder: &Paged, offset: u64, len: usize) -> Vec { } fn main() { - let names = toyos::endow::namespace().expect("a test is endowed a namespace"); - let mut dir = Dir::connect(names, DIR).expect("the home directory"); + let mut dir = Dir::named(DIR).expect("the home directory"); let data = pattern(5 * 4096 + 123); let writer = dir.open(Base::Root, NAME, O_WRITE | O_CREATE | O_TRUNCATE).expect("open to write"); diff --git a/tests/guest/rust-tests/src/bin/abuse_connect_flood.rs b/tests/guest/rust-tests/src/bin/abuse_connect_flood.rs index d48aca53814..4a8331d438f 100644 --- a/tests/guest/rust-tests/src/bin/abuse_connect_flood.rs +++ b/tests/guest/rust-tests/src/bin/abuse_connect_flood.rs @@ -21,9 +21,6 @@ use toyos_abi::RawHandle; /// table's cap. const ATTEMPTS: usize = 200; -/// The name this process's own namespace maps to its own port. -const NAME: &str = "abuse-connect-flood"; - /// Physical bytes in use, from `SYS_SYSINFO`'s header. fn used_bytes() -> u64 { let mut buf = [0u8; toyos::system::SYSINFO_HEADER_SIZE]; @@ -33,20 +30,15 @@ fn used_bytes() -> u64 { } fn main() { + // A connector this process holds is the only way to open a connection at + // all. let (acceptor, connector) = port::create().expect("a port of our own"); - // One namespace holding that connector, which is the only way to open a - // connection at all: a name resolves in a namespace this process holds and - // nowhere else. - let ns = toyos::namespace::build() - .add(NAME, &connector) - .finish() - .expect("a namespace naming our own port"); let before = used_bytes(); let mut held: Vec = Vec::new(); let mut refused_after = None; for i in 0..ATTEMPTS { - match syscall::namespace_open(ns.as_handle().raw(), NAME) { + match syscall::connect(connector.as_handle().raw()) { Ok(handle) => held.push(handle), Err(SyscallError::ResourceExhausted) => { refused_after = Some(i); @@ -69,6 +61,27 @@ fn main() { // decision someone makes on purpose. Tracks `MAX_PENDING_CONNECTIONS`. assert_eq!(depth, 32, "the burst allowance moved"); + // A refused connect leaves the caller's table as it was. The table hands + // out the slot it was given back last, so a handle made and closed around + // the refusal lands where it lands around nothing: one installed and + // closed between would have moved the slot's generation on further. + let made_and_closed = || { + // SAFETY: nothing relies on being the source's only holder, and no type holds the copy. + let copy = unsafe { syscall::dup(connector.as_handle().raw()) }.expect("a copy of the connector"); + unsafe { syscall::close(copy) }; + copy + }; + let (first, second) = (made_and_closed(), made_and_closed()); + let before_refusal = made_and_closed(); + assert_eq!(syscall::connect(connector.as_handle().raw()), Err(SyscallError::ResourceExhausted), "a connect past the depth"); + let after_refusal = made_and_closed(); + assert_eq!( + (after_refusal.slot(), after_refusal.generation().wrapping_sub(before_refusal.generation())), + (before_refusal.slot(), second.generation().wrapping_sub(first.generation())), + "a refused connect changed the caller's table", + ); + println!(" a refused connect left the caller's table as it was"); + // What the queued connections cost. Eagerly allocated, `depth` of them is // `depth * 4 MiB`; lazily, it is the queue entries and nothing else. The // ceiling is a quarter of the eager figure, which no amount of ordinary @@ -103,7 +116,7 @@ fn main() { // SAFETY: no type holds this number. unsafe { syscall::close(accepted) }; - let handle = syscall::namespace_open(ns.as_handle().raw(), NAME) + let handle = syscall::connect(connector.as_handle().raw()) .expect("an open must succeed once the server drains one"); held.push(handle); diff --git a/tests/guest/rust-tests/src/bin/abuse_handle_table.rs b/tests/guest/rust-tests/src/bin/abuse_handle_table.rs index df5295657bf..d2991238b8d 100644 --- a/tests/guest/rust-tests/src/bin/abuse_handle_table.rs +++ b/tests/guest/rust-tests/src/bin/abuse_handle_table.rs @@ -20,10 +20,11 @@ //! checked before the spawn's endowments move: at the cap the spawn is //! refused with every handle it named still the caller's. //! -//! **An endowment vector is two entries and two labels short of a table**, -//! which the kernel's own `self` and `quit` fill. So a caller's entry labelled -//! either is refused before anything moves, and so is a vector of one entry -//! more than `MAX_SPAWN_ENDOWMENTS`, while that many start. +//! **A child's table starts in the order the ABI states**: the kernel's own +//! two, then the endowments as the spawn listed them, the `i`th at +//! `RawHandle::endowed(i)`, which is all a child finds them by. A vector of +//! one entry more than `MAX_SPAWN_ENDOWMENTS` is refused before anything +//! moves, while that many start. //! //! **A pipe and a port are two inserts answered as one**: at the cap, and one //! slot short of fitting, each is refused with the table as it was. @@ -31,9 +32,7 @@ #[path = "../image.rs"] mod image; -use toyos_abi::syscall::{ - self, SpawnEndow, VmAt, VmProt, SpawnArgs, SyscallError, MAX_SPAWN_ENDOWMENTS, QUIT_LABEL, SELF_LABEL, -}; +use toyos_abi::syscall::{self, VmAt, VmProt, SpawnArgs, SyscallError, MAX_SPAWN_ENDOWMENTS}; use toyos_abi::handle::TransferEntry; use toyos_abi::RawHandle; use toyos_abi::end::End; @@ -41,7 +40,8 @@ use toyos_abi::end::End; const SELF: &str = "/system/bin/test_rs_abuse_handle_table"; /// The name its children go by. const NAME: &str = "test_rs_abuse_handle_table"; -/// The role a spawned copy of this binary takes: it exits at once. +/// The role a spawned copy of this binary takes: it writes its place's +/// digit into each of the pipe ends it was endowed, and exits. const CHILD: &str = "endowed"; @@ -51,11 +51,6 @@ const MAX_HANDLES: u32 = 4096; const REGION: usize = 4 * 1024 * 1024; -/// The label blob the endowment arms index into. Its content is never read — -/// the vector is refused before anything is installed — but the offsets have to -/// be in range or the refusal would be the label check rather than the repeat. -const LABELS: &[u8] = b"twice"; - /// This binary laid out to start as [`CHILD`], read before the table is /// filled: a full table has no slot for the objects a read and a startup /// block make. @@ -64,14 +59,20 @@ fn own() -> &'static image::Laid { static OWN: std::sync::OnceLock<(toyos::process::Image, image::Laid)> = std::sync::OnceLock::new(); let (_, laid) = OWN.get_or_init(|| { let image = image::image_of(SELF); - let laid = image::laid(&image, &format!("{SELF}\0{CHILD}")); + let laid = image::laid(&image, &format!("{SELF}\0{CHILD}\0{PROMISED}")); (image, laid) }); laid } fn main() { - if std::env::args().nth(1).as_deref() == Some(CHILD) { + if let Some(count) = std::env::args().nth(2).filter(|_| std::env::args().nth(1).as_deref() == Some(CHILD)) { + for i in 0..count.parse::().expect("a count of endowments") { + // The `i`th endowment is the `i`th handle after the kernel's own, + // whatever the child's own SDK makes of its table. + let digit = [b'0' + i as u8]; + assert_eq!(syscall::write(RawHandle::endowed(i), &digit), Ok(1), "endowment {i} is no pipe's write end"); + } return; } own(); @@ -94,8 +95,6 @@ fn main() { name_len: NAME.len() as u64, endow_ptr: (region as u64) + (REGION / 4) as u64, endow_count, - labels_ptr: (region as u64) + (REGION / 8) as u64, - labels_len: LABELS.len() as u64, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -103,6 +102,7 @@ fn main() { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(toyos_abi::HANDLE_INVALID.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }; unsafe { syscall::spawn(&args) } @@ -112,22 +112,17 @@ fn main() { // preflight runs against a table nothing has been removed from, so a repeat // passed both times; the first removal retired the slot and the second // answered `Stale` into an `expect`. - let endow = unsafe { region.add(REGION / 4) } as *mut SpawnEndow; - let entry = SpawnEndow { label_off: 0, label_len: LABELS.len() as u32, moved: TransferEntry::unchanged(stdout()) }; + let endow = unsafe { region.add(REGION / 4) } as *mut TransferEntry; + let entry = TransferEntry::unchanged(stdout()); unsafe { endow.write(entry); endow.add(1).write(entry); - core::ptr::copy_nonoverlapping( - LABELS.as_ptr(), - region.add(REGION / 8), - LABELS.len(), - ); } let err = spawn_with(2).expect_err("an endowment vector naming one handle twice must be rejected"); assert_eq!(err, SyscallError::InvalidArgument, "wrong error for a repeated endowment"); - a_caller_endows_no_self(); - a_spawn_carries_two_entries_fewer_than_a_table(); + a_child_finds_each_endowment_at_its_place(); + a_spawn_carries_its_bound(); let mut filled = Vec::new(); let mut refused = None; @@ -148,8 +143,7 @@ fn main() { a_pair_is_refused_whole(&mut filled); let last = filled.pop().expect("the fill installed a handle"); - let entry = SpawnEndow { label_off: 0, label_len: LABELS.len() as u32, moved: TransferEntry::unchanged(last) }; - let spawned = spawn_endowed(&[entry], LABELS); + let spawned = spawn_endowed(&[TransferEntry::unchanged(last)]); // The cap is a live limit, not a latched failure. for handle in filled { @@ -200,9 +194,8 @@ fn a_pair_is_refused_whole(filled: &mut Vec) { ); } -/// `SYS_SPAWN` of this binary as a child that exits at once, carrying -/// `entries` into the blob `labels`. -fn spawn_endowed(entries: &[SpawnEndow], labels: &[u8]) -> Result { +/// `SYS_SPAWN` of this binary as [`CHILD`], carrying `entries`. +fn spawn_endowed(entries: &[TransferEntry]) -> Result { let laid = own(); // SAFETY: every pointer names a live local for the whole call. unsafe { @@ -213,8 +206,6 @@ fn spawn_endowed(entries: &[SpawnEndow], labels: &[u8]) -> Result Result = (0..PROMISED).map(|_| syscall::pipe().expect("a pipe for the place arm")).collect(); + let entries: Vec = pipes.iter().map(|ends| TransferEntry::unchanged(ends.write)).collect(); + let child = spawn_endowed(&entries).unwrap_or_else(|e| panic!("a spawn carrying {PROMISED} pipe ends was refused: {e:?}")); + assert_eq!(syscall::process_wait(child), Ok(End::Exited(0)), "the child that writes its endowments"); + for (i, ends) in pipes.iter().enumerate() { let mut byte = [0u8; 1]; - assert_eq!(syscall::read(ends.read, &mut byte), Ok(1), "the byte did not arrive"); + assert_eq!(syscall::read(ends.read, &mut byte), Ok(1), "endowment {i} carried nothing"); + assert_eq!(byte[0], b'0' + i as u8, "endowment {i} was found at another place"); // SAFETY: no type holds this number. unsafe { syscall::close(ends.read) }; - // SAFETY: no type holds this number. - unsafe { syscall::close(ends.write) }; } + // SAFETY: no type holds this number. + unsafe { syscall::close(child) }; } /// One entry more than `MAX_SPAWN_ENDOWMENTS`, each naming a distinct /// `TRANSFER` handle, so the count is the only refusal: refused, and one fewer -/// starts. -fn a_spawn_carries_two_entries_fewer_than_a_table() { +/// starts, the first [`PROMISED`] pipe write ends. +fn a_spawn_carries_its_bound() { let over = MAX_SPAWN_ENDOWMENTS + 1; let mut handles = Vec::with_capacity(over + 1); while handles.len() < over { let ends = syscall::pipe().expect("a pipe for the count arm"); - handles.extend([ends.read, ends.write]); + handles.extend([ends.write, ends.read]); } - let mut labels = Vec::new(); - let entries: Vec = handles - .iter() - .enumerate() - .map(|(i, &handle)| { - let label = format!("e{i:02}"); - let entry = - SpawnEndow { label_off: labels.len() as u32, label_len: label.len() as u32, moved: TransferEntry::unchanged(handle) }; - labels.extend_from_slice(label.as_bytes()); - entry - }) - .collect(); - let entries = &entries[..over]; - let err = spawn_endowed(entries, &labels).expect_err("a spawn carrying an entry past MAX_SPAWN_ENDOWMENTS must be refused"); + let entries: Vec = handles[..over].iter().map(|&handle| TransferEntry::unchanged(handle)).collect(); + let err = spawn_endowed(&entries).expect_err("a spawn carrying an entry past MAX_SPAWN_ENDOWMENTS must be refused"); assert_eq!(err, SyscallError::InvalidArgument, "wrong error for {over} entries"); + // The child writes into its first `PROMISED`: write ends, in that order. let fewer = MAX_SPAWN_ENDOWMENTS; - let used = entries[fewer].label_off as usize; - let child = spawn_endowed(&entries[..fewer], &labels[..used]) - .unwrap_or_else(|e| panic!("a spawn carrying {fewer} entries was refused: {e:?}")); + let ordered: Vec = handles.iter().step_by(2).chain(handles.iter().skip(1).step_by(2)).take(fewer).map(|&h| TransferEntry::unchanged(h)).collect(); + let child = spawn_endowed(&ordered).unwrap_or_else(|e| panic!("a spawn carrying {fewer} entries was refused: {e:?}")); assert_eq!(syscall::process_wait(child), Ok(End::Exited(0)), "the child carrying {fewer} entries"); // SAFETY: no type holds this number. unsafe { syscall::close(child) }; - for &handle in &handles[fewer..] { - // SAFETY: no type holds this number. - unsafe { syscall::close(handle) }; + for &handle in &handles { + if !ordered.iter().any(|moved| moved.handle == handle) { + // SAFETY: no type holds this number. + unsafe { syscall::close(handle) }; + } } - println!("an endowment labelled self or quit and {over} entries are refused, and {fewer} start"); + println!("{over} entries are refused, {fewer} start, and each lands at the place the ABI states"); } /// The kernel handle this process's standard output is, descriptor 1's. diff --git a/tests/guest/rust-tests/src/bin/abuse_listener_hijack.rs b/tests/guest/rust-tests/src/bin/abuse_listener_hijack.rs index dd81314da71..dc0e29968c2 100644 --- a/tests/guest/rust-tests/src/bin/abuse_listener_hijack.rs +++ b/tests/guest/rust-tests/src/bin/abuse_listener_hijack.rs @@ -12,44 +12,40 @@ //! //! **The whole setup is gone.** There is no registry, no `listen` and no name a //! process can present: a service is a port, its two ends are two types, and a -//! client is given a `Connector` inside a namespace its parent built. So the +//! client is given a `Connector` by its parent. So the //! squat is not something to detect at run time — it is not something that can //! be written — and what is left to check is the boundary the type system draws //! and the kernel enforces underneath it. //! -//! Four arms, each the runtime half of a thing the compiler already refuses to -//! spell. **They no longer answer the same way, and the split is the design.** -//! An attenuated handle is a thing a process may legitimately probe, so arms 1, -//! 2 and 4 come back as words; naming a handle from somebody else's table is a +//! Three arms, each the runtime half of a thing the compiler already refuses to +//! spell. **They do not answer the same way, and the split is the design.** +//! An attenuated handle is a thing a process may legitimately probe, so arms 1 +//! and 2 come back as words; naming a handle from somebody else's table is a //! bug no correct program has, so arm 3 ends the caller and is raised in a child //! that prints a marker first. //! //! 1. **A connector cannot accept.** `Connector` has no `accept` method, and //! the handle behind one carries no `READ` — so `SYS_ACCEPT` refuses it with //! a word before it ever looks at the type. -//! 2. **An acceptor cannot be put in a namespace as a connector.** Two types, +//! 2. **An acceptor cannot be connected through as a connector.** Two types, //! one wire word, and the kernel checks the type rather than trusting it. -//! **It answers `InvalidArgument` rather than ending the caller, and it is -//! the only wrong-typed handle in the ABI that does**: an `add` entry's -//! connector is routinely one a *peer* transferred — a `provides` name is -//! exactly that — so presenting the wrong one may be reporting a peer's bug -//! rather than your own. the supervisor's launcher is why. +//! **It answers `InvalidArgument` rather than ending the caller**, as a +//! spawn's place and job do: a connector is routinely one a *peer* +//! transferred — a `provides` name is exactly that — so presenting the +//! wrong one may be reporting a peer's bug rather than your own. //! 3. **A handle number from another process's table names nothing here.** The //! victim prints the raw number of a live acceptor of its own; the thief //! presenting it is ended, and the victim's port is still serving afterwards. -//! 4. **A name a namespace does not carry resolves to nothing** — and that is -//! `NotFound`, which is a different word from a port that has closed. use std::io::{BufRead, BufReader, Read, Write}; use std::process::{Command, Stdio}; -use toyos::{namespace, port, AsHandle}; +use toyos::{port, AsHandle}; use toyos_abi::syscall::{self, SyscallError}; use std::os::toyos::process::ExitStatusExt; use toyos_abi::end::{End, Fault}; const SELF_PATH: &str = "/system/bin/test_rs_abuse_listener_hijack"; -const NAME: &str = "abuse-listener-hijack"; /// How the kernel ends a process that names a handle it does not hold. const HANDLE_FAULT: End = End::Fault(Fault::Handle); @@ -76,30 +72,20 @@ fn test() { "a connector accepted a connection" ); - // 2. And the server's end is not a ticket to hand out: an acceptor in an - // `add` entry is a wrong-typed handle, so nothing can build a namespace - // whose entry hands the *acceptor* to whoever holds it. A word rather - // than a death — see the header — so it is raised here and needs no - // child and no marker. + // 2. And the server's end is not a ticket to hand out: an acceptor is a + // wrong-typed handle to connect through, so nothing handed one as a + // connector reaches the port by it. A word rather than a death — see + // the header — so it is raised here and needs no child and no marker. let (smuggled, its_connector) = port::create().expect("a second port, to smuggle"); - // SAFETY: read for its number and nothing else. `ManuallyDrop` is what - // stops it being closed as a second owner, and it does not outlive - // `smuggled`. - let fake = core::mem::ManuallyDrop::new(unsafe { - port::Connector::from_raw(smuggled.as_handle().raw()) - }); assert_eq!( - namespace::build().add(NAME, &fake).finish().err(), + syscall::connect(smuggled.as_handle().raw()).err(), Some(SyscallError::InvalidArgument), - "an acceptor was taken as a namespace's connector", + "an acceptor was connected through as a connector", ); - // The *same port's* connector is taken, so the refusal was the type and + // The *same port's* connector connects, so the refusal was the type and // not the port — which is what stops this arm passing against a - // `SYS_NAMESPACE_BUILD` that refuses everything. - namespace::build() - .add(NAME, &its_connector) - .finish() - .expect("the same port's connector is a namespace entry"); + // `SYS_CONNECT` that refuses everything. + its_connector.connect().expect("the same port's connector connects"); drop(smuggled); // 3. A handle is an index into one process's own table and means nothing @@ -131,17 +117,6 @@ fn test() { "the victim's port did not survive the number being presented elsewhere", ); - // 4. A name is resolved in a namespace this process holds, and in no other - // place. One that is not in it is `NotFound` — a fact about this - // process — and not `Gone`, which is a server that has left. - let ns = namespace::build().add(NAME, &connector).finish().expect("a namespace of our own"); - assert!(ns.open(NAME).is_ok(), "our own port did not answer"); - assert_eq!( - ns.open("something-we-were-not-given").err(), - Some(SyscallError::NotFound), - "a name outside the namespace resolved" - ); - println!("a connector cannot accept, an acceptor cannot be a connector, and a handle is one process's"); } @@ -193,11 +168,7 @@ fn victim() -> ! { // Nobody else ever reached this port, and it is still the victim's to // accept from: the number it published named something live for the whole // of the attack. - let ns = namespace::build() - .add(NAME, &connector) - .finish() - .expect("victim: a namespace over its own port"); - let _client = ns.open(NAME).expect("victim: connect to its own port"); + let _client = connector.connect().expect("victim: connect to its own port"); let _served = acceptor.accept().expect("victim: accept its own connection"); println!("port still mine"); std::io::stdout().flush().expect("victim: flush the report"); diff --git a/tests/guest/rust-tests/src/bin/abuse_page_straddle.rs b/tests/guest/rust-tests/src/bin/abuse_page_straddle.rs index 8e7e44898da..f8ead833ba9 100644 --- a/tests/guest/rust-tests/src/bin/abuse_page_straddle.rs +++ b/tests/guest/rust-tests/src/bin/abuse_page_straddle.rs @@ -99,8 +99,6 @@ fn main() { name_len: NAME.len() as u64, endow_ptr: 0, endow_count: 0, - labels_ptr: 0, - labels_len: 0, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -108,6 +106,7 @@ fn main() { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(toyos_abi::HANDLE_INVALID.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }; let placed = (boundary - 8) as *mut SpawnArgs; diff --git a/tests/guest/rust-tests/src/bin/abuse_shared_grant.rs b/tests/guest/rust-tests/src/bin/abuse_shared_grant.rs index db55db80a54..cd8c21d5155 100644 --- a/tests/guest/rust-tests/src/bin/abuse_shared_grant.rs +++ b/tests/guest/rust-tests/src/bin/abuse_shared_grant.rs @@ -29,8 +29,9 @@ use std::os::toyos::process::CommandExt; use std::process::{Child, Command, Stdio}; use toyos::shm::SharedMemory; -use toyos::{namespace, port, AsHandle}; -use toyos_abi::syscall::{self, SVC_LABEL}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::{port, AsHandle}; +use toyos_abi::syscall; use toyos_abi::RawHandle; use std::os::toyos::process::ExitStatusExt; use toyos_abi::end::{End, Fault}; @@ -64,13 +65,10 @@ fn owner() { // The peer that is *given* the region. It holds one connector and this is // where it points; it never names a number, because being sent the handle // is the whole of how it gets there. - let ns = namespace::build() - .add(SERVICE, &connector) - .finish() - .expect("the kernel refused a namespace"); let mut invited = Command::new(SELF_PATH) .arg("invited") - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdout(Stdio::piped()) .spawn() .expect("spawn the invited peer"); diff --git a/tests/guest/rust-tests/src/bin/abuse_spawn_maps.rs b/tests/guest/rust-tests/src/bin/abuse_spawn_maps.rs index d4420ae7628..2b5b1d577a3 100644 --- a/tests/guest/rust-tests/src/bin/abuse_spawn_maps.rs +++ b/tests/guest/rust-tests/src/bin/abuse_spawn_maps.rs @@ -30,8 +30,6 @@ fn spawn(maps: &[SpawnMap], entry: u64, image_base: u64) -> Result Result adopter(), + Some("helper") => helper(), + Some(other) => panic!("unknown role {other:?}"), + None => test(), + } +} + +fn test() { + let system = toyos::endow::connector(SYSTEM) + .expect("a test is endowed /system") + .try_clone_to_owned() + .expect("a copy of /system's capability"); + let status = Command::new(SELF) + .arg("adopter") + .services_only(&[]) + .endow(GIVEN, system.into()) + .status() + .expect("spawn the adopter"); + assert!(status.success(), "the adopter failed: {status:?}"); + println!("adopted_services_stay: a helper of a process that adopted its services holds none of them"); +} + +fn adopter() { + assert_eq!(toyos::endow::services().count(), 0, "the adopter was endowed a service"); + let system: Connector = Endowments::get().take(GIVEN).expect("the adopter is given /system"); + // SAFETY: a connector, as this process was endowed it. + unsafe { std::os::toyos::fs::adopt_services(vec![(SYSTEM.to_string(), system.into())]) }.expect("the adopter adopts /system"); + // Non-vacuity: the adopter holds it, and its spawn reads the helper's image through it. + assert_eq!(toyos::endow::services().collect::>(), [SYSTEM], "what the adopter holds"); + let status = Command::new(SELF).arg("helper").status().expect("spawn the helper through the adopted /system"); + assert_eq!(status.code(), Some(HELD_NONE), "the helper held a service: {status:?}"); +} + +fn helper() { + let held: Vec<&str> = toyos::endow::services().collect(); + assert!(held.is_empty(), "a helper of an adopter holds {held:?}"); + std::process::exit(HELD_NONE); +} diff --git a/tests/guest/rust-tests/src/bin/an_export_shares_its_descriptions.rs b/tests/guest/rust-tests/src/bin/an_export_shares_its_descriptions.rs index e6bef60de5b..ab9b5e63ab8 100644 --- a/tests/guest/rust-tests/src/bin/an_export_shares_its_descriptions.rs +++ b/tests/guest/rust-tests/src/bin/an_export_shares_its_descriptions.rs @@ -22,8 +22,7 @@ const STREAMED: &str = "an-export-shares-its-descriptions-streamed"; const STREAM_BYTES: usize = 1 << 20; fn main() { - let names = toyos::endow::namespace().expect("a test is endowed a namespace"); - let mut parent = Dir::connect(names, DIR).expect("the home directory"); + let mut parent = Dir::named(DIR).expect("the home directory"); let fid = parent.open(Base::Root, FILE, O_READ | O_WRITE | O_CREATE | O_TRUNCATE).expect("open").fid; let written = parent.write(fid, b"abcdef").expect("write"); assert_eq!((written.len, written.offset), (6, 6)); diff --git a/tests/guest/rust-tests/src/bin/app_view.rs b/tests/guest/rust-tests/src/bin/app_view.rs index a99e4b934e6..5e9addcba71 100644 --- a/tests/guest/rust-tests/src/bin/app_view.rs +++ b/tests/guest/rust-tests/src/bin/app_view.rs @@ -198,8 +198,7 @@ fn app() { other => red.push(format!("std's write into its own package was answered {other:?}")), } - let names = toyos::endow::namespace().expect("an app is endowed a namespace"); - match Dir::connect(names, &format!("fs:{PACKAGE}")) { + match Dir::named(&format!("fs:{PACKAGE}")) { Err(e) => red.push(format!("fs:{PACKAGE} would not connect: {e:?}")), Ok(mut dir) => { if dir.writable() { @@ -233,7 +232,7 @@ fn app() { each("symlink", dir.symlink("manifest.toml", (Base::Root, "link"))); } } - match Dir::connect(names, &format!("fs:{HOME}")) { + match Dir::named(&format!("fs:{HOME}")) { Ok(dir) if dir.writable() => {} Ok(_) => red.push(format!("fs:{HOME} says it is read-only")), Err(e) => red.push(format!("fs:{HOME} would not connect: {e:?}")), @@ -251,7 +250,7 @@ fn app() { "fs:/log", "fs:/boot", ] { - match names.open(held) { + match toyos::endow::service(held) { Err(_) => {} Ok(_) => red.push(format!("it holds {held}")), } diff --git a/tests/guest/rust-tests/src/bin/bar_map_again.rs b/tests/guest/rust-tests/src/bin/bar_map_again.rs index 0b58c086e51..d2ada668c53 100644 --- a/tests/guest/rust-tests/src/bin/bar_map_again.rs +++ b/tests/guest/rust-tests/src/bin/bar_map_again.rs @@ -20,9 +20,10 @@ use toyos::endow::Endowments; use toyos::ipc::FrameRx; use toyos::shm::SharedMemory; use toyos::syscap::SysCap; -use toyos::{namespace, port, AsHandle, OwnedHandle, PciDev}; +use toyos::{port, AsHandle, OwnedHandle, PciDev}; use toyos_abi::handle::RawHandle; -use toyos_abi::syscall::{self, PciId, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{self, PciId, SyscallError}; +use toyos::endow::SYSCAP_LABEL; const NIC: PciId = PciId { vendor: 0x1af4, device: 0x1041 }; @@ -165,8 +166,7 @@ fn main() { // is this process again, maps the BAR afresh. let kept = syscall::device_bar_map(nic.as_handle().raw(), bar).expect("bar_map_again: the BAR, asked for"); let (acceptor, connector) = port::create().expect("bar_map_again: a port"); - let names = namespace::build().add("conn", &connector).finish().expect("bar_map_again: a namespace"); - let client = names.open("conn").expect("bar_map_again: a connection"); + let client = connector.connect().expect("bar_map_again: a connection"); let server = acceptor.accept().expect("bar_map_again: the connection accepted"); let claim = nic.as_handle().raw(); // The claim's handle goes with the frame: nothing may close it here. diff --git a/tests/guest/rust-tests/src/bin/connect_before_serve.rs b/tests/guest/rust-tests/src/bin/connect_before_serve.rs index cddec4b97f5..8ffe9fe4db5 100644 --- a/tests/guest/rust-tests/src/bin/connect_before_serve.rs +++ b/tests/guest/rust-tests/src/bin/connect_before_serve.rs @@ -32,8 +32,9 @@ use std::process::{Child, ChildStdout, Command, Stdio}; use toyos::endow::Endowments; use toyos::port::Acceptor; -use toyos::{namespace, port, AsHandle}; -use toyos_abi::syscall::{self, SyscallError, SVC_LABEL}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::{port, AsHandle}; +use toyos_abi::syscall::{self, SyscallError}; const SELF_PATH: &str = "/system/bin/test_rs_connect_before_serve"; const SERVICE: &str = "before-serve"; @@ -119,11 +120,12 @@ fn left() { /// The `sent` line is the ordering: it is printed after the write, so nothing /// below it can be reading a port the client had not yet written to. fn start_client(connector: &port::Connector, expect: &str) -> (Child, BufReader) { - let ns = namespace::build().add(SERVICE, connector).finish().expect("a namespace"); + let connector = connector.duplicate().expect("a copy of the connector"); let mut client = Command::new(SELF_PATH) .arg("client") .arg(expect) - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdout(Stdio::piped()) .spawn() .expect("spawn the client"); diff --git a/tests/guest/rust-tests/src/bin/cpal_drop_unreleased.rs b/tests/guest/rust-tests/src/bin/cpal_drop_unreleased.rs index 80f98cf4cb1..ea865fbe8b3 100644 --- a/tests/guest/rust-tests/src/bin/cpal_drop_unreleased.rs +++ b/tests/guest/rust-tests/src/bin/cpal_drop_unreleased.rs @@ -25,9 +25,9 @@ use toyos::audio::{ }; use toyos::ipc::IpcError; use toyos::shm::SharedMemory; -use toyos::{namespace, port}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::port; use toyos_abi::audio::AudioSlotHeader; -use toyos_abi::syscall::SVC_LABEL; const SELF: &str = "/system/bin/test_rs_cpal_drop_unreleased"; @@ -65,13 +65,11 @@ fn main() { fn serve() { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build() - .add("soundserver", &connector) - .finish() - .expect("a namespace naming this binary soundserver"); + // The client is given this binary as its soundserver, and no other. let mut child = Command::new(SELF) .arg("client") - .endow(SVC_LABEL, names.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, "soundserver"), connector.into()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/tests/guest/rust-tests/src/bin/endowment_denied.rs b/tests/guest/rust-tests/src/bin/endowment_denied.rs index 32f1a6db5d9..8d778c4871c 100644 --- a/tests/guest/rust-tests/src/bin/endowment_denied.rs +++ b/tests/guest/rust-tests/src/bin/endowment_denied.rs @@ -5,16 +5,16 @@ //! binary holds what `test-runner` holds, because the guest binaries are not //! `[programs]` keys and no manifest row can name what any of them needs. //! This binary is where least -//! authority *is* asserted, and it works because it builds its own namespaces -//! and spawns itself: nothing here depends on what test-runner handed over. +//! authority *is* asserted, and it works because it gives its own children +//! their services: nothing here depends on what test-runner handed over. //! //! Two halves. //! -//! **Names.** One child is endowed a namespace carrying `echo` alone and another -//! the same namespace carrying `echo` and `privileged`. The first must resolve -//! `echo` and answer `NotFound` for `privileged`; the second must resolve both. -//! The second arm is the whole of what stops the first passing because the -//! service was never there. +//! **Names.** One child is given the service `echo` alone and another `echo` +//! and `privileged`. The first must connect to `echo` and answer `NotEndowed` +//! for `privileged`; the second must connect to both. The second arm is the +//! whole of what stops the first passing because the service was never there. +//! A connector without `WRITE` connects nowhere. //! //! **Capabilities.** Five things are reachable no other way — minting a device //! claim, entering the real-time band, turning a pid into a process handle, @@ -64,15 +64,12 @@ use std::os::toyos::process::CommandExt; use std::process::{Command, Stdio}; use std::sync::OnceLock; -use toyos::endow::{Endowments, SYSCAP_LABEL}; +use toyos::endow::{service_label, Endowments, SVC_PREFIX, SYSCAP_LABEL}; use toyos::syscap::SysCap; use toyos::system::{SYSINFO_ENTRY_SIZE, SYSINFO_HEADER_SIZE}; -use toyos::{namespace, port, AsHandle}; +use toyos::{port, AsHandle}; use toyos_abi::handle::{Rights, HANDLE_INVALID}; -use toyos_abi::syscall::{ - self, DeviceRequest, DeviceType, NamespaceBuild, SyscallError, NAMESPACE_FLAGS_KNOWN, - NAMESPACE_KEEP_ALL, SVC_LABEL, -}; +use toyos_abi::syscall::{self, DeviceRequest, DeviceType, SyscallError}; use toyos_abi::RawHandle; use std::os::toyos::process::ExitStatusExt; use toyos_abi::end::{End, Fault}; @@ -81,10 +78,6 @@ const SELF_PATH: &str = "/system/bin/test_rs_endowment_denied"; const OPEN: &str = "echo"; const PRIVILEGED: &str = "privileged"; -/// A `NamespaceBuild` flags bit nothing defines; the assert keeps it that way. -const UNDEFINED_FLAG: u32 = 1 << 31; -const _: () = assert!(UNDEFINED_FLAG & NAMESPACE_FLAGS_KNOWN == 0); - /// A buffer with room for one roster entry: the smallest question that costs /// `Rights::ROSTER`, and one byte more than the largest that does not. const ONE_ENTRY: usize = SYSINFO_HEADER_SIZE + SYSINFO_ENTRY_SIZE; @@ -164,107 +157,58 @@ fn main() { fn test() { only_what_was_given(); - the_base_plus_one_more_name(); + a_connector_without_write_connects_nowhere(); a_right_the_capability_lacks_is_a_word(); the_roster_is_a_right_and_the_header_is_not(); every_applet_holds_only_what_its_policy_names(); for (role, what_would_be_wrong) in NOT_A_HANDLE { killed(role, what_would_be_wrong); } - println!("a name outside the namespace resolves to nothing, and a capability is its bits"); + println!("a service not given resolves to nothing, and a capability is its bits"); } -/// The two namespaces, and the one difference between them. +/// The two children, and the one difference between them. fn only_what_was_given() { let (_open_acceptor, open) = port::create().expect("a port for the open service"); let (_priv_acceptor, privileged) = port::create().expect("a port for the privileged service"); - let narrow = namespace::build().add(OPEN, &open).finish().expect("the narrow namespace"); + let copy = |connector: &port::Connector| connector.duplicate().expect("a copy of a connector"); assert_eq!( - probe_with(narrow), - format!("{OPEN}=ok {PRIVILEGED}=NotFound"), - "a child endowed one name reached the other", + probe_with(vec![(OPEN, copy(&open))]), + format!("{OPEN}=ok {PRIVILEGED}=NotEndowed"), + "a child given one service reached the other", ); - - let wide = namespace::build() - .add(OPEN, &open) - .add(PRIVILEGED, &privileged) - .finish() - .expect("the wide namespace"); assert_eq!( - probe_with(wide), + probe_with(vec![(OPEN, copy(&open)), (PRIVILEGED, privileged)]), format!("{OPEN}=ok {PRIVILEGED}=ok"), - "a child endowed both names could not reach one of them", + "a child given both services could not reach one of them", ); println!(" names: the narrow child reached one, the wide child reached both"); } -/// Inheritance plus one more name, which only a flags bit can spell: no syscall -/// enumerates a namespace. The refusal arm is what stops the first from being a -/// bit the kernel merely tolerated. -fn the_base_plus_one_more_name() { - let (_open_acceptor, open) = port::create().expect("a port for the open service"); - let (_priv_acceptor, privileged) = port::create().expect("a port for the privileged service"); - - let base = namespace::build().add(OPEN, &open).finish().expect("the base namespace"); - // `keep` spells the same base by naming it, so the two spellings must reach - // the same child. - let listed = namespace::build() - .keep(&base, &[OPEN]) - .add(PRIVILEGED, &privileged) - .finish() - .expect("the base's one name listed, plus one more"); - let by_name = probe_with(listed); - let merged = namespace::build() - .keep_all(&base) - .add(PRIVILEGED, &privileged) - .finish() - .expect("the whole base, plus one more name"); +/// A connector carrying no `WRITE` connects nowhere, which is a word: a +/// connector is routinely one a peer sent. The same port's connector +/// carrying it connects, so what was refused is the right. +fn a_connector_without_write_connects_nowhere() { + let (_acceptor, connector) = port::create().expect("a port"); + let unwritable = connector.as_handle().try_clone_narrowed(Rights::DUP.union(Rights::TRANSFER)).expect("a connector without WRITE"); assert_eq!( - probe_with(merged), - by_name, - "keep_all and a keep list over the same base reached different children", - ); - assert_eq!( - by_name, - format!("{OPEN}=ok {PRIVILEGED}=ok"), - "a child given the base plus one name could not reach both", - ); - - let undefined = NamespaceBuild { - base: base.as_handle().raw(), - flags: NAMESPACE_KEEP_ALL | UNDEFINED_FLAG, - keep_ptr: 0, - keep_n: 0, - add_ptr: 0, - add_n: 0, - names_ptr: 0, - names_len: 0, - }; - // SAFETY: every length is zero, so no pointer above is read. - let refused = unsafe { syscall::namespace_build(&undefined) }; - assert_eq!( - refused.err(), - Some(SyscallError::InvalidArgument), - "a build carrying an undefined flags bit was not refused", + syscall::connect(unwritable.as_handle().raw()).err(), + Some(SyscallError::PermissionDenied), + "a connector without WRITE connected", ); - - // The same request with the bit cleared, so what was refused is the bit. - let defined = NamespaceBuild { flags: NAMESPACE_KEEP_ALL, ..undefined }; - // SAFETY: as above. - let built = unsafe { syscall::namespace_build(&defined) }.expect("the request without the bit"); - // SAFETY: no type holds this number. - unsafe { syscall::close(built) }; - println!(" keep_all: the whole base carried over, and an undefined flags bit was refused"); + connector.connect().expect("the same port's connector, carrying WRITE, connects"); + println!(" connect: a connector without WRITE was refused, and one with it connected"); } -fn probe_with(ns: namespace::Namespace) -> String { - let mut child = Command::new(SELF_PATH) - .arg("probe") - .endow(SVC_LABEL, ns.into()) - .stdout(Stdio::piped()) - .spawn() - .expect("spawn the probe"); +/// A probe holding `services` and no other. +fn probe_with(services: Vec<(&str, port::Connector)>) -> String { + let mut command = Command::new(SELF_PATH); + command.arg("probe").services_only(&[]).stdout(Stdio::piped()); + for (name, connector) in services { + command.endow(&service_label(SVC_PREFIX, name), connector.into()); + } + let mut child = command.spawn().expect("spawn the probe"); let mut out = BufReader::new(child.stdout.take().expect("probe stdout")); let mut line = String::new(); out.read_line(&mut line).expect("the probe's report"); @@ -559,10 +503,9 @@ fn killed(role: &str, what_would_be_wrong: &str) { } fn probe() { - let answer = |name: &str| match toyos::endow::namespace().map(|ns| ns.open(name)) { - Some(Ok(_)) => "ok".to_string(), - Some(Err(e)) => format!("{e:?}"), - None => "no namespace".to_string(), + let answer = |name: &str| match toyos::endow::service(name) { + Ok(_) => "ok".to_string(), + Err(e) => format!("{e:?}"), }; println!("{OPEN}={} {PRIVILEGED}={}", answer(OPEN), answer(PRIVILEGED)); std::io::stdout().flush().expect("probe: flush"); diff --git a/tests/guest/rust-tests/src/bin/ends_sent_over_each_other_both_end.rs b/tests/guest/rust-tests/src/bin/ends_sent_over_each_other_both_end.rs index e275284f4e8..e55133e3e20 100644 --- a/tests/guest/rust-tests/src/bin/ends_sent_over_each_other_both_end.rs +++ b/tests/guest/rust-tests/src/bin/ends_sent_over_each_other_both_end.rs @@ -5,7 +5,7 @@ use toyos::ipc::{Connection, FrameRx, RxStep}; use toyos::poller::{Poller, READABLE}; -use toyos::{namespace, port, AsHandle, OwnedHandle}; +use toyos::{port, AsHandle, OwnedHandle}; use toyos_abi::syscall; /// A failure ceiling only: an end that never comes is the defect. @@ -13,9 +13,8 @@ const CEILING_NANOS: u64 = 10_000_000_000; fn main() { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build().add("conn", &connector).finish().expect("a namespace"); let pair = || { - let client = names.open("conn").expect("connect"); + let client = connector.connect().expect("connect"); (client, acceptor.accept().expect("accept")) }; diff --git a/tests/guest/rust-tests/src/bin/handle_lifetime.rs b/tests/guest/rust-tests/src/bin/handle_lifetime.rs index bb533f600d6..2ff9c738b9d 100644 --- a/tests/guest/rust-tests/src/bin/handle_lifetime.rs +++ b/tests/guest/rust-tests/src/bin/handle_lifetime.rs @@ -8,18 +8,18 @@ use std::os::toyos::process::{CommandExt, ExitStatusExt}; use std::process::{Child, ChildStdout, Command, Stdio}; use toyos::census::Census; -use toyos::{namespace, port, AsHandle}; +use toyos::{port, AsHandle}; use toyos_abi::end::{End, Fault}; use toyos_abi::handle::ObjectKind; use toyos_abi::inbox::RingLayout; -use toyos_abi::syscall::{self, SyscallError, SERVE_PREFIX}; +use toyos_abi::syscall::{self, SyscallError}; +use toyos::endow::SERVE_PREFIX; #[path = "../census_wait.rs"] mod census_wait; const SELF_PATH: &str = "/system/bin/test_rs_handle_lifetime"; -/// The name this test's own namespaces map to the port under test. Private to -/// this process and its children, which is the whole of what a namespace is. +/// What this test endows its holder the acceptor under, as `serve:`. const SERVICE: &str = "handle-lifetime-service"; const PATH: &str = "/tmp/handle-lifetime.txt"; const KILLED_PATH: &str = "/tmp/handle-lifetime-killed.txt"; @@ -69,7 +69,6 @@ fn file_survives_one_close() { fn acceptor_survives_one_close() { let (acceptor, connector) = port::create().expect("a port of our own"); - let ns = namespace::build().add(SERVICE, &connector).finish().expect("a namespace for it"); let a = acceptor.into_raw(); // SAFETY: nothing relies on being the source's only holder. @@ -79,20 +78,19 @@ fn acceptor_survives_one_close() { // The survivor still serves: a client's connection is queued on the port // and this handle takes it. - let client = syscall::namespace_open(ns.as_handle().raw(), SERVICE) - .expect("open through the connector of a live port"); + let client = syscall::connect(connector.as_handle().raw()).expect("connect through the connector of a live port"); let accepted = syscall::accept(b).expect("accept on the surviving acceptor handle"); // SAFETY: no type holds this number. unsafe { syscall::close(accepted) }; // SAFETY: no type holds this number. unsafe { syscall::close(client) }; - // And the last close is what closes the port. `Gone` and not `NotFound`: - // the name is still in the namespace, and it is the server that has left. + // And the last close is what closes the port: `Gone`, the server that + // has left, through a connector this process still holds. // SAFETY: no type holds this number. unsafe { syscall::close(b) }; assert_eq!( - syscall::namespace_open(ns.as_handle().raw(), SERVICE).err(), + syscall::connect(connector.as_handle().raw()).err(), Some(SyscallError::Gone), "the last close of an acceptor did not close the port" ); @@ -145,9 +143,8 @@ fn ring_survives_one_close() { /// client's side while the holder is killed. fn kill_releases_acceptor() { let (acceptor, connector) = port::create().expect("a port for the holder"); - let ns = namespace::build().add(SERVICE, &connector).finish().expect("a namespace for it"); assert!( - syscall::namespace_open(ns.as_handle().raw(), SERVICE).is_ok(), + connector.connect().is_ok(), "the port was not live before its holder was killed" ); @@ -155,7 +152,7 @@ fn kill_releases_acceptor() { kill_and_reap(&mut child); assert_eq!( - syscall::namespace_open(ns.as_handle().raw(), SERVICE).err(), + connector.connect().err(), Some(SyscallError::Gone), "a killed process did not give its acceptor back" ); diff --git a/tests/guest/rust-tests/src/bin/hierarchy_paths.rs b/tests/guest/rust-tests/src/bin/hierarchy_paths.rs index 9efffdf4caa..441c6fe2cd3 100644 --- a/tests/guest/rust-tests/src/bin/hierarchy_paths.rs +++ b/tests/guest/rust-tests/src/bin/hierarchy_paths.rs @@ -5,8 +5,9 @@ use std::os::toyos::process::CommandExt; use std::process::Command; use toyos::files::{self, At, Open}; -use toyos::{endow, namespace, port}; -use toyos_abi::syscall::{FileType, SVC_LABEL}; +use toyos::endow::{self, service_label, SVC_PREFIX}; +use toyos::port; +use toyos_abi::syscall::FileType; const SELF: &str = "/system/bin/test_rs_hierarchy_paths"; const PROBE: &str = "/tmp/hierarchy_paths_probe"; @@ -20,14 +21,21 @@ fn names(dir: &str) -> Vec { out } +/// The services this process holds, sorted. +fn services() -> Vec<&'static str> { + let mut held: Vec<_> = endow::services().collect(); + held.sort(); + held +} + fn main() { match std::env::args().nth(1).as_deref() { Some("empty") => { - assert!(endow::namespace().expect("namespace").names().unwrap().is_empty()); + assert!(services().is_empty()); assert!(fs::metadata("/").expect("empty root").is_dir()); assert!(names("/").is_empty()); std::env::set_current_dir("/").expect("cwd in empty root"); - assert!(fs::read(PROBE).is_err(), "an empty namespace reached /tmp"); + assert!(fs::read(PROBE).is_err(), "a process given no service reached /tmp"); } Some("narrow") => narrowed(), Some(other) => panic!("unknown role {other}"), @@ -36,14 +44,14 @@ fn main() { // The unserved connector supplies an ancestor, without granting any // global filesystem access or requiring a filesystem implementation. let (_acceptor, connector) = port::create().expect("port"); - let narrow = namespace::build() - .keep(endow::namespace().expect("namespace"), &["fs:/tmp"]) - .add("fs:/nested/leaf", &connector) - .finish().expect("narrow namespace"); - for (role, names) in [("narrow", narrow), ("empty", namespace::build().finish().unwrap())] { - assert!(Command::new(SELF).arg(role).endow(SVC_LABEL, names.into()) - .status().expect("spawn namespace probe").success(), "{role} child failed"); - } + let narrow = Command::new(SELF) + .arg("narrow") + .services_only(&["fs:/tmp"]) + .endow(&service_label(SVC_PREFIX, "fs:/nested/leaf"), connector.into()) + .status(); + assert!(narrow.expect("spawn the narrowed probe").success(), "narrow child failed"); + let empty = Command::new(SELF).arg("empty").services_only(&[]).status(); + assert!(empty.expect("spawn the empty probe").success(), "empty child failed"); fs::remove_file(PROBE).expect("remove probe"); println!("all hierarchy tests passed"); } @@ -51,7 +59,7 @@ fn main() { } fn narrowed() { - assert_eq!(endow::namespace().expect("namespace").names().unwrap(), ["fs:/nested/leaf", "fs:/tmp"]); + assert_eq!(services(), ["fs:/nested/leaf", "fs:/tmp"]); assert_eq!(names("/"), ["nested", "tmp"]); assert_eq!(names("/nested"), ["leaf"]); assert!(fs::metadata("/nested").expect("synthetic ancestor").is_dir()); diff --git a/tests/guest/rust-tests/src/bin/i219_msi.rs b/tests/guest/rust-tests/src/bin/i219_msi.rs index 7e9a102995a..03d5901c7bb 100644 --- a/tests/guest/rust-tests/src/bin/i219_msi.rs +++ b/tests/guest/rust-tests/src/bin/i219_msi.rs @@ -19,7 +19,8 @@ use toyos::poller::{Poller, READABLE}; use toyos::syscap::SysCap; use toyos::volatile::Window; use toyos::PciDev; -use toyos_abi::syscall::{PciId, RegWidth, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{PciId, RegWidth, SyscallError}; +use toyos::endow::SYSCAP_LABEL; use toyos_device_memory::Registers; use toyos_i219::regs::{self, cause, ctrl_ext}; use toyos_i219::{Clock, Delivery, Interrupts, Part, I219}; diff --git a/tests/guest/rust-tests/src/bin/isa_claim_refused.rs b/tests/guest/rust-tests/src/bin/isa_claim_refused.rs index 3113fc81abc..dbdf764198e 100644 --- a/tests/guest/rust-tests/src/bin/isa_claim_refused.rs +++ b/tests/guest/rust-tests/src/bin/isa_claim_refused.rs @@ -7,7 +7,8 @@ use toyos::endow::Endowments; use toyos::syscap::SysCap; use toyos::Device; -use toyos_abi::syscall::{IsaId, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{IsaId, SyscallError}; +use toyos::endow::SYSCAP_LABEL; fn main() { let cap: SysCap = Endowments::get() diff --git a/tests/guest/rust-tests/src/bin/isa_grant.rs b/tests/guest/rust-tests/src/bin/isa_grant.rs index 764793a9dfd..c5533a8980d 100644 --- a/tests/guest/rust-tests/src/bin/isa_grant.rs +++ b/tests/guest/rust-tests/src/bin/isa_grant.rs @@ -20,7 +20,8 @@ use toyos::poller::{Poller, READABLE}; use toyos::syscap::SysCap; use toyos::Device; use toyos_abi::pci::DeviceIrqRecord; -use toyos_abi::syscall::{IsaId, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{IsaId, SyscallError}; +use toyos::endow::SYSCAP_LABEL; use toyos::ioport; diff --git a/tests/guest/rust-tests/src/bin/isa_lines.rs b/tests/guest/rust-tests/src/bin/isa_lines.rs index d8b3fd2a06d..e2619c63120 100644 --- a/tests/guest/rust-tests/src/bin/isa_lines.rs +++ b/tests/guest/rust-tests/src/bin/isa_lines.rs @@ -23,7 +23,8 @@ use toyos::poller::{Poller, READABLE}; use toyos::syscap::SysCap; use toyos::{AsHandle, Device}; use toyos_abi::pci::DeviceIrqRecord; -use toyos_abi::syscall::{self, IsaId, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{self, IsaId, SyscallError}; +use toyos::endow::SYSCAP_LABEL; use toyos::ioport; diff --git a/tests/guest/rust-tests/src/bin/job_fairness.rs b/tests/guest/rust-tests/src/bin/job_fairness.rs index 0befe23776c..9bc6f092e12 100644 --- a/tests/guest/rust-tests/src/bin/job_fairness.rs +++ b/tests/guest/rust-tests/src/bin/job_fairness.rs @@ -23,7 +23,6 @@ use std::thread; use std::time::Duration; use toyos::endow::{Endowments, SYSCAP_LABEL}; -use toyos::job::JOB_LABEL; use toyos::syscap::SysCap; use toyos_abi::syscall::{self, ProcessStats}; use toyos_abi::RawHandle; @@ -67,11 +66,10 @@ fn run() { let job = cap.new_job().expect("the estate's capability makes a job"); let children = (0..helpers) .map(|_| { - let copy = job.duplicate().expect("a second handle to the job"); Command::new(ME) .args(["spin", &threads.to_string()]) .stdin(Stdio::piped()) - .endow(JOB_LABEL, copy.into()) + .job(toyos::AsHandle::as_handle(&job).into()) .spawn() .expect("spawn a helper into its job") }) diff --git a/tests/guest/rust-tests/src/bin/launch_unsent_stdio.rs b/tests/guest/rust-tests/src/bin/launch_unsent_stdio.rs index b6a8a0be40d..3fa6d3d4920 100644 --- a/tests/guest/rust-tests/src/bin/launch_unsent_stdio.rs +++ b/tests/guest/rust-tests/src/bin/launch_unsent_stdio.rs @@ -29,6 +29,8 @@ fn launched(program: &str, argv: &[u8], fds: &[(u32, BorrowedHandle<'_>, Kind)]) cwd: "/", fds, endow: Vec::new(), + inherit: toyos::spawn::Inherit::All, + job: None, provided: Vec::new(), parent: Parent::Caller, independent: true, diff --git a/tests/guest/rust-tests/src/bin/lent_device_memory_refused.rs b/tests/guest/rust-tests/src/bin/lent_device_memory_refused.rs index 6b079cb291f..54df2732326 100644 --- a/tests/guest/rust-tests/src/bin/lent_device_memory_refused.rs +++ b/tests/guest/rust-tests/src/bin/lent_device_memory_refused.rs @@ -13,7 +13,8 @@ use toyos::fs::WINDOW_BYTES; use toyos::shm::{Lent, SharedMemory}; use toyos::syscap::SysCap; use toyos::{AsHandle, PciDev}; -use toyos_abi::syscall::{self, PciId, SYSCAP_LABEL}; +use toyos_abi::syscall::{self, PciId}; +use toyos::endow::SYSCAP_LABEL; const NIC: PciId = PciId { vendor: 0x1af4, device: 0x1041 }; diff --git a/tests/guest/rust-tests/src/bin/libc_recv_beside_close.rs b/tests/guest/rust-tests/src/bin/libc_recv_beside_close.rs index 075d974a3a0..2d6c68076cd 100644 --- a/tests/guest/rust-tests/src/bin/libc_recv_beside_close.rs +++ b/tests/guest/rust-tests/src/bin/libc_recv_beside_close.rs @@ -35,9 +35,9 @@ use toyos::net::stream::{Server, Words, LAYOUT, RECEIVE_RING, REGION_BYTES}; use toyos::wake::{self, Hungup}; use toyos::net::{MsgType, RespType, TcpConnectRequest, TcpConnectResponse}; use toyos::shm::SharedMemory; -use toyos::{namespace, port, OwnedHandle}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::{port, OwnedHandle}; use std::os::toyos::process::CommandExt; -use toyos_abi::syscall::SVC_LABEL; const SELF_PATH: &str = "/system/bin/test_rs_libc_recv_beside_close"; @@ -115,10 +115,10 @@ fn main() { fn test(role: &str) { let (acceptor, connector) = port::create().expect("a port"); - let ns = namespace::build().add(SERVICE, &connector).finish().expect("a namespace"); let mut child = Command::new(SELF_PATH) .arg(role) - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/tests/guest/rust-tests/src/bin/memory_floor.rs b/tests/guest/rust-tests/src/bin/memory_floor.rs index 1178cf848d2..5fc43f759a9 100644 --- a/tests/guest/rust-tests/src/bin/memory_floor.rs +++ b/tests/guest/rust-tests/src/bin/memory_floor.rs @@ -35,7 +35,6 @@ use std::time::{Duration, Instant}; use toyos::endow::{self, Endowments, SYSCAP_LABEL}; use toyos::ipc::Connection; -use toyos::job::JOB_LABEL; use toyos::poller::{Poller, READABLE}; use toyos::power::{self, Refused}; use toyos::syscap::SysCap; @@ -108,7 +107,7 @@ fn control(input: bool) { .arg("greedy") .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .endow(JOB_LABEL, job.duplicate().expect("a second handle to its job").into()) + .job(toyos::AsHandle::as_handle(&job).into()) .spawn() .expect("spawn the greedy job"); let mut said = BufReader::new(greedy.stdout.take().expect("its stdout")); @@ -126,7 +125,7 @@ fn control(input: bool) { let other = cap.new_job().expect("a job is a few bytes this job's floor holds"); let refused = Command::new(ME) .arg("idle") - .endow(JOB_LABEL, other.into()) + .job(toyos::AsHandle::as_handle(&other).into()) .spawn() .map(|_| ()); assert!( @@ -198,12 +197,11 @@ fn directions_are_the_clients(cap: &SysCap) { .arg("serve") .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .endow(JOB_LABEL, job.duplicate().expect("a second handle to its job").into()) + .job(toyos::AsHandle::as_handle(&job).into()) .endow("acceptor", acceptor.into()) .spawn() .expect("spawn the server"); - let names = toyos::namespace::build().add("served", &connector).finish().expect("a namespace of the port"); - let conn = names.open("served").expect("connect to the server"); + let conn = connector.connect().expect("connect to the server"); let mut said = BufReader::new(server.stdout.take().expect("its stdout")); let mut line = String::new(); said.read_line(&mut line).expect("the server's line"); diff --git a/tests/guest/rust-tests/src/bin/messages_are_whole.rs b/tests/guest/rust-tests/src/bin/messages_are_whole.rs index c42589d611c..bd74bf3b395 100644 --- a/tests/guest/rust-tests/src/bin/messages_are_whole.rs +++ b/tests/guest/rust-tests/src/bin/messages_are_whole.rs @@ -18,7 +18,7 @@ use toyos::ipc::{Connection, FrameRx, WrongKind}; use toyos::poller::OTHER_END_GONE; -use toyos::{namespace, pipe_pair, port, AsHandle, OwnedHandle, Pipe}; +use toyos::{pipe_pair, port, AsHandle, OwnedHandle, Pipe}; use toyos_abi::syscall::{ self, Received, SyscallError, MAX_MESSAGE_LEN, MAX_QUEUED_HANDLES, MAX_QUEUED_MESSAGES, MAX_TRANSFER_HANDLES, PIPE_CAPACITY, @@ -30,9 +30,8 @@ const CEILING_NANOS: u64 = 10_000_000_000; fn main() { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build().add("conn", &connector).finish().expect("a namespace"); let pair = || { - let client = names.open("conn").expect("connect"); + let client = connector.connect().expect("connect"); (client, acceptor.accept().expect("accept")) }; diff --git a/tests/guest/rust-tests/src/bin/moved_rights.rs b/tests/guest/rust-tests/src/bin/moved_rights.rs index 1401aac48db..8de47e33f02 100644 --- a/tests/guest/rust-tests/src/bin/moved_rights.rs +++ b/tests/guest/rust-tests/src/bin/moved_rights.rs @@ -19,17 +19,15 @@ mod image; use toyos::ipc::Connection; -use toyos::{namespace, port, AsHandle}; +use toyos::{port, AsHandle}; use toyos_abi::end::End; use toyos_abi::handle::{ReceivedEntry, Rights, TransferEntry}; -use toyos_abi::syscall::{self, SpawnArgs, SpawnEndow, SyscallError, MAX_TRANSFER_HANDLES}; +use toyos_abi::syscall::{self, SpawnArgs, SyscallError, MAX_TRANSFER_HANDLES}; use toyos_abi::RawHandle; const SELF: &str = "/system/bin/test_rs_moved_rights"; /// The name the child goes by. const NAME: &str = "moved_rights"; -/// The label a child finds its endowment under. -const LABEL: &str = "moved"; /// The role a spawned copy takes: its exit code is what it found its /// endowment to carry ([`code`]). const CHILD: &str = "endowed"; @@ -39,8 +37,8 @@ const NO_RIGHTS: u32 = 1 << 30; fn main() { if std::env::args().nth(1).as_deref() == Some(CHILD) { - let endowed = toyos::endow::Endowments::get().take::(LABEL).expect("the endowment"); - std::process::exit(code(carries(&Pair::new(), endowed.into_raw()))); + // A raw spawn names nothing: the endowment is the first handle after the kernel's own. + std::process::exit(code(carries(&Pair::new(), RawHandle::endowed(0)))); } // Each arm is judged by itself, so one red names every claim that broke. let arms: [(&str, fn(&Pair)); 5] = [ @@ -70,8 +68,7 @@ struct Pair { impl Pair { fn new() -> Self { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build().add("conn", &connector).finish().expect("a namespace"); - let client = names.open("conn").expect("connect"); + let client = connector.connect().expect("connect"); Self { client, server: acceptor.accept().expect("accept") } } @@ -166,11 +163,10 @@ fn a_refused_send_gives_back_the_rights_it_had(pair: &Pair) { close(&[read]); } -/// `SYS_SPAWN` of this binary as [`CHILD`], endowed `moved` under [`LABEL`]. +/// `SYS_SPAWN` of this binary as [`CHILD`], endowed `moved`. fn spawn(moved: TransferEntry) -> Result { static OWN: std::sync::OnceLock = std::sync::OnceLock::new(); let laid = image::laid(OWN.get_or_init(|| image::image_of(SELF)), &format!("{SELF}\0{CHILD}")); - let entry = SpawnEndow { label_off: 0, label_len: LABEL.len() as u32, moved }; // SAFETY: every pointer names a live local for the whole call. unsafe { syscall::spawn(&SpawnArgs { @@ -178,10 +174,8 @@ fn spawn(moved: TransferEntry) -> Result { path_len: SELF.len() as u64, name_ptr: NAME.as_ptr() as u64, name_len: NAME.len() as u64, - endow_ptr: &entry as *const SpawnEndow as u64, + endow_ptr: &moved as *const TransferEntry as u64, endow_count: 1, - labels_ptr: LABEL.as_ptr() as u64, - labels_len: LABEL.len() as u64, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -189,6 +183,7 @@ fn spawn(moved: TransferEntry) -> Result { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(toyos_abi::HANDLE_INVALID.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }) } diff --git a/tests/guest/rust-tests/src/bin/msix_in_place.rs b/tests/guest/rust-tests/src/bin/msix_in_place.rs index 1f5265d0981..ab762bd6cd4 100644 --- a/tests/guest/rust-tests/src/bin/msix_in_place.rs +++ b/tests/guest/rust-tests/src/bin/msix_in_place.rs @@ -15,7 +15,8 @@ mod function; use toyos::endow::Endowments; use toyos::syscap::SysCap; use toyos::PciDev; -use toyos_abi::syscall::{PciId, RegWidth, SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::{PciId, RegWidth, SyscallError}; +use toyos::endow::SYSCAP_LABEL; use toyos_pci::caps::CapWalk; use toyos_pci::msix; diff --git a/tests/guest/rust-tests/src/bin/mutual_kill.rs b/tests/guest/rust-tests/src/bin/mutual_kill.rs index 5e9d433cea6..370ba0789e5 100644 --- a/tests/guest/rust-tests/src/bin/mutual_kill.rs +++ b/tests/guest/rust-tests/src/bin/mutual_kill.rs @@ -15,15 +15,16 @@ use std::sync::atomic::{AtomicU32, Ordering}; use toyos::ipc::Connection; use toyos::process::Process; use toyos::shm::SharedMemory; -use toyos::{endow, namespace, port, OwnedHandle}; -use toyos_abi::syscall::{self, SVC_LABEL}; +use toyos::endow::{self, service_label, SVC_PREFIX}; +use toyos::{port, OwnedHandle}; +use toyos_abi::syscall; use toyos_abi::RawHandle; use std::os::toyos::process::ExitStatusExt; use toyos_abi::end::End; const SELF_PATH: &str = "/system/bin/test_rs_mutual_kill"; -/// The name the child's namespace carries the connector under. +/// The service the child is given the connector as. const SERVICE: &str = "mutual"; const ROUNDS: usize = 64; @@ -79,13 +80,10 @@ fn test() { /// Spawn a killer holding a connector for a fresh port, and accept it. fn killer_child() -> (Connection, Child) { let (acceptor, connector) = port::create().expect("a port of our own"); - let ns = namespace::build() - .add(SERVICE, &connector) - .finish() - .expect("a namespace carrying one connector"); let child = Command::new(SELF_PATH) .arg("killer") - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdout(Stdio::piped()) .spawn() .expect("spawn a killer"); @@ -117,8 +115,7 @@ fn word(go: &SharedMemory) -> &AtomicU32 { } fn killer() -> ! { - let ns = endow::namespace().expect("the parent endowed a namespace"); - let conn = ns.open(SERVICE).expect("connect through the endowed connector"); + let conn = endow::service(SERVICE).expect("connect through the endowed connector"); let mut rx = toyos::ipc::FrameRx::<{ toyos::ipc::MAX_FRAME_LEN as usize }>::new(); let frame = rx.recv(&conn).expect("the arming frame"); assert_eq!(frame.msg_type, ARM, "an unexpected frame"); diff --git a/tests/guest/rust-tests/src/bin/netstack_gone_mid_bind.rs b/tests/guest/rust-tests/src/bin/netstack_gone_mid_bind.rs index a5b8137f726..1cd67f05087 100644 --- a/tests/guest/rust-tests/src/bin/netstack_gone_mid_bind.rs +++ b/tests/guest/rust-tests/src/bin/netstack_gone_mid_bind.rs @@ -19,7 +19,7 @@ //! Three arms, and they are three different syscalls answering the same fact: //! //! 1. **Gone before the connect.** The port closes first, so -//! `SYS_NAMESPACE_OPEN` refuses — `SyscallError::Gone`. This is the arm that +//! `SYS_CONNECT` refuses — `SyscallError::Gone`. This is the arm that //! already worked, and it is asserted through `std::net::TcpListener` as //! well as through the SDK, because `ErrorKind::NotConnected` is the literal //! thing `/system/bin/sshserver` matches on. @@ -51,10 +51,10 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener}; use std::os::toyos::process::CommandExt; use std::process::{Child, ChildStdout, Command, Stdio}; -use toyos::endow::EndowError; +use toyos::endow::{service_label, EndowError, SVC_PREFIX}; use toyos::net::{BoundResponse, MsgType, NetError, NetstackConn, TcpConnectRequest, TcpListenRequest, TcpOptions}; -use toyos::{namespace, port, AsHandle}; -use toyos_abi::syscall::{self, SyscallError, SVC_LABEL}; +use toyos::{port, AsHandle}; +use toyos_abi::syscall::{self, SyscallError}; const SELF_PATH: &str = "/system/bin/test_rs_netstack_gone_mid_bind"; @@ -83,7 +83,7 @@ fn test() { } /// Arm 1. The port is closed before the child exists, so its very first -/// `SYS_NAMESPACE_OPEN` is refused. +/// `SYS_CONNECT` is refused. fn arm_already_gone() { let (acceptor, connector) = port::create().expect("a port"); // The whole of a server leaving: there is no unregister and nothing to tell @@ -120,12 +120,13 @@ fn arm_mid_flight(role: &str, what: &str) { println!(" mid-flight: {what} says the peer is gone"); } -/// Spawn `role` with a namespace whose only name is this test's own port. +/// Spawn `role` holding one service, this test's own port. fn spawn(connector: &port::Connector, role: &str) -> (Child, BufReader) { - let ns = namespace::build().add(SERVICE, connector).finish().expect("a namespace"); + let connector = connector.duplicate().expect("a copy of the connector"); let mut child = Command::new(SELF_PATH) .arg(role) - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdout(Stdio::piped()) .spawn() .unwrap_or_else(|e| panic!("spawn {role}: {e}")); diff --git a/tests/guest/rust-tests/src/bin/pci_reclaim.rs b/tests/guest/rust-tests/src/bin/pci_reclaim.rs index b26b8394c53..55eaf05c5c1 100644 --- a/tests/guest/rust-tests/src/bin/pci_reclaim.rs +++ b/tests/guest/rust-tests/src/bin/pci_reclaim.rs @@ -8,7 +8,8 @@ mod function; use toyos::endow::Endowments; use toyos::syscap::SysCap; use toyos::PciDev; -use toyos_abi::syscall::{PciId, SYSCAP_LABEL}; +use toyos_abi::syscall::PciId; +use toyos::endow::SYSCAP_LABEL; const I219: PciId = PciId { vendor: 0x8086, device: 0x15fc }; diff --git a/tests/guest/rust-tests/src/bin/pci_undeclared.rs b/tests/guest/rust-tests/src/bin/pci_undeclared.rs index c96665e1652..11d390efdd2 100644 --- a/tests/guest/rust-tests/src/bin/pci_undeclared.rs +++ b/tests/guest/rust-tests/src/bin/pci_undeclared.rs @@ -7,7 +7,8 @@ use toyos::syscap::SysCap; use toyos::Device; use toyos_abi::devset::Declaration; use toyos_abi::inventory::{Pci, RawRecord}; -use toyos_abi::syscall::{SyscallError, SYSCAP_LABEL}; +use toyos_abi::syscall::SyscallError; +use toyos::endow::SYSCAP_LABEL; /// What it says once every claim was refused. const REFUSED: &str = "pci_undeclared: the set says the declaration is pending, and each edu's claim was refused at once: WouldBlock"; diff --git a/tests/guest/rust-tests/src/bin/pipe_other_end_gone.rs b/tests/guest/rust-tests/src/bin/pipe_other_end_gone.rs index eb3f180a4fa..69b4556a763 100644 --- a/tests/guest/rust-tests/src/bin/pipe_other_end_gone.rs +++ b/tests/guest/rust-tests/src/bin/pipe_other_end_gone.rs @@ -31,7 +31,7 @@ use toyos::endow::{Endowments, SYSCAP_LABEL}; use toyos::ipc::Connection; use toyos::poller::{Poller, OTHER_END_GONE, READABLE}; use toyos::syscap::SysCap; -use toyos::{namespace, pipe_pair, port, AsHandle, BorrowedHandle, OwnedHandle, Pipe}; +use toyos::{pipe_pair, port, AsHandle, BorrowedHandle, OwnedHandle, Pipe}; use toyos_abi::syscall::SyscallError; #[path = "../roster.rs"] @@ -42,7 +42,6 @@ const TOKEN: u64 = 7; /// What the holding child is endowed the write end as. It never takes it: its /// table holds it. const HELD_LABEL: &str = "held"; -const SERVICE: &str = "queue"; fn main() { match std::env::args().nth(1).as_deref() { @@ -159,8 +158,7 @@ fn pipe(unread: &str) -> (Pipe, Pipe) { /// Both ends of one connection, made through a port of this process's own. fn connection() -> (Connection, Connection) { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build().add(SERVICE, &connector).finish().expect("a namespace"); - let client = names.open(SERVICE).expect("a connection to the port"); + let client = connector.connect().expect("a connection to the port"); let server = acceptor.accept().expect("the connection's other end"); (client, server) } diff --git a/tests/guest/rust-tests/src/bin/poll_belongs_to_its_handle.rs b/tests/guest/rust-tests/src/bin/poll_belongs_to_its_handle.rs index 9fdf3c9e7c3..17b9fcb38a8 100644 --- a/tests/guest/rust-tests/src/bin/poll_belongs_to_its_handle.rs +++ b/tests/guest/rust-tests/src/bin/poll_belongs_to_its_handle.rs @@ -27,7 +27,7 @@ use toyos::endow::{Endowments, SYSCAP_LABEL}; use toyos::ipc::Connection; use toyos::poller::{Poller, READABLE}; use toyos::syscap::SysCap; -use toyos::{namespace, port, AsHandle, OwnedHandle}; +use toyos::{port, AsHandle, OwnedHandle}; use toyos_abi::inbox::{RingHeader, Submission, OP_WATCH, SUBMISSIONS_OFF, SUBMISSION_RING_OFF}; use toyos_abi::syscall::{self, SyscallError}; use toyos_abi::RawHandle; @@ -126,8 +126,7 @@ fn a_child_watched_through_a_duplicate_is_answered_at_its_close() { fn a_watched_handle_sent_away_is_answered() { let (acceptor, connector) = port::create().expect("a port"); - let names = namespace::build().add("conn", &connector).finish().expect("a namespace"); - let client: Connection = names.open("conn").expect("a connection"); + let client: Connection = connector.connect().expect("a connection"); let server = acceptor.accept().expect("its other end"); let (read, _write) = toyos::pipe_pair().expect("a pipe no byte enters"); let poller = Poller::new(1); diff --git a/tests/guest/rust-tests/src/bin/port_badge.rs b/tests/guest/rust-tests/src/bin/port_badge.rs index 701e779f73f..f87ac95a577 100644 --- a/tests/guest/rust-tests/src/bin/port_badge.rs +++ b/tests/guest/rust-tests/src/bin/port_badge.rs @@ -15,7 +15,6 @@ use core::sync::atomic::Ordering; use toyos::ipc::Connection; -use toyos::namespace; use toyos::port::{self, Acceptor, Connector}; use toyos::AsHandle; use toyos_abi::handle::Rights; @@ -25,8 +24,6 @@ use toyos_abi::inbox::{ use toyos_abi::syscall::{self, SyscallError, MAX_BADGE}; use toyos_abi::RawHandle; -const NAME: &str = "port"; - fn main() { let (acceptor, _) = port::create().expect("a port"); let (other, _) = port::create().expect("a second port"); @@ -46,8 +43,7 @@ fn main() { Err(SyscallError::PermissionDenied), "another port's acceptor read this port's stamp" ); - let names = namespace::build().add(NAME, &minted).finish().expect("a namespace"); - let _client = names.open(NAME).expect("connect"); + let _client = minted.connect().expect("connect"); let server = accept_through_inbox(&acceptor); let len = syscall::port_badge(acceptor.as_handle().raw(), server, &mut out); assert_eq!(len.map(|len| &out[..len]), Ok(badge), "an inbox's accept lost the stamp"); @@ -119,8 +115,7 @@ fn accept_through_inbox(acceptor: &Acceptor) -> RawHandle { /// A connection through `connector`, and its end accepted on `acceptor`. fn connect(acceptor: &Acceptor, connector: &Connector) -> (Connection, Connection) { - let names = namespace::build().add(NAME, connector).finish().expect("a namespace"); - let client = names.open(NAME).expect("connect"); + let client = connector.connect().expect("connect"); let server = acceptor.accept().expect("accept"); (client, server) } diff --git a/tests/guest/rust-tests/src/bin/process_tree.rs b/tests/guest/rust-tests/src/bin/process_tree.rs index 1a927416f54..ca5245bec38 100644 --- a/tests/guest/rust-tests/src/bin/process_tree.rs +++ b/tests/guest/rust-tests/src/bin/process_tree.rs @@ -2,7 +2,7 @@ //! //! **Every end takes the whole subtree.** A starts B; B spawns C, launches D //! through the supervisor and asks the supervisor for E; B hands A a handle to each and a copy of -//! its own `self`. Then B ends — killed by A, and by a CPU fault, one B per +//! its handle to itself. Then B ends — killed by A, and by a CPU fault, one B per //! arm — and the arm asserts, once A's wait on B answers: //! //! - C and D have ended, as killed; @@ -33,12 +33,12 @@ use std::io::{BufRead, BufReader}; use std::os::toyos::process::CommandExt; use std::process::{Child, ChildStdin, Command, Stdio}; -use toyos::endow::{self, Endowments, SVC_LABEL, SYSCAP_LABEL}; +use toyos::endow::{self, service_label, Endowments, SVC_PREFIX, SYSCAP_LABEL}; use toyos::ipc::Connection; use toyos::launch::{self, Launch, Outcome, Parent, LAUNCHER}; use toyos::process::Process; use toyos::syscap::SysCap; -use toyos::{namespace, port, AsHandle, OwnedHandle}; +use toyos::{port, AsHandle, OwnedHandle}; use toyos_abi::handle::Rights; use toyos_abi::syscall::{self, SpawnArgs, SyscallError}; use toyos_abi::RawHandle; @@ -55,13 +55,13 @@ const INVALID_PATH: &str = "/tmp/process_tree_invalid_elf"; const HELD: &str = "/system/bin/cat"; const SHELL: &str = "/system/bin/shell"; -/// The name B's namespace carries the port back to A under. +/// The service B is given the port back to A as. const BACK: &str = "back"; /// The label a chain's link is endowed this program's image under. const LINK_IMAGE: &str = "link-image"; -/// B to A: the handles to C, D, B's own `self` and E, in that order. +/// B to A: the handles to C, D, B itself and E, in that order. const MSG_GROWN: u32 = 1; /// A to B: fault. const MSG_FAULT: u32 = 2; @@ -136,19 +136,13 @@ struct Grown { _held: ChildStdin, } -/// Start B, holding a port back to A beside this process's own namespace, and +/// Start B, holding a port back to A beside this process's own services, and /// take what it sends once its subtree is grown. fn grow() -> Grown { let (acceptor, connector) = port::create().expect("a port of our own"); - let own = endow::namespace().expect("test-runner endows its namespace"); - let ns = namespace::build() - .keep_all(own) - .add(BACK, &connector) - .finish() - .expect("a namespace with the port back beside ours"); let mut b = Command::new(SELF_PATH) .arg("b") - .endow(SVC_LABEL, ns.into()) + .endow(&service_label(SVC_PREFIX, BACK), connector.into()) .endow(LAUNCHER, launcher_copy().into()) .stdin(Stdio::piped()) .spawn() @@ -226,7 +220,7 @@ fn under_b_is_gone(grown: &Grown) { fn launcher() -> Connection { endow::launcher() .expect("test-runner endows every job its launcher") - .open(LAUNCHER) + .connect() .expect("the launcher answers") } @@ -248,8 +242,6 @@ fn spawn_under(place: RawHandle) -> Result { name_len: NAME.len() as u64, endow_ptr: 0, endow_count: 0, - labels_ptr: 0, - labels_len: 0, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -257,6 +249,7 @@ fn spawn_under(place: RawHandle) -> Result { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(place.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }; // SAFETY: every pointer names a buffer of this frame that outlives the call. diff --git a/tests/guest/rust-tests/src/bin/quit_tree.rs b/tests/guest/rust-tests/src/bin/quit_tree.rs index c4fc72b169a..c1cb1c6a2f2 100644 --- a/tests/guest/rust-tests/src/bin/quit_tree.rs +++ b/tests/guest/rust-tests/src/bin/quit_tree.rs @@ -183,7 +183,7 @@ fn a_quit_needs_manage_and_an_ended_process_is_no_refusal() { } fn notice() -> &'static toyos::quit::Notice { - toyos::quit::notice().expect("every process is started holding its notice") + toyos::quit::notice() } /// Listen by `how`, start a listener `depth - 1` below, say so, then read one diff --git a/tests/guest/rust-tests/src/bin/sched_stress.rs b/tests/guest/rust-tests/src/bin/sched_stress.rs index f8f25632d1c..a02892eef4b 100644 --- a/tests/guest/rust-tests/src/bin/sched_stress.rs +++ b/tests/guest/rust-tests/src/bin/sched_stress.rs @@ -3,7 +3,7 @@ use std::thread; use std::time::Duration; use toyos::poller::{Poller, READABLE}; -use toyos::{namespace, port}; +use toyos::port; fn main() { test_acceptor_isolation_io_uring(); @@ -34,8 +34,7 @@ fn test_acceptor_isolation_io_uring() { b_poller.watch(&acc_b, READABLE, 0); // Open a connection through port A's connector only. - let ns = namespace::build().add("a", &con_a).finish().expect("a namespace naming port a"); - let client = ns.open("a").expect("open a"); + let client = con_a.connect().expect("open a"); drop(client); a.join().expect("port a's watcher panicked"); @@ -52,9 +51,7 @@ fn test_connect_storm() { let num_clients = 8; let (acceptor, connector) = port::create().expect("a port to storm"); - let ns = Arc::new( - namespace::build().add("storm", &connector).finish().expect("a namespace naming it"), - ); + let connector = Arc::new(connector); let server = thread::spawn(move || { for _ in 0..num_clients { @@ -66,9 +63,9 @@ fn test_connect_storm() { let mut clients = Vec::new(); for _ in 0..num_clients { - let ns = Arc::clone(&ns); + let connector = Arc::clone(&connector); clients.push(thread::spawn(move || { - drop(ns.open("storm").expect("open failed")); + drop(connector.connect().expect("open failed")); })); } diff --git a/tests/guest/rust-tests/src/bin/served_file_offset.rs b/tests/guest/rust-tests/src/bin/served_file_offset.rs index 47cc427a080..34da9e6d83f 100644 --- a/tests/guest/rust-tests/src/bin/served_file_offset.rs +++ b/tests/guest/rust-tests/src/bin/served_file_offset.rs @@ -122,7 +122,7 @@ fn refused_spawn_keeps_export(fd: i32) { let request = toyos::spawn::Spawn { program: "/system/bin/toybox", image: None, image_held: false, argv: b"toybox\0", env: &[], cwd: "/", fds: &fds, - endow: Vec::new(), provided: Vec::new(), parent: toyos::spawn::Parent::Place(write.as_handle()), + endow: Vec::new(), inherit: toyos::spawn::Inherit::All, job: None, provided: Vec::new(), parent: toyos::spawn::Parent::Place(write.as_handle()), independent: false, launcher: None, pager: None, }; let toyos::spawn::Start::Helper(helper) = request.start().expect("a helper") else { panic!("a helper became a launch") }; diff --git a/tests/guest/rust-tests/src/bin/session_lifecycle.rs b/tests/guest/rust-tests/src/bin/session_lifecycle.rs index 001c5e5c8bd..070016e3dc3 100644 --- a/tests/guest/rust-tests/src/bin/session_lifecycle.rs +++ b/tests/guest/rust-tests/src/bin/session_lifecycle.rs @@ -13,7 +13,7 @@ use std::time::{Duration, Instant}; use toyos::endow::{Endowments, SYSCAP_LABEL}; use toyos::ipc::{self, Connection, RxStep}; -use toyos::namespace::Namespace; +use toyos::port::Connector; use toyos::poller::{Poller, READABLE}; use toyos::power::{self, Mode, Pending, Stop, Update}; use toyos::syscap::SysCap; @@ -28,12 +28,12 @@ const OPENS: usize = 10; const CEILING: Duration = Duration::from_secs(60); fn main() { - let login = Endowments::get().take::(wire::LOGIN_PORT).expect("test-runner hands this job its `login`"); + let login = Endowments::get().take::(wire::LOGIN_PORT).expect("test-runner hands this job its `login`"); let (launcher, control) = wire::open(&login).expect("a session for this job"); let mut control = Control { conn: control, rx: ipc::FrameRx::new() }; // `logout` runs in a second session, which no sign-out here asks to quit. let (admin, _admin_control) = wire::open(&login).expect("a second session, for `logout`"); - let launched = |launcher: &Namespace, program: &str, args: &[&str]| { + let launched = |launcher: &Connector, program: &str, args: &[&str]| { Command::new(program) .independent() .launcher(launcher.as_handle().into()) @@ -116,7 +116,7 @@ fn main() { /// `hangupholder` detached in `launcher`'s session, once it listens for its /// quits: its pid. Until it listens a quit ends it, so a stop asked before /// that would find nothing to wait for. -fn detach(launcher: &Namespace) -> u32 { +fn detach(launcher: &Connector) -> u32 { let before = live(HOLDER); let mut holder = Command::new(format!("/system/bin/{HOLDER}")) .independent() diff --git a/tests/guest/rust-tests/src/bin/shm_release_reclaims.rs b/tests/guest/rust-tests/src/bin/shm_release_reclaims.rs index 0b2ecf04466..6a80dc88004 100644 --- a/tests/guest/rust-tests/src/bin/shm_release_reclaims.rs +++ b/tests/guest/rust-tests/src/bin/shm_release_reclaims.rs @@ -14,9 +14,9 @@ use std::process::{Command, Stdio}; use toyos::census::Census; use toyos::shm::SharedMemory; -use toyos::{namespace, port}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::port; use toyos_abi::handle::ObjectKind; -use toyos_abi::syscall::SVC_LABEL; #[path = "../census_wait.rs"] mod census_wait; @@ -66,13 +66,10 @@ fn main() { // its own handle before this process has mapped anything — nobody has the // region mapped at that moment, and it is still this process's. let (acceptor, connector) = port::create().expect("the kernel refused a port"); - let ns = namespace::build() - .add(SERVICE, &connector) - .finish() - .expect("the kernel refused a namespace"); let mut donor = Command::new(SELF_PATH) .arg("donor") - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/tests/guest/rust-tests/src/bin/spawn_child_ends_first.rs b/tests/guest/rust-tests/src/bin/spawn_child_ends_first.rs index 582d62ae4ba..d723a1faaac 100644 --- a/tests/guest/rust-tests/src/bin/spawn_child_ends_first.rs +++ b/tests/guest/rust-tests/src/bin/spawn_child_ends_first.rs @@ -17,7 +17,7 @@ #[path = "../image.rs"] mod image; -use toyos_abi::syscall::{self, debug_action, SpawnEndow, SpawnArgs, SyscallError}; +use toyos_abi::syscall::{self, debug_action, SpawnArgs, SyscallError}; use toyos_abi::handle::TransferEntry; use toyos_abi::RawHandle; use toyos_abi::end::End; @@ -44,8 +44,8 @@ fn main() { fn test() { // Laid out once, before the table is filled: an image and a startup block take handles. let own = image::image_of(SELF_PATH); - let laid = |role: &str| image::laid(&own, &format!("{SELF_PATH}\0{role}")); - let (exit, speak) = (laid("exit"), laid("speak")); + let laid = |role: &str, names: &[&str]| image::laid_naming(&own, &format!("{SELF_PATH}\0{role}"), names); + let (exit, speak) = (laid("exit", &[]), laid("speak", &[STDOUT])); hold_the_next_spawn(); let child = spawn(&exit, None).expect("a spawn whose child ended inside it"); assert_eq!( @@ -97,12 +97,13 @@ fn hold_the_next_spawn() { ); } -/// `SYS_SPAWN` of `laid`, this binary in one role, endowed `stdout` as its -/// descriptor 1, which moves only on a spawn that happens. +/// What a child laid out to speak calls its one endowment: its descriptor 1. +const STDOUT: &str = "fd:1"; + +/// `SYS_SPAWN` of `laid`, this binary in one role, endowed `stdout`, which +/// moves only on a spawn that happens. fn spawn(laid: &image::Laid, stdout: Option) -> Result { - const LABEL: &str = "fd:1"; - let endow: Vec = - stdout.into_iter().map(|handle| SpawnEndow { label_off: 0, label_len: LABEL.len() as u32, moved: TransferEntry::unchanged(handle) }).collect(); + let endow: Vec = stdout.into_iter().map(TransferEntry::unchanged).collect(); let args = SpawnArgs { path_ptr: SELF_PATH.as_ptr() as u64, path_len: SELF_PATH.len() as u64, @@ -110,8 +111,6 @@ fn spawn(laid: &image::Laid, stdout: Option) -> Result) -> Result (SharedMemory, Vec) { /// Spawn the program `bytes`, laid out from the object `image` names. fn spawn(image: RawHandle, bytes: &[u8]) -> Result { - let block = Block::new(format!("{SELF}\0{CHILD}").as_bytes(), b"", None).expect("a startup block"); + let block = Block::new(format!("{SELF}\0{CHILD}").as_bytes(), b"", &[], None).expect("a startup block"); let laid = lay_out(bytes, Machine::RUNNING, Source::Copied(u64::from(image.0))).expect("this program lays out").starting_with(&block); // SAFETY: every pointer names a live local for the whole call. unsafe { @@ -44,8 +44,6 @@ fn spawn(image: RawHandle, bytes: &[u8]) -> Result { name_len: NAME.len() as u64, endow_ptr: 0, endow_count: 0, - labels_ptr: 0, - labels_len: 0, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -53,6 +51,7 @@ fn spawn(image: RawHandle, bytes: &[u8]) -> Result { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(toyos_abi::HANDLE_INVALID.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }) } diff --git a/tests/guest/rust-tests/src/bin/spawn_lands_claimed.rs b/tests/guest/rust-tests/src/bin/spawn_lands_claimed.rs index 9f757b4ea88..163207690cb 100644 --- a/tests/guest/rust-tests/src/bin/spawn_lands_claimed.rs +++ b/tests/guest/rust-tests/src/bin/spawn_lands_claimed.rs @@ -16,9 +16,9 @@ mod image; use std::os::toyos::process::CommandExt; use std::process::{Command, Stdio}; -use toyos::endow::{self, SVC_LABEL}; +use toyos::endow::{self, service_label, SVC_PREFIX}; use toyos::process::Process; -use toyos::{namespace, port, AsHandle, OwnedHandle}; +use toyos::{port, AsHandle, OwnedHandle}; use toyos_abi::syscall::{self, debug_action, SpawnArgs, SyscallError}; use toyos_abi::RawHandle; use std::os::toyos::process::ExitStatusExt; @@ -28,10 +28,10 @@ const SELF_PATH: &str = "/system/bin/test_rs_spawn_lands_claimed"; /// The name its children go by. const NAME: &str = "test_rs_spawn_lands_claimed"; -/// The name the place's namespace carries the port back to the test under. +/// The service the place is given the port back to the test as. const BACK: &str = "back"; -/// The place to the test: a copy of its own `self`. +/// The place to the test: a copy of its handle to itself. const MSG_SELF: u32 = 1; const KILLED: End = End::Killed; @@ -47,10 +47,10 @@ fn main() { fn test() { let (acceptor, connector) = port::create().expect("a port of our own"); - let ns = namespace::build().add(BACK, &connector).finish().expect("a namespace carrying the port back"); let mut place = Command::new(SELF_PATH) .arg("place") - .endow(SVC_LABEL, ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, BACK), connector.into()) .stdin(Stdio::null()) .spawn() .expect("spawn the place"); @@ -91,8 +91,6 @@ fn spawn_under(place: RawHandle) -> Result { name_len: NAME.len() as u64, endow_ptr: 0, endow_count: 0, - labels_ptr: 0, - labels_len: 0, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -100,6 +98,7 @@ fn spawn_under(place: RawHandle) -> Result { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(place.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }; // SAFETY: every pointer names a buffer of this frame that outlives the call. diff --git a/tests/guest/rust-tests/src/bin/start_refuses_itself.rs b/tests/guest/rust-tests/src/bin/start_refuses_itself.rs index 867dcf64748..e1a3c836142 100644 --- a/tests/guest/rust-tests/src/bin/start_refuses_itself.rs +++ b/tests/guest/rust-tests/src/bin/start_refuses_itself.rs @@ -66,8 +66,6 @@ fn spawn_endowed_nothing(path: &str, argv: &str) -> Result Result End { name_len: NAME.len() as u64, endow_ptr: 0, endow_count: 0, - labels_ptr: 0, - labels_len: 0, maps_ptr: laid.maps().as_ptr() as u64, maps_count: laid.maps().len() as u64, entry: laid.entry, @@ -68,6 +66,7 @@ fn spawn_unsaid() -> End { build_id_ptr: laid.build_id().as_ptr() as u64, build_id_len: laid.build_id().len() as u64, place: u64::from(toyos_abi::HANDLE_INVALID.0), + job: u64::from(toyos_abi::HANDLE_INVALID.0), pager: u64::from(toyos_abi::HANDLE_INVALID.0), }; // SAFETY: every pointer names a live local for the whole call. diff --git a/tests/guest/rust-tests/src/bin/stat_identity.rs b/tests/guest/rust-tests/src/bin/stat_identity.rs index 6c21be0339c..fca0c07e0db 100644 --- a/tests/guest/rust-tests/src/bin/stat_identity.rs +++ b/tests/guest/rust-tests/src/bin/stat_identity.rs @@ -94,7 +94,7 @@ fn directory_replacement(root: &str) { fs::create_dir(&source).unwrap(); fs::create_dir(&target).unwrap(); write(&format!("{source}/file")); - let mut directory = Dir::connect(toyos::endow::namespace().unwrap(), &format!("fs:{root}")).unwrap(); + let mut directory = Dir::named(&format!("fs:{root}")).unwrap(); let original = directory.open(Base::Root, "stat-identity-source", O_DIRECTORY | O_READ).unwrap().fid; let alias = directory.dup(original).unwrap(); let replaced = directory.open(Base::Root, "stat-identity-target", O_DIRECTORY | O_READ).unwrap().fid; diff --git a/tests/guest/rust-tests/src/bin/state_view.rs b/tests/guest/rust-tests/src/bin/state_view.rs index 72e97917893..97fb816d43d 100644 --- a/tests/guest/rust-tests/src/bin/state_view.rs +++ b/tests/guest/rust-tests/src/bin/state_view.rs @@ -15,15 +15,14 @@ const OWN: &str = "/state/test-runner"; const SERVICE: &str = "/state/logkeeper"; fn main() { - let names = toyos::endow::namespace().expect("a job inherits test-runner's namespace"); let mut red = Vec::new(); for held in ["fs:/state", "fs:/state/supervisor", "fs:/state/logkeeper"] { - if names.open(held).is_ok() { + if toyos::endow::service(held).is_ok() { red.push(format!("it holds {held}")); } } - if let Err(e) = names.open(&format!("fs:{OWN}")) { + if let Err(e) = toyos::endow::service(&format!("fs:{OWN}")) { red.push(format!("fs:{OWN} would not connect: {e:?}")); } diff --git a/tests/guest/rust-tests/src/bin/stream_handles.rs b/tests/guest/rust-tests/src/bin/stream_handles.rs index a1b2e46754b..a37f06d4672 100644 --- a/tests/guest/rust-tests/src/bin/stream_handles.rs +++ b/tests/guest/rust-tests/src/bin/stream_handles.rs @@ -58,8 +58,9 @@ use toyos::poller::{Poller, READABLE}; use toyos::shm::SharedMemory; use toyos::transport::session::{Holder, Rings}; use toyos::wake::{self, Bell, Hungup, Waker}; -use toyos::{namespace, port, AsHandle, Connection, OwnedHandle}; -use toyos_abi::syscall::{self, SyscallError, SVC_LABEL}; +use toyos::endow::{service_label, SVC_PREFIX}; +use toyos::{port, AsHandle, Connection, OwnedHandle}; +use toyos_abi::syscall::{self, SyscallError}; const SELF_PATH: &str = "/system/bin/test_rs_stream_handles"; @@ -412,7 +413,7 @@ struct Netstack { server: Server, holders: Vec, /// The private port every holder past the first is a connection of. - exports: (port::Acceptor, namespace::Namespace), + exports: (port::Acceptor, port::Connector), } /// The region's header. @@ -466,7 +467,7 @@ impl Netstack { fn serve_export(&mut self, asker: Holder) -> Holder { assert_eq!(self.next_frame(asker), Some(EXPORT), "the spawn asks for a holder"); let number = Holder::new(self.holders.len() as u16 + 1).expect("a holder's number"); - let theirs = self.exports.1.open("export").expect("a connection of the private port"); + let theirs = self.exports.1.connect().expect("a connection of the private port"); let ours = self.exports.0.accept().expect("the private port's connection"); let ([receive_bell, send_bell, rung_waker], (receive, send, rung)) = bells(); let adopt = Adopt { holder: number.number(), peer_port: 80, peer_addr: [192, 0, 2, 2], local_addr: [192, 0, 2, 1], local_port: 49_152, _pad: 0 }; @@ -514,13 +515,11 @@ fn run(role: &str, peer: &[u8]) -> (Child, Netstack) { /// [`run`], the connect answered as left from `local`, port 49152. fn run_answering(role: &str, peer: &[u8], local: [u8; 4]) -> (Child, Netstack) { let (acceptor, connector) = port::create().expect("a port"); - // This test's own names, so a child reaches the file server its own + // This test's own services, so a child reaches the file server its own // spawns read images through, and this test as its `netstack`. - let own = toyos::endow::namespace().expect("this test's namespace"); - let ns = namespace::build().keep_all(own).add(SERVICE, &connector).finish().expect("a namespace"); let child = Command::new(SELF_PATH) .arg(role) - .endow(SVC_LABEL, ns.into()) + .endow(&service_label(SVC_PREFIX, SERVICE), connector.into()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -540,9 +539,8 @@ fn run_answering(role: &str, peer: &[u8], local: [u8; 4]) -> (Child, Netstack) { conn.send_with(RespType::Result as u32, &TcpConnectResponse { local_addr: local, local_port: 49_152, _pad: 0 }, handed) .expect("answer the connect with the bells"); let (exports, export_connector) = port::create().expect("the private port"); - let names = namespace::build().add("export", &export_connector).finish().expect("the private port's namespace"); let first = Holding { number: Holder::FIRST, connection: conn, frames: FrameRx::new(), receive, send, rung }; - (child, Netstack { region, server, holders: vec![first], exports: (exports, names) }) + (child, Netstack { region, server, holders: vec![first], exports: (exports, export_connector) }) } fn line(out: &mut BufReader) -> String { @@ -702,8 +700,7 @@ fn client() -> ! { } } let (acceptor, connector) = port::create().expect("a port of the client's own"); - let names = namespace::build().add("sink", &connector).finish().expect("its namespace"); - let conn = names.open("sink").expect("a connection to it"); + let conn = connector.connect().expect("a connection to it"); let _accepted = acceptor.accept().expect("its other end"); match conn.signal_with(1, [OwnedHandle::from(copy())]) { Err(IpcError::Syscall(SyscallError::PermissionDenied)) => say("send refused"), diff --git a/tests/guest/rust-tests/src/bin/tty_interrupt.rs b/tests/guest/rust-tests/src/bin/tty_interrupt.rs index 30f912fa708..ee1cc3ee866 100644 --- a/tests/guest/rust-tests/src/bin/tty_interrupt.rs +++ b/tests/guest/rust-tests/src/bin/tty_interrupt.rs @@ -25,7 +25,7 @@ use std::io::{Read, Write}; use std::os::toyos::process::CommandExt; use std::process::{ChildStdin, ChildStdout, Command, Stdio}; -use toyos::endow::SVC_LABEL; +use toyos::endow::{service_label, SVC_PREFIX}; use toyos::poller::{Poller, READABLE}; use toyos::surface::{self, Host, Notice}; @@ -100,7 +100,7 @@ const SELF_PATH: &str = "/system/bin/test_rs_tty_interrupt"; /// Listen, say so, and read three interrupts, saying each. fn reader() { - let notice = toyos::quit::notice().expect("a notice"); + let notice = toyos::quit::notice(); notice.listen().expect("listen"); println!("reader-armed"); for n in 1..=3 { @@ -111,7 +111,7 @@ fn reader() { /// Listen, say so, and never read. fn holder() { - toyos::quit::notice().expect("a notice").listen().expect("listen"); + toyos::quit::notice().listen().expect("listen"); println!("holder-armed"); loop { std::thread::park(); @@ -134,13 +134,8 @@ fn main() { }); let (acceptor, connector) = toyos::port::create().expect("a port of our own"); let mut host = Host::serve(acceptor); - let mut names = toyos::namespace::build(); - if let Some(own) = toyos::endow::namespace() { - names = names.keep_all(own); - } - let ns = names.add(surface::SERVICE, &connector).finish().expect("the shell's namespace"); let mut child = Command::new("/system/bin/shell") - .endow(SVC_LABEL, ns.into()) + .endow(&service_label(SVC_PREFIX, surface::SERVICE), connector.into()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/tests/guest/rust-tests/src/bin/user_copy_faults.rs b/tests/guest/rust-tests/src/bin/user_copy_faults.rs index 6d15bfcbada..b0d43eb9d6c 100644 --- a/tests/guest/rust-tests/src/bin/user_copy_faults.rs +++ b/tests/guest/rust-tests/src/bin/user_copy_faults.rs @@ -7,7 +7,8 @@ //! Each buffer starts in a page the program touched and runs into one it did //! not, so the first fault comes mid-copy rather than at its first byte. -use toyos_abi::syscall::{self, NamespaceBuild, SyscallError, VmAt, VmProt, PAGE_SIZE}; +use toyos::AsHandle; +use toyos_abi::syscall::{self, SyscallError, VmAt, VmProt, MAX_BADGE, PAGE_SIZE}; const PAGE: usize = PAGE_SIZE; /// Bytes of each buffer on either side of the page boundary it straddles. @@ -67,47 +68,41 @@ fn a_fault_served_mid_copy_resumes(len: usize, seed: u8, from: usize, into: usiz len } -/// `SYS_NAMESPACE_BUILD` reads its names blob whole before anything else, so a -/// blob that runs off its mapping, or into a page the program may not read, +/// `SYS_PORT_MINT` copies its badge whole before it looks at a handle, so a +/// badge that runs off its mapping, or into a page the program may not read, /// is refused `BadAddress` mid-copy, and a kernel address before any copy. fn a_byte_nothing_serves_ends_the_copy() { + /// Bytes of the badge on either side of the boundary it straddles. + const HALF: usize = MAX_BADGE / 2; + let (acceptor, _connector) = toyos::port::create().expect("a port"); + let mint = |at: u64, len: usize| { + // SAFETY: the address names memory the kernel must refuse; nothing here reads it. + let badge = unsafe { core::slice::from_raw_parts(at as *const u8, len) }; + syscall::port_mint(acceptor.as_handle().raw(), badge) + }; let blob = fresh(2); // SAFETY: inside the mapping: the first page is touched and the second given back. unsafe { - blob.add(PAGE - SIDE).write_volatile(b'a'); + blob.add(PAGE - HALF).write_volatile(b'a'); syscall::vm_release(blob.add(PAGE), PAGE).expect("release the second page"); } - let build = |names_ptr: u64, names_len: u64| { - let args = NamespaceBuild { - base: toyos_abi::handle::HANDLE_INVALID, - flags: 0, - keep_ptr: 0, - keep_n: 0, - add_ptr: 0, - add_n: 0, - names_ptr, - names_len, - }; - // SAFETY: the pointers name memory the kernel must refuse; nothing here reads it. - unsafe { syscall::namespace_build(&args) } - }; - let across = build(blob as u64 + (PAGE - SIDE) as u64, 2 * SIDE as u64); - assert_eq!(across, Err(SyscallError::BadAddress), "a names blob running into a released page"); + let across = mint(blob as u64 + (PAGE - HALF) as u64, MAX_BADGE); + assert_eq!(across.err(), Some(SyscallError::BadAddress), "a badge running into a released page"); let guarded = fresh(2); // SAFETY: inside the mapping: the first page is touched, the second made unreadable. unsafe { - guarded.add(PAGE - SIDE).write_volatile(b'a'); + guarded.add(PAGE - HALF).write_volatile(b'a'); syscall::vm_protect(guarded.add(PAGE), PAGE, VmProt::None).expect("protect the second page"); } - let unreadable = build(guarded as u64 + (PAGE - SIDE) as u64, 2 * SIDE as u64); - assert_eq!(unreadable, Err(SyscallError::BadAddress), "a names blob running into an unreadable page"); + let unreadable = mint(guarded as u64 + (PAGE - HALF) as u64, MAX_BADGE); + assert_eq!(unreadable.err(), Some(SyscallError::BadAddress), "a badge running into an unreadable page"); - let kernel = build(0xFFFF_8000_0000_0000, 16); - assert_eq!(kernel, Err(SyscallError::BadAddress), "a names blob at a kernel address"); + let kernel = mint(0xFFFF_8000_0000_0000, 16); + assert_eq!(kernel.err(), Some(SyscallError::BadAddress), "a badge at a kernel address"); - let past = build(0x0000_7FFF_FFFF_FF00, 0x200); - assert_eq!(past, Err(SyscallError::BadAddress), "a names blob running past the user half"); + let past = mint(0x0000_8000_0000_0000 - HALF as u64, MAX_BADGE); + assert_eq!(past.err(), Some(SyscallError::BadAddress), "a badge running past the user half"); println!("a byte nothing serves ends the copy as BadAddress"); } diff --git a/tests/guest/rust-tests/src/bin/window_refusal.rs b/tests/guest/rust-tests/src/bin/window_refusal.rs index 0741c0eab3e..b2e00b2ca74 100644 --- a/tests/guest/rust-tests/src/bin/window_refusal.rs +++ b/tests/guest/rust-tests/src/bin/window_refusal.rs @@ -9,8 +9,8 @@ //! **This binary plays the compositor, and how it does that is the point.** It //! used to squat `services::listen("compositor")` on a boot where no compositor //! ran, which worked because any process could take any name. There is no name -//! to take now. Instead it creates a port, builds a namespace mapping -//! `"compositor"` to that port's connector, and spawns a child holding it: the +//! to take now. Instead it creates a port and spawns a child holding its +//! connector as the service `"compositor"`, and no other service: the //! child's `Window::create` reaches this process and nothing else, no other //! process can see the service. That is the pattern every hostile-server test //! uses from here. @@ -25,11 +25,10 @@ use std::os::toyos::process::CommandExt; use std::process::{Command, Stdio}; -use toyos::endow::{self, EndowError}; +use toyos::endow::{self, service_label, EndowError, SVC_PREFIX}; use toyos::port::Acceptor; use toyos::AsHandle; -use toyos::{ipc, namespace, port}; -use toyos_abi::syscall::SVC_LABEL; +use toyos::{ipc, port}; use window::{CreateError, Window}; const SELF_PATH: &str = "/system/bin/test_rs_window_refusal"; @@ -78,15 +77,12 @@ fn main() { fn server() { let (acceptor, connector) = port::create().expect("window_refusal: the kernel refused a port"); - let child_ns = namespace::build() - .add("compositor", &connector) - .finish() - .expect("window_refusal: the kernel refused a namespace"); // The child is spawned holding exactly one connector, and it points here. let mut child = Command::new(SELF_PATH) .arg("client") - .endow(SVC_LABEL, child_ns.into()) + .services_only(&[]) + .endow(&service_label(SVC_PREFIX, "compositor"), connector.into()) .stdout(Stdio::inherit()) .spawn() .expect("window_refusal: spawn the client"); diff --git a/tests/guest/rust-tests/src/image.rs b/tests/guest/rust-tests/src/image.rs index e676223d49d..0a0a7efbab7 100644 --- a/tests/guest/rust-tests/src/image.rs +++ b/tests/guest/rust-tests/src/image.rs @@ -45,7 +45,12 @@ impl std::ops::Deref for Laid { /// with `argv`, NUL-separated, and no environment: a raw `SpawnArgs` names /// its ranges (`maps_ptr`, `maps_count`), `entry`, `image_base` and build-id. pub fn laid(image: &Image, argv: &str) -> Laid { - let block = Block::new(argv.as_bytes(), b"", None).unwrap_or_else(|e| panic!("a startup block: {e:?}")); + laid_naming(image, argv, &[]) +} + +/// [`laid`], its child calling the spawn's `i`th endowment `names[i]`. +pub fn laid_naming(image: &Image, argv: &str, names: &[&str]) -> Laid { + let block = Block::new(argv.as_bytes(), b"", names, None).unwrap_or_else(|e| panic!("a startup block: {e:?}")); let program = lay_out(image.bytes(), Machine::RUNNING, image.source()).unwrap_or_else(|why| panic!("lay out an image: {why}")); Laid { program: program.starting_with(&block), _block: block } } diff --git a/tests/guest/rust-tests/src/usbd_spare.rs b/tests/guest/rust-tests/src/usbd_spare.rs index b3b9656b139..3d173ad0f86 100644 --- a/tests/guest/rust-tests/src/usbd_spare.rs +++ b/tests/guest/rust-tests/src/usbd_spare.rs @@ -146,8 +146,7 @@ impl Usbd { /// One `inspect` answer, printed whole. fn ask(&self, when: &str) -> BTreeMap { - let ns = toyos::namespace::build().add(USB.port, &self.conn).finish().expect("usbd_spare: a namespace"); - let conn: Connection = ns.open(USB.port).expect("usbd_spare: a connection to usbd"); + let conn: Connection = self.conn.connect().expect("usbd_spare: a connection to usbd"); conn.signal(MSG_INSPECT).expect("usbd_spare: the request"); let mut rx = Box::new(toyos::ipc::FrameRx::<{ toyos::ipc::MAX_FRAME_LEN as usize }>::new()); let frame = rx.recv(&conn).expect("usbd_spare: usbd's answer"); diff --git a/tests/guest/test-runner/src/main.rs b/tests/guest/test-runner/src/main.rs index 904e8148fe2..b16ab0f00ab 100644 --- a/tests/guest/test-runner/src/main.rs +++ b/tests/guest/test-runner/src/main.rs @@ -29,8 +29,8 @@ static JOB: Mutex> = Mutex::new(None); /// connector, which each job is handed a copy of, and the launcher of the /// session it opens for its jobs, which a login authority's launches are in. struct Login { - connector: toyos::namespace::Namespace, - session: toyos::namespace::Namespace, + connector: toyos::port::Connector, + session: toyos::port::Connector, _control: toyos::ipc::Connection, } @@ -39,7 +39,7 @@ static LOGIN: std::sync::OnceLock> = std::sync::OnceLock::new(); fn login() -> Option<&'static Login> { LOGIN .get_or_init(|| { - let connector = Endowments::get().take::(LOGIN_PORT)?; + let connector = Endowments::get().take::(LOGIN_PORT)?; match toyos_session::open(&connector) { Ok((session, control)) => Some(Login { connector, session, _control: control }), Err(why) => panic!("test-runner: a login authority with no session for its jobs: {why}"), @@ -80,8 +80,8 @@ fn main() { // **The test estate's authority, and the one place least authority is not // enforced.** The guest binaries are not `[programs]` keys, so no manifest // row can name what any of them holds: a test binary holds what test-runner - // holds. The namespace - // travels by inheritance; this capability is handed over explicitly, as a + // holds. Its services + // travel by inheritance; this capability is handed over explicitly, as a // duplicate rather than the cap itself, because one boot runs several // binaries that each need the keyboard and a device claim moves. let cap: Option = Endowments::get().take(SYSCAP_LABEL); @@ -261,7 +261,7 @@ fn run_one(name: &str, args: &[&str], cap: Option<&SysCap>) -> Ran { // **A refused dup is an answer and not a failure — but only one // refusal is.** `duplicate` needs `DUP` on the capability, which a // manifest grants by name, so `PermissionDenied` says this cap is one - // the program holds *for itself* and the child gets the namespace and + // the program holds *for itself* and the child gets the services and // no capability at all. `logread` is exactly such a cap, as `realtime` // is: the estate does not hand either down. The `expect` here // assumed every cap was dup-able and took the whole boot down on the diff --git a/tests/images/designatecase.toml b/tests/images/designatecase.toml index bcaf00852e2..45033fccd50 100644 --- a/tests/images/designatecase.toml +++ b/tests/images/designatecase.toml @@ -24,8 +24,8 @@ service = true devices = ["acpi"] receives = ["power"] -# `power` for the reboot that ends the list. Its jobs inherit its namespace, -# the authorization among it. +# `power` for the reboot that ends the list. Its jobs inherit its services, +# the authorization among them. [programs.test-runner] receives = ["power"] authorize = ["designate:data"] diff --git a/tests/images/netcase.toml b/tests/images/netcase.toml index d9299344ef2..947beea55a4 100644 --- a/tests/images/netcase.toml +++ b/tests/images/netcase.toml @@ -51,7 +51,8 @@ serves = ["soundserver", "volume", "sound-output"] devices = ["pci:04:01:00/1af4:1059"] syscap = ["rt", "wake-latency"] -# A job reaches netstack through test-runner's namespace: +# A job reaches netstack through the service test-runner holds and its jobs +# inherit: # `netstack_holders_exported_to_children`'s jobs reach the harness through it. [programs.test-runner] receives = ["netstack"] diff --git a/tests/images/testcases.toml b/tests/images/testcases.toml index 035046d066f..378112b5c3e 100644 --- a/tests/images/testcases.toml +++ b/tests/images/testcases.toml @@ -51,7 +51,7 @@ syscap = ["rt"] # and no manifest row can name them — they are not `[programs]` keys — and `dup` # because a claim moves and one boot runs several of them. # `logread` because the log gate reads the kernel's own records and cannot be -# a spawned binary: a `SysCap` dup is not part of the namespace test-runner +# a spawned binary: a `SysCap` dup is not among the services test-runner # hands down. # `power` because `endowment_denied` narrows it *away* to prove the two power # syscalls refuse a capability without it, which a capability that never diff --git a/tests/metal/src/image.rs b/tests/metal/src/image.rs index 5be91304db3..eaf843d2920 100644 --- a/tests/metal/src/image.rs +++ b/tests/metal/src/image.rs @@ -11,8 +11,8 @@ //! So a metal image is a committed boot config with that one field derived onto //! it. The derivation is here, pure, because what it changes about the config //! is what the T14 sees and nothing else may drift from it: the one authority a -//! `reboot` job needs (the `power` connector, which the applet inherits with the -//! runner's namespace and asks the supervisor through), the applet itself, and the name +//! `reboot` job needs (the `power` connector, which the applet inherits as one of +//! the runner's services and asks the supervisor through), the applet itself, and the name //! that reaches it. #![forbid(unsafe_code)]