|
|
|
|
@@ -15,6 +15,7 @@ use types::bounded::CapSlot;
|
|
|
|
|
use types::budget::Limits;
|
|
|
|
|
use types::mapping::PagePerms;
|
|
|
|
|
use types::permission::{Permission, PermissionSet};
|
|
|
|
|
use types::display::window::role as display_role;
|
|
|
|
|
use types::start_arg::StartArg;
|
|
|
|
|
|
|
|
|
|
use crate::consent::{CapabilityNames, TerminalConsent, version};
|
|
|
|
|
@@ -27,6 +28,12 @@ const PROGRAM: Limits = Limits { pages: 0, endpoints: 1, notifications: 1, regio
|
|
|
|
|
/// Apps running at once; a death is a bit each on the notification.
|
|
|
|
|
const MAX_RUNNING: usize = 8;
|
|
|
|
|
const MAX_ARGS: usize = 16;
|
|
|
|
|
/// The session's desktop, started with it and again if it dies.
|
|
|
|
|
const HOME: &str = "kos.home";
|
|
|
|
|
/// Home's starts in a session at most: one that keeps dying is left down.
|
|
|
|
|
const MAX_HOMES: u8 = 5;
|
|
|
|
|
/// A launch that no launcher asked for (Home's): nothing staged is its.
|
|
|
|
|
const NO_LAUNCHER: usize = usize::MAX;
|
|
|
|
|
/// Where a client's request page is mapped during its call.
|
|
|
|
|
const PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x01A0_0000);
|
|
|
|
|
|
|
|
|
|
@@ -66,6 +73,8 @@ pub struct PermissionServer {
|
|
|
|
|
deaths: CapSlot,
|
|
|
|
|
staged: Staged,
|
|
|
|
|
running: [Option<Running>; MAX_RUNNING],
|
|
|
|
|
/// Home's starts this session.
|
|
|
|
|
homes: u8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn refused(r: Refusal) -> [usize; 4] {
|
|
|
|
|
@@ -127,12 +136,12 @@ fn parts(page: &[u8]) -> impl Iterator<Item = &str> {
|
|
|
|
|
impl PermissionServer {
|
|
|
|
|
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] }
|
|
|
|
|
Self { store, consent, init, display, session, endpoint, deaths, staged: Staged::default(), running: [const { None }; MAX_RUNNING], homes: 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// What this device has a server for.
|
|
|
|
|
fn available(&self) -> Capabilities {
|
|
|
|
|
let mut c = Capabilities::NONE.with(Capability::Storage).with(Capability::Permissions);
|
|
|
|
|
let mut c = Capabilities::NONE.with(Capability::Storage).with(Capability::Permissions).with(Capability::Launch);
|
|
|
|
|
if self.display.is_some() {
|
|
|
|
|
c = c.with(Capability::Display);
|
|
|
|
|
}
|
|
|
|
|
@@ -177,7 +186,27 @@ impl PermissionServer {
|
|
|
|
|
*slot = p;
|
|
|
|
|
n += 1;
|
|
|
|
|
}
|
|
|
|
|
let args = &args[..n];
|
|
|
|
|
self.run(badge, &args[..n])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Starts Home, the session's desktop, unless it runs already or has
|
|
|
|
|
/// died too often.
|
|
|
|
|
pub fn start_home(&mut self) {
|
|
|
|
|
let running = self.running.iter().flatten().any(|r| r.id.as_str() == HOME);
|
|
|
|
|
if running || self.homes >= MAX_HOMES {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.homes += 1;
|
|
|
|
|
match self.run(NO_LAUNCHER, &[HOME]) {
|
|
|
|
|
Ok((process, _)) => {
|
|
|
|
|
syscall::drop_cap(process);
|
|
|
|
|
}
|
|
|
|
|
Err(words) => libkos::println!("permission: home did not start (refused: {})", words[0]),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Runs `args[0]` with `args` for the launcher behind `badge`.
|
|
|
|
|
fn run(&mut self, badge: usize, args: &[&str]) -> Result<(CapSlot, Capabilities), [usize; 4]> {
|
|
|
|
|
let command = *args.first().ok_or(code(reply::NOT_INSTALLED))?;
|
|
|
|
|
let available = self.available();
|
|
|
|
|
let plan = policy::plan_launch(&mut self.store, command, available).map_err(refused)?;
|
|
|
|
|
@@ -201,18 +230,27 @@ 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,
|
|
|
|
|
};
|
|
|
|
|
// Its brand colour too, which the display adapts to the theme.
|
|
|
|
|
if let (Some(w), Some(accent)) = (window, plan.app.accent) {
|
|
|
|
|
libkos::display::set_accent(w, accent.0);
|
|
|
|
|
}
|
|
|
|
|
// 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()
|
|
|
|
|
// Its brand colour too, which the display adapts to the theme, and
|
|
|
|
|
// what it is: Home, or an app the user may close.
|
|
|
|
|
if let Some(w) = window {
|
|
|
|
|
if let Some(accent) = plan.app.accent {
|
|
|
|
|
libkos::display::set_accent(w, accent.0);
|
|
|
|
|
}
|
|
|
|
|
let role = if plan.app.id.as_str() == HOME { display_role::HOME } else { display_role::APP };
|
|
|
|
|
libkos::display::set_role(w, role);
|
|
|
|
|
}
|
|
|
|
|
// Managing others, or launching them, through a copy of this
|
|
|
|
|
// endpoint that can do no more (see `MANAGE_BADGE`, `LAUNCH_BADGE`).
|
|
|
|
|
let bits = [(Capability::Permissions, wire::MANAGE_BADGE), (Capability::Launch, wire::LAUNCH_BADGE)]
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(c, _)| plan.granted.contains(*c))
|
|
|
|
|
.fold(0, |b, (_, bit)| b | bit);
|
|
|
|
|
let manage = match bits {
|
|
|
|
|
0 => None,
|
|
|
|
|
bits => {
|
|
|
|
|
let perms = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
|
|
|
|
|
syscall::mint(self.endpoint, types::badge::Badge::new(bits | 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);
|
|
|
|
|
@@ -319,8 +357,9 @@ impl PermissionServer {
|
|
|
|
|
code(reply::OK)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Apps behind `bits` died: their data grants go back.
|
|
|
|
|
/// Apps behind `bits` died: their data grants go back. Home comes back.
|
|
|
|
|
fn reap(&mut self, bits: usize) {
|
|
|
|
|
let home_died = self.running.iter().enumerate().any(|(i, r)| bits & (1 << i) != 0 && r.as_ref().is_some_and(|r| r.id.as_str() == HOME));
|
|
|
|
|
for (i, slot) in self.running.iter_mut().enumerate() {
|
|
|
|
|
if bits & (1 << i) == 0 {
|
|
|
|
|
continue;
|
|
|
|
|
@@ -334,6 +373,9 @@ impl PermissionServer {
|
|
|
|
|
syscall::drop_cap(r.process);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if home_died {
|
|
|
|
|
self.start_home();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -407,9 +449,14 @@ fn close_window(window: Option<CapSlot>) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether `op` may come through the copy of this endpoint badged `badge`:
|
|
|
|
|
/// a management copy (`permissions`) decides, never installs or launches.
|
|
|
|
|
/// a management copy (`permissions`) decides, never installs or launches;
|
|
|
|
|
/// a launcher's (`launch`) lists and launches, passing nothing along.
|
|
|
|
|
pub fn allowed(badge: usize, op: usize) -> bool {
|
|
|
|
|
badge & wire::MANAGE_BADGE == 0 || matches!(op, wire::LIST | wire::APPS | wire::REVOKE | wire::ALLOW)
|
|
|
|
|
let (manage, launch) = (badge & wire::MANAGE_BADGE != 0, badge & wire::LAUNCH_BADGE != 0);
|
|
|
|
|
if !manage && !launch {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
(manage && matches!(op, wire::LIST | wire::APPS | wire::REVOKE | wire::ALLOW)) || (launch && matches!(op, wire::APPS | wire::LAUNCH))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn revoke(cap: Option<CapSlot>) {
|
|
|
|
|
|