Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
043b86e
A package granted a folder of the home by `grants` starts in it and s…
Japabu Oct 9, 2026
04750cc
The decide table names its row type, which clippy asks of a tuple tha…
Japabu Oct 9, 2026
89c4c7e
The supervisor asserts a granted folder fits a grant and the grant li…
Japabu Oct 9, 2026
c190ac7
app_view names a missing grants program as red rather than panicking
Japabu Oct 9, 2026
d9c6ec1
A package launched from the desktop asks the person at the screen for…
Japabu Oct 10, 2026
b4bb07e
consent_prompt clicks the launcher row once the launcher is on the panel
Japabu Oct 10, 2026
a07d7e9
Merge origin/main at a1eb2c0b9 into launch-time consent
Japabu Oct 10, 2026
1a1e8dc
The supervisor's header says a package's launch may wait for the pers…
Japabu Oct 10, 2026
7d1c726
Merge origin/main at d5bc4df9d into launch-time consent: consent_prom…
Japabu Oct 10, 2026
40fa6c9
One judgement of a consent answer and one path to the grants store, h…
Japabu Oct 10, 2026
c5d744e
The compositor admits the prompt past every client ceiling
Japabu Oct 10, 2026
a207c4f
File the picker holding the question and the second poller, and give …
Japabu Oct 10, 2026
35a679f
consent's job ends at once on a red before its steps
Japabu Oct 10, 2026
27b2b92
Merge origin/main at a09dce906, which carries usbd (#827), the SMMUv3…
Japabu Oct 10, 2026
64a81c5
File the parked launch's untested hang-up wiring, and give the second…
Japabu Oct 10, 2026
fcf8fec
Merge origin/main at 60dc7fcec, which carries per-partition block aut…
Japabu Oct 10, 2026
be3479a
Merge origin/main at 8b2969d0f, which carries #822, #828, #831, #833,…
Japabu Oct 10, 2026
b47715b
Merge origin/main at bb3344d1f, which carries #829, #832, #841, #845,…
Japabu Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ members = [
"userland/editor",
"userland/filepicker",
"userland/filepicker-api",
"userland/grants",
"userland/files",
"userland/fileserver",
"userland/host",
Expand Down Expand Up @@ -249,6 +250,9 @@ toyos-xhci = { path = "toyos-xhci" }
# The ACPI server's count line, so the judge reads the words the server writes
# and the job that waits for them matches.
acpiserver-api = { path = "userland/acpiserver-api" }
# The desktop's geometry and the prompt's colour, so `consent_prompt` clicks
# where the compositor draws and finds the prompt by what the compositor paints.
toyos-desktop = { path = "userland/compositor/desktop" }
# `common::https`'s server, the peer a guest's HTTPS client is judged
# against, and the authority and certificates it presents, made per run.
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# A parked launch's hang-up reaches `settle` through wiring no test runs

A launch that asks the person at the screen is parked in the supervisor until
its question is answered, and `toyos_manifest::consent::settle` decides its
next step: a caller that hung up withdraws it before any answer. That decision
is host-tested. What feeds it is not: in `userland/supervisor/src/main.rs`, the
loop's watch of the parked caller's connection (`TOKEN_PARKED_CALLER`), the
ready test that reads it, `Supervisor::advance_parked`'s two arms, and `heard`,
which turns a `FrameRx` step into a `consent::Heard`. A defect there starts a
launch whose caller has gone, with the folder the person answered; it grants
nothing the person did not give.

**Evidence**: no guest test can hang up a caller while its question is up.
It needs a caller in a session that holds the screen which ends while asking,
and each candidate fails:

- the compositor's launcher thread never hangs up;
- a shell's own spawn waits on the answer, and the prompt takes every key;
- a program a shell spawns directly holds no launcher, and its helper panicked
for want of one when tried;
- a test binary cannot be an image row (`build_programs` builds rows from
`userland/<name>`), and the build refused one when tried.

## Owner

Stage 2 of `issues/the-supervisor-is-host-tested-and-owns-the-stop.md`, which
keeps handles, spawns and the loop in `userland/supervisor`.

## Exit condition

A guest test in which a caller in a session that holds the screen ends while
its question is up: the supervisor says `withdrawn`, the package never starts,
and the prompt leaves the panel.
37 changes: 37 additions & 0 deletions issues/a-question-waits-unseen-while-a-file-is-picked.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# A question waits unseen while a file is picked, and every desktop launch waits with it

`/system/bin/filepicker` serves both a program's file-picking session
(`filepicker`) and the supervisor's question before a package's launch
(`consent`), from one loop. A picking session is `run_picker`, called inside
that loop, which returns only when the session ends. A question asked during
one stays queued on the `consent` acceptor, unseen, until the session ends.

The supervisor holds that launch parked until the question is answered, which
is right. But the compositor makes every launch on one thread, one at a time
(`userland/compositor/src/session.rs`, `launcher()`), because a launch that
asks waits as long as its question. So while the question waits unseen, no
launch from the desktop is made at all, the terminal's chord included, until
the person closes the picker, whose window gives no sign of why.

**Evidence**: read from the code at the branch that made the question
(`userland/filepicker/src/main.rs`, the `run_picker` call in `main`'s loop;
the compositor's `launcher`). Not run: no test opens a picker and launches a
package while it is open.

## Owner

`userland/filepicker` and the compositor's launch path, in the desktop track
`issues/toyos-has-a-desktop.md`.

## Exit condition

A guest test opens a file-picking session, launches a package that asks from
the desktop, and sees the prompt on the panel while the picker is still open;
and a launch from the desktop that asks nothing starts while another's
question is up.
38 changes: 38 additions & 0 deletions issues/a-second-poller-on-a-windows-handle-stalled-the-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# A second poller on a window's handle stalled the prompt, and why is unknown

Filepicker's prompt (`userland/filepicker/src/consent.rs`, `ask`) waits on
one `Poller` watching both its window's connection and the supervisor's.
While the branch that made it was built, a version that also drained the
window with `Window::poll_event(0)` after each wake, which watches the same
handle through the window's own second `Poller`, stalled the prompt in
`consent_prompt` (`tests/toyos.rs`) after its first keys. Reading one event per wake off the first poller
alone, as `ask` does now, does not stall.

The kernel-side cause was not found. Nothing says the shape is the prompt's
alone: a program that waits on a `Window` beside another handle through its
own poller and drains with `poll_event(0)` watches one handle from two
pollers in the same way. `rg poll_event` finds those callers.

**Evidence**: the stalling variant was not committed and its runs' logs were
not kept; what is recorded is the shape above and that removing the second
poller ended the stall. Related, and not shown to be the cause:
`issues/a-close-of-one-handle-ends-every-rings-poll-on-its-object.md` and
`issues/toyos-poller-is-sync-and-a-watch-moves-the-tail-in-two-steps.md`.

## Owner

`toyos-window`'s `Window::poll_event` and the windows that drain through it,
in the desktop track `issues/toyos-has-a-desktop.md`.

## Exit condition

A guest test in which one thread watches a connection from two pollers, one
waiting and one polling with no wait, and is handed every frame its peer
sends, red on the shape above or green with the cause named and fixed; or
`Window::poll_event` no longer owns a poller of its own.
35 changes: 35 additions & 0 deletions issues/any-declared-row-can-write-the-grants-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# Any declared row can write the grants store

`/system/bin/supervisor` keeps every answer about a folder of the home in
`/state/supervisor/grants` (`toyos_manifest::grants::STORE`): which folder a
package's exact binary is granted, and which binaries were denied. The
supervisor is the only program meant to write it, and it reads it back at boot
refusing by name anything its own renderer cannot have written.

But every row the image declares sees the whole tree read-write
(`toyos_manifest::whole_tree`), `/state` included. So any declared program, a
shell and everything a shell starts among them, can write a line granting a
package any folder the store's parser accepts, or delete a Deny, and the next
boot honours it without asking the person at the screen. Such a row already
reaches the whole tree itself, so it gains no file it lacks; what it gains is
a package's access, without the consent the prompt exists to ask for.

The store is read once at boot, so a rewrite made while the machine runs takes
effect at the next boot.

## Owner

`issues/every-program-sees-only-the-files-it-was-given.md`, stage 2: every
row declares its files, and nothing still sees the global tree.

## Exit condition

No row but the supervisor's own view holds `/state/supervisor` writable, and a
guest test whose job writes a grant line into the store from a shell is
refused by the file server.
31 changes: 31 additions & 0 deletions issues/two-clicks-the-compositor-reads-at-once-are-one-click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# Two clicks the compositor reads at once are one click

`toyos_desktop::fold_mouse` folds a whole read of the mouse claim into one
sample: whether the left button went down and came up somewhere in it, and the
last position. Two clicks in one read, at two places, are therefore one press
and one release, both at the second click's position, and the first click is
lost. A person clicking `+` and then a launcher row quickly enough for one
read to carry both, which a loaded machine makes likelier, has the row's
click land on a closed launcher.

`consent_prompt` (`tests/toyos.rs`) met it on a loaded host (load average
57 when its batch of runs ended): it clicked `+` and then the launcher row
with nothing between them, and no launch was made, which these two clicks
folded into one on the row of a launcher not yet open accounts for. The test
now waits for the launcher on the panel before its second click.

## Owner

The compositor's input decisions, `userland/compositor/desktop/src/input.rs`,
in the desktop track `issues/toyos-has-a-desktop.md`.

## Exit condition

A host test of `fold_mouse` (or what replaces it) in which one read carrying
two clicks at two positions yields both, each at its own position.
14 changes: 13 additions & 1 deletion sdk/std/os/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,18 @@ pub trait CommandExt {
/// for a caller whose spawning thread may not wait on one, as
/// `/system/bin/supervisor`'s loop may not wait on the file servers it starts
/// again. A later [`current_dir`](crate::process::Command::current_dir)
/// undoes it.
/// undoes it but for the program read, which a prepare after it keeps and
/// a later [`image_from`](CommandExt::image_from) does not.
#[stable(feature = "toyos_ext", since = "1.0.0")]
fn prepare(&mut self) -> crate::io::Result<&mut Self>;

/// The bytes a prepared spawn starts, as [`prepare`](CommandExt::prepare)
/// read them off a file server: what a caller holds a program to before it
/// runs, with no second read between the two. `None` before a prepare and
/// for a program the kernel opens itself.
#[stable(feature = "toyos_ext", since = "1.0.0")]
fn prepared_image(&self) -> Option<&[u8]>;

/// Run the program at `path` rather than the one the command names, which
/// stays the child's `argv[0]`: the kernel opens `path`, or the spawn reads
/// it when a file server serves it, and nothing opens `argv[0]`.
Expand Down Expand Up @@ -109,6 +117,10 @@ impl CommandExt for crate::process::Command {
Ok(self)
}

fn prepared_image(&self) -> Option<&[u8]> {
self.as_inner().prepared_image()
}

fn image_from(&mut self, path: &crate::path::Path) -> &mut Self {
self.as_inner_mut().image_from(path.as_os_str());
self
Expand Down
38 changes: 28 additions & 10 deletions sdk/std/sys/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub struct Command {
/// The program the child runs, when it is not the one `argv[0]` names.
image_path: Option<OsString>,
prepared: Option<Prepared>,
/// The program [`Command::prepare`] read off a file server, and the path
/// it read: kept across a later working directory, so a spawn starts the
/// bytes the caller saw ([`Command::prepared_image`]).
image: Option<(OsString, toyos::process::Image)>,
}

/// Whom a child is placed under, whose end takes it down.
Expand All @@ -46,8 +50,6 @@ enum Parent {
struct Prepared {
program: OsString,
cwd: String,
/// The program, when it is on a file server.
image: Option<toyos::process::Image>,
}

/// Where a spawn goes once the launcher has been asked, or could not be.
Expand Down Expand Up @@ -98,6 +100,7 @@ impl Command {
parent: Parent::Caller,
image_path: None,
prepared: None,
image: None,
}
}

Expand All @@ -110,6 +113,7 @@ impl Command {
pub fn image_from(&mut self, path: &OsStr) {
self.image_path = Some(path.to_owned());
self.prepared = None;
self.image = None;
}

pub fn env_mut(&mut self) -> &mut CommandEnv {
Expand All @@ -122,19 +126,32 @@ impl Command {
}

/// Find the program, judge the working directory and read a program on a
/// file server, so that [`Self::spawn`] calls no file server for them.
/// file server, so that [`Self::spawn`] calls no file server for them. A
/// program already read from the same path is kept, not read again.
pub fn prepare(&mut self) -> io::Result<()> {
let program = self.resolve_program()?;
let cwd = self.child_cwd()?;
let source = Path::new(self.image_path.as_deref().unwrap_or(&program));
let image = match crate::sys::fs::is_served(source) {
true => Some(crate::sys::fs::read_image(source)?),
false => None,
};
self.prepared = Some(Prepared { program, cwd, image });
let source = self.image_path.clone().unwrap_or_else(|| program.clone());
let kept = self.image.as_ref().is_some_and(|(read, _)| *read == source);
if !kept {
self.image = None;
if crate::sys::fs::is_served(Path::new(&source)) {
let image = crate::sys::fs::read_image(Path::new(&source))?;
self.image = Some((source, image));
}
}
self.prepared = Some(Prepared { program, cwd });
Ok(())
}

/// The program a prepared spawn starts, as [`Self::prepare`] read it off a
/// file server: `None` before a prepare, and for a program the kernel
/// opens itself.
pub fn prepared_image(&self) -> Option<&[u8]> {
self.prepared.as_ref()?;
self.image.as_ref().map(|(_, image)| image.bytes())
}

pub fn stdin(&mut self, stdin: Stdio) {
self.stdin = Some(stdin);
}
Expand Down Expand Up @@ -284,8 +301,9 @@ impl Command {
));
}
let prepared = self.prepared.take();
let image = self.image.take().map(|(_, image)| image);
let (resolved, cwd, prepared_image) = match prepared {
Some(Prepared { program, cwd, image }) => (program, cwd, image),
Some(Prepared { program, cwd }) => (program, cwd, image),
None => (self.resolve_program()?, self.child_cwd()?, None),
};
let mut argv_buf = Vec::new();
Expand Down
Loading
Loading