160 lines
5.8 KiB
Rust
160 lines
5.8 KiB
Rust
//! 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<T> = core::result::Result<T, Refused>;
|
|
|
|
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<Self> {
|
|
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<Give>) -> Result<(usize, Option<CapSlot>)> {
|
|
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<CapSlot>)> {
|
|
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::<usize>() + 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<CapSlot>, folder: Option<CapSlot>) -> 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<u32> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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<Item = AppEntry<'_>> {
|
|
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 });
|
|
}
|
|
}
|