feat(settings): settings app: apps list + per-capability allow/revoke, about (who/role), sign out; system-only caps permissions/session (install refused); pm MANAGE_BADGE copy (list/apps/allow/revoke only), APPS op; init WHO + session pass-on; factory decisions seeded for new system apps; k-ui list_item/toggle, screens/back/scroll

This commit is contained in:
2026-10-28 13:14:35 +01:00
parent a2d4a7d085
commit 29becf297c
38 changed files with 947 additions and 85 deletions

View File

@@ -46,6 +46,9 @@ struct Running {
data: Option<CapSlot>,
/// Its window, closed when it dies.
window: Option<CapSlot>,
/// The management copy of this server's endpoint (`permissions`),
/// revoked when it dies.
manage: Option<CapSlot>,
}
pub struct PermissionServer {
@@ -54,6 +57,11 @@ pub struct PermissionServer {
/// Init's spawn endpoint.
init: CapSlot,
display: Option<CapSlot>,
/// Init's session (who is signed in, signing out), for apps granted
/// `session`.
session: Option<CapSlot>,
/// This server's own endpoint, which management copies are minted from.
endpoint: CapSlot,
/// Bound to the serving thread: deaths of running apps.
deaths: CapSlot,
staged: Staged,
@@ -70,6 +78,7 @@ fn refused(r: Refusal) -> [usize; 4] {
Refusal::Declined => (reply::DECLINED, 0),
Refusal::Missing(c) => (reply::MISSING, c.bits() as usize),
Refusal::NotRequested => (reply::NOT_REQUESTED, 0),
Refusal::SystemOnly(c) => (reply::SYSTEM_ONLY, c.bits() as usize),
Refusal::Store => (reply::FAILED, 0),
};
[code, detail, 0, 0]
@@ -116,14 +125,21 @@ fn parts(page: &[u8]) -> impl Iterator<Item = &str> {
}
impl PermissionServer {
pub fn new(store: DiskStore, consent: TerminalConsent, init: CapSlot, display: Option<CapSlot>, deaths: CapSlot) -> Self {
Self { store, consent, init, display, deaths, staged: Staged::default(), running: [const { None }; MAX_RUNNING] }
pub fn new(store: DiskStore, consent: TerminalConsent, ports: Ports, deaths: CapSlot) -> Self {
let Ports { init, display, session, endpoint } = ports;
Self { store, consent, init, display, session, endpoint, deaths, staged: Staged::default(), running: [const { None }; MAX_RUNNING] }
}
/// What this device has a server for.
fn available(&self) -> Capabilities {
let base = Capabilities::NONE.with(Capability::Storage);
if self.display.is_some() { base.with(Capability::Display) } else { base }
let mut c = Capabilities::NONE.with(Capability::Storage).with(Capability::Permissions);
if self.display.is_some() {
c = c.with(Capability::Display);
}
if self.session.is_some() {
c = c.with(Capability::Session);
}
c
}
fn stage(&mut self, badge: usize, op: usize, cap: Option<CapSlot>) -> [usize; 4] {
@@ -185,6 +201,16 @@ impl PermissionServer {
true => self.display.and_then(|d| libkos::display::open(d, plan.app.name.as_str().as_bytes())).map(|o| o.window),
false => None,
};
// Managing others through a copy of this endpoint that can do no
// more (see `MANAGE_BADGE`).
let manage = match plan.granted.contains(Capability::Permissions) {
true => {
let perms = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
syscall::mint(self.endpoint, types::badge::Badge::new(wire::MANAGE_BADGE | index), perms).ok()
}
false => None,
};
let session = self.session.filter(|_| plan.granted.contains(Capability::Session));
let staged = (badge == self.staged.badge).then_some(&self.staged);
let arg = StartArg {
log: staged.and_then(|s| s.output).and_then(|o| child.give(o, call)).or(child.log()),
@@ -195,6 +221,9 @@ impl PermissionServer {
display: window.and_then(|w| child.give_sending(w, call)),
args: child.set_args(args),
console: None,
// Delegate, to lend its request page with each call.
permissions: manage.and_then(|m| child.give_sending(m, call)),
session: session.and_then(|s| child.give(s, call)),
};
let watched = self.watch(child.process, index);
if !watched || !child.start_with(arg) {
@@ -203,10 +232,11 @@ impl PermissionServer {
let _ = libkos::fs::release(d);
}
close_window(window);
revoke(manage);
syscall::drop_cap(child.process);
return Err(code(reply::FAILED));
}
self.running[index] = Some(Running { id: plan.app.id, process: child.process, granted: plan.granted, data, window });
self.running[index] = Some(Running { id: plan.app.id, process: child.process, granted: plan.granted, data, window, manage });
Ok((child.process, plan.granted))
}
@@ -248,6 +278,20 @@ impl PermissionServer {
if write_page(page, &text.0[..len]) { [reply::OK, len, 0, 0] } else { code(reply::FAILED) }
}
/// Every app, for a program to read (`types::apps::APPS`).
fn apps(&mut self, page: CapSlot) -> [usize; 4] {
use core::fmt::Write;
let mut ids = Vec::new();
self.store.ids(|id| ids.push(id));
let mut text = Text(Vec::new());
for id in ids {
let (Some(app), Some(d)) = (self.store.manifest(&id), self.store.decision(&id)) else { continue };
let _ = writeln!(text, "{}\t{}\t{}\t{}\t{}", app.id, app.name.as_str(), app.required.bits(), app.optional.bits(), d.granted.bits());
}
let len = text.0.len().min(PAGE_SIZE);
if write_page(page, &text.0[..len]) { [reply::OK, len, 0, 0] } else { code(reply::FAILED) }
}
/// The owner changes app `id`'s (in the page) capability `index`.
fn change(&mut self, page: CapSlot, index: usize, allow: bool) -> [usize; 4] {
let page = read_page(page);
@@ -282,6 +326,7 @@ impl PermissionServer {
let _ = libkos::fs::release(d);
}
close_window(r.window);
revoke(r.manage);
syscall::drop_cap(r.process);
}
}
@@ -322,11 +367,18 @@ impl Server for PermissionServer {
}
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<Give>) {
if !allowed(badge, words[0]) {
if let Some(c) = cap {
syscall::drop_cap(c);
}
return ([MALFORMED, 0, 0, 0], None);
}
match (words[0], cap) {
(wire::PASS_OUTPUT | wire::PASS_FOLDER, cap) => (self.stage(badge, words[0], cap), None),
(wire::LAUNCH, Some(page)) => self.launch(badge, page),
(wire::INSTALL, source) => (self.install(source), None),
(wire::LIST, Some(page)) => (self.list(page), None),
(wire::APPS, Some(page)) => (self.apps(page), None),
(wire::REVOKE, Some(page)) => (self.change(page, words[1], false), None),
(wire::ALLOW, Some(page)) => (self.change(page, words[1], true), None),
(_, cap) => {
@@ -349,3 +401,26 @@ fn close_window(window: Option<CapSlot>) {
syscall::drop_cap(w);
}
}
/// Whether `op` may come through the copy of this endpoint badged `badge`:
/// a management copy (`permissions`) decides, never installs or launches.
pub fn allowed(badge: usize, op: usize) -> bool {
badge & wire::MANAGE_BADGE == 0 || matches!(op, wire::LIST | wire::APPS | wire::REVOKE | wire::ALLOW)
}
fn revoke(cap: Option<CapSlot>) {
if let Some(c) = cap {
let _ = syscall::revoke(c);
syscall::drop_cap(c);
}
}
/// What the permission manager was given to reach others.
pub struct Ports {
/// Init's spawn endpoint.
pub init: CapSlot,
pub display: Option<CapSlot>,
pub session: Option<CapSlot>,
/// Its own endpoint.
pub endpoint: CapSlot,
}