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:
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user