//! The permission manager's client (protocol: `types::apps`): run, install //! and manage apps. Text goes through a request page this process owns and //! lends with each call. use crate::syscall::{self, Give, Received}; use crate::window::{self, Window}; use types::address::{ByteOffset, VirtualAddress}; use types::apps::{self as wire, PAGE_SIZE, reply}; use types::bounded::{CapSlot, PageCount}; use types::mapping::PagePerms; /// What the permission manager refused with: a `types::apps::reply` code /// and its word (the capability bits it is about, or a manifest line). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Refused { pub code: usize, pub detail: usize, } pub type Result = core::result::Result; const FAILED: Refused = Refused { code: reply::FAILED, detail: 0 }; pub struct Apps { ep: CapSlot, page_cap: CapSlot, page: Window, } impl Apps { /// Talks to the permission manager behind `ep`, with a request page /// mapped at `va`. pub fn connect(ep: CapSlot, va: VirtualAddress) -> Option { let page_cap = syscall::create_region(PageCount::new(1)?).ok()?; let page = window::map(page_cap, va, PagePerms::ReadWrite).ok()?; Some(Self { ep, page_cap, page }) } fn call(&self, words: [usize; 4], cap: Option) -> Result<(usize, Option)> { let r = syscall::call(self.ep, words, cap).map_err(|_| FAILED)?; let ([code, detail, _, _], cap) = match r { Received::Message { words, cap, .. } | Received::Call { words, cap, .. } => (words, cap), Received::Signal { .. } => return Err(FAILED), }; match code { reply::OK => Ok((detail, cap)), code => Err(Refused { code, detail }), } } /// Lends the request page for one call. fn with_page(&self, op: usize, word: usize) -> Result<(usize, Option)> { self.call([op, word, 0, 0], Some(self.page_cap.into())) } /// `parts`, NUL-separated and ending in an empty one, into the page. fn put(&self, parts: &[&str]) -> Result<()> { let need: usize = parts.iter().map(|p| p.len() + 1).sum::() + 1; if need > PAGE_SIZE || parts.iter().any(|p| p.contains('\0')) { return Err(FAILED); } let mut at = ByteOffset::ZERO; for p in parts { self.page.copy_from(at, p.as_bytes()); self.page.write(at + p.len(), 0u8); at = at + p.len() + 1; } self.page.write(at, 0u8); Ok(()) } /// Runs `args[0]` with `args`, printing to `output`, working in /// `folder`; the running process (to wait on and kill) and the bits of /// what it was granted. Both caps go with Delegate kept, so the /// permission manager can hand them on. pub fn launch(&self, args: &[&str], output: Option, folder: Option) -> Result<(CapSlot, u32)> { for (op, cap) in [(wire::PASS_OUTPUT, output), (wire::PASS_FOLDER, folder)] { if let Some(cap) = cap { self.call([op, 0, 0, 0], Some(Give::keeping_delegate(cap)))?; } } self.put(args)?; match self.with_page(wire::LAUNCH, 0)? { (granted, Some(process)) => Ok((process, granted as u32)), (_, None) => Err(FAILED), } } /// Installs the app in `source` (a read-only grant to its folder), /// asking the owner; the bits of what it was granted. pub fn install(&self, source: CapSlot) -> Result { self.call([wire::INSTALL, 0, 0, 0], Some(source.into())).map(|(bits, _)| bits as u32) } /// One line per installed app, into `out`; how much. pub fn list(&self, out: &mut [u8]) -> Result { let (len, _) = self.with_page(wire::LIST, 0)?; let len = len.min(out.len()).min(PAGE_SIZE); self.page.copy_to(ByteOffset::ZERO, &mut out[..len]); Ok(len) } /// Every installed app, for a program to read (see `entries`), into /// `out`; how much. pub fn apps(&self, out: &mut [u8]) -> Result { let (len, _) = self.with_page(wire::APPS, 0)?; let len = len.min(out.len()).min(PAGE_SIZE); self.page.copy_to(ByteOffset::ZERO, &mut out[..len]); Ok(len) } /// Takes the capability with catalog index `capability` away from app /// `id` (stopping it if it runs with it). pub fn revoke(&self, id: &str, capability: usize) -> Result<()> { self.put(&[id])?; self.with_page(wire::REVOKE, capability).map(|_| ()) } /// Gives app `id` the capability with catalog index `capability`, which /// it asked for. pub fn allow(&self, id: &str, capability: usize) -> Result<()> { self.put(&[id])?; self.with_page(wire::ALLOW, capability).map(|_| ()) } } /// One installed app, as `Apps::apps` lists it. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AppEntry<'a> { pub id: &'a str, pub name: &'a str, /// Capability bits (`manifest::Capabilities`). pub required: u32, pub optional: u32, pub granted: u32, } /// The apps in a listing; a line that does not parse is skipped. pub fn entries(text: &str) -> impl Iterator> { text.lines().filter_map(|line| { let mut f = line.split('\t'); let (id, name) = (f.next()?, f.next()?); let mut bits = || f.next()?.parse().ok(); Some(AppEntry { id, name, required: bits()?, optional: bits()?, granted: bits()? }) }) } #[cfg(test)] mod tests { use super::*; #[test] fn a_listing_parses_and_bad_lines_are_skipped() { let text = "kos.snake\tSnake\t1\t0\t1\nnonsense\nkos.notes\tNotes\t0\t3\t2\n"; let all: alloc::vec::Vec<_> = entries(text).collect(); assert_eq!(all.len(), 2); assert_eq!(all[1], AppEntry { id: "kos.notes", name: "Notes", required: 0, optional: 3, granted: 2 }); } }