228 lines
8.5 KiB
Rust
228 lines
8.5 KiB
Rust
//! The `Store` port on the storage server, through two grants: the
|
|
//! signed-in user's folder (their apps in `apps/<id>/`, each app's data in
|
|
//! `data/<id>/`, their decisions in `permissions/`) and, read-only, the
|
|
//! system image's apps (`/apps/<id>/`), which every user shares.
|
|
|
|
use libkos::fs::Storage;
|
|
use manifest::{AppId, Decision, MAX_DECISION_TEXT, Manifest};
|
|
use types::address::VirtualAddress;
|
|
use types::bounded::CapSlot;
|
|
|
|
use crate::policy::{MAX_MANIFEST, Store};
|
|
|
|
const APPS: &str = "apps";
|
|
const DATA: &str = "data";
|
|
const DECISIONS: &str = "permissions";
|
|
const MANIFEST: &str = "manifest.toml";
|
|
/// Apps `find` looks through.
|
|
pub const MAX_APPS: usize = 32;
|
|
const LONGEST_PATH: usize = 160;
|
|
const COPY_CHUNK: usize = 4096;
|
|
|
|
/// `parts` (the empty ones skipped) joined with `/`, in `buf`.
|
|
fn path<'a>(buf: &'a mut [u8; LONGEST_PATH], parts: &[&str]) -> Option<&'a [u8]> {
|
|
let mut len = 0;
|
|
for p in parts.iter().filter(|p| !p.is_empty()) {
|
|
let sep = usize::from(len > 0);
|
|
let end = len + sep + p.len();
|
|
let room = buf.get_mut(len..end)?;
|
|
room[..sep].copy_from_slice(&b"/"[..sep]);
|
|
room[sep..].copy_from_slice(p.as_bytes());
|
|
len = end;
|
|
}
|
|
Some(&buf[..len])
|
|
}
|
|
|
|
/// All of the file at `path` into `buf`, as text.
|
|
fn read_text<'a>(fs: &mut Storage, path: &[u8], buf: &'a mut [u8]) -> Option<&'a str> {
|
|
let h = fs.open(path).ok()?;
|
|
let size = fs.file_size(h).ok().filter(|s| *s <= buf.len());
|
|
let read = size.and_then(|s| fs.read(h, &mut buf[..s]).ok());
|
|
let _ = fs.close(h);
|
|
let n = read.filter(|r| Some(*r) == size)?;
|
|
core::str::from_utf8(&buf[..n]).ok()
|
|
}
|
|
|
|
/// Copies the file at `from` (in `src`) to `to` (in `dst`), replacing
|
|
/// what an earlier, failed attempt left there.
|
|
fn copy(src: &mut Storage, from: &[u8], dst: &mut Storage, to: &[u8]) -> bool {
|
|
let Ok(input) = src.open(from) else { return false };
|
|
let _ = dst.delete(to);
|
|
let Ok(output) = dst.create(to) else {
|
|
let _ = src.close(input);
|
|
return false;
|
|
};
|
|
let mut chunk = [0u8; COPY_CHUNK];
|
|
let ok = loop {
|
|
match src.read(input, &mut chunk) {
|
|
Ok(0) => break true,
|
|
Ok(n) if dst.write(output, &chunk[..n]) == Ok(n) => {}
|
|
_ => break false,
|
|
}
|
|
};
|
|
let _ = src.close(input);
|
|
let _ = dst.close(output);
|
|
ok
|
|
}
|
|
|
|
/// Where an app is installed: on the system image, or by the user.
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum Shelf {
|
|
System,
|
|
User,
|
|
}
|
|
|
|
pub struct DiskStore {
|
|
/// The signed-in user's folder.
|
|
user: Storage,
|
|
/// The system image's apps, read-only.
|
|
system: Storage,
|
|
/// Where an offered app folder's grant is mapped while it is read.
|
|
source_va: VirtualAddress,
|
|
}
|
|
|
|
impl DiskStore {
|
|
pub fn new(user: Storage, system: Storage, source_va: VirtualAddress) -> Self {
|
|
Self { user, system, source_va }
|
|
}
|
|
|
|
/// The storage holding `shelf`, and its apps' folder in it.
|
|
fn on(&mut self, shelf: Shelf) -> (&mut Storage, &'static str) {
|
|
match shelf {
|
|
Shelf::System => (&mut self.system, ""),
|
|
Shelf::User => (&mut self.user, APPS),
|
|
}
|
|
}
|
|
|
|
/// The ids of the installed apps (folder names), the system's first.
|
|
pub fn ids(&mut self, mut each: impl FnMut(AppId)) {
|
|
for shelf in [Shelf::System, Shelf::User] {
|
|
let (fs, apps) = self.on(shelf);
|
|
let _ = fs.list_dir(apps.as_bytes(), |e| {
|
|
if let Some(id) = core::str::from_utf8(e.name()).ok().and_then(AppId::new) {
|
|
each(id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Which shelf holds `id`'s manifest; the system's wins, so a user
|
|
/// cannot stand in for a system app.
|
|
fn shelf(&mut self, id: &AppId) -> Option<Shelf> {
|
|
[Shelf::System, Shelf::User].into_iter().find(|shelf| {
|
|
let (fs, apps) = self.on(*shelf);
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
path(&mut p, &[apps, id.as_str(), MANIFEST]).is_some_and(|p| fs.open(p).and_then(|h| fs.close(h)).is_ok())
|
|
})
|
|
}
|
|
|
|
/// A read-only grant to `app`'s binary, for init to load it from.
|
|
pub fn binary_grant(&mut self, app: &Manifest) -> Option<CapSlot> {
|
|
let shelf = self.shelf(&app.id)?;
|
|
let (fs, apps) = self.on(shelf);
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
fs.grant(path(&mut p, &[apps, app.id.as_str(), app.binary.as_str()])?, false).ok()
|
|
}
|
|
|
|
/// A read-write grant to `id`'s own data folder, for the app.
|
|
pub fn data_grant(&mut self, id: &AppId) -> Option<CapSlot> {
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
self.user.grant(path(&mut p, &[DATA, id.as_str()])?, true).ok()
|
|
}
|
|
|
|
fn with_source<T>(&self, source: CapSlot, f: impl FnOnce(&mut Storage) -> T) -> Option<T> {
|
|
let mut src = Storage::attach(source, self.source_va).ok()?;
|
|
let out = f(&mut src);
|
|
src.detach();
|
|
Some(out)
|
|
}
|
|
}
|
|
|
|
impl Store for DiskStore {
|
|
/// A read-only storage grant to the offered app's folder.
|
|
type Source = CapSlot;
|
|
|
|
fn source_manifest<'a>(&mut self, source: &CapSlot, buf: &'a mut [u8]) -> Option<&'a str> {
|
|
let mut text = [0u8; MAX_MANIFEST];
|
|
let n = self.with_source(*source, |src| read_text(src, MANIFEST.as_bytes(), &mut text).map(str::len))??;
|
|
buf.get_mut(..n)?.copy_from_slice(&text[..n]);
|
|
core::str::from_utf8(&buf[..n]).ok()
|
|
}
|
|
|
|
fn is_installed(&mut self, id: &AppId) -> bool {
|
|
self.shelf(id).is_some()
|
|
}
|
|
|
|
fn install(&mut self, source: &CapSlot, app: &Manifest, data_folder: bool) -> bool {
|
|
let id = app.id.as_str();
|
|
let (mut folder, mut to_manifest, mut to_binary) = ([0u8; LONGEST_PATH], [0u8; LONGEST_PATH], [0u8; LONGEST_PATH]);
|
|
let (Some(folder), Some(to_manifest), Some(to_binary)) = (
|
|
path(&mut folder, &[APPS, id]),
|
|
path(&mut to_manifest, &[APPS, id, MANIFEST]),
|
|
path(&mut to_binary, &[APPS, id, app.binary.as_str()]),
|
|
) else {
|
|
return false;
|
|
};
|
|
// There already if an earlier attempt failed part way; an app
|
|
// counts as installed only once its manifest is in.
|
|
let _ = self.user.mkdir(folder);
|
|
let fs = &mut self.user;
|
|
let copied = Storage::attach(*source, self.source_va).ok().map(|mut src| {
|
|
// The binary first: an app folder with a manifest is installed.
|
|
let ok = copy(&mut src, app.binary.as_str().as_bytes(), fs, to_binary)
|
|
&& copy(&mut src, MANIFEST.as_bytes(), fs, to_manifest);
|
|
src.detach();
|
|
ok
|
|
});
|
|
if copied != Some(true) {
|
|
return false;
|
|
}
|
|
let mut data = [0u8; LONGEST_PATH];
|
|
// Kept from an earlier install if there is one: the app's data.
|
|
!data_folder || path(&mut data, &[DATA, id]).is_some_and(|d| self.user.mkdir(d).is_ok() || self.user.list_dir(d, |_| {}).is_ok())
|
|
}
|
|
|
|
fn manifest(&mut self, id: &AppId) -> Option<Manifest> {
|
|
let shelf = self.shelf(id)?;
|
|
let (fs, apps) = self.on(shelf);
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
let p = path(&mut p, &[apps, id.as_str(), MANIFEST])?;
|
|
let mut text = [0u8; MAX_MANIFEST];
|
|
let m = Manifest::parse(read_text(fs, p, &mut text)?).ok()?;
|
|
// A manifest in the wrong folder is no app.
|
|
(m.id == *id).then_some(m)
|
|
}
|
|
|
|
fn find(&mut self, command: &str) -> Option<AppId> {
|
|
let mut ids = [None; MAX_APPS];
|
|
let mut n = 0;
|
|
self.ids(|id| {
|
|
if let Some(slot) = ids.get_mut(n) {
|
|
*slot = Some(id);
|
|
n += 1;
|
|
}
|
|
});
|
|
ids.into_iter().flatten().find(|id| self.manifest(id).is_some_and(|m| m.command.as_str() == command))
|
|
}
|
|
|
|
fn decision(&mut self, id: &AppId) -> Option<Decision> {
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
let p = path(&mut p, &[DECISIONS, id.as_str()])?;
|
|
let mut text = [0u8; MAX_DECISION_TEXT];
|
|
Decision::parse(read_text(&mut self.user, p, &mut text)?).ok()
|
|
}
|
|
|
|
fn record(&mut self, id: &AppId, decision: &Decision) -> bool {
|
|
let mut p = [0u8; LONGEST_PATH];
|
|
let Some(p) = path(&mut p, &[DECISIONS, id.as_str()]) else { return false };
|
|
let mut text = [0u8; MAX_DECISION_TEXT];
|
|
let n = decision.write(&mut text);
|
|
// Replaced whole: there is no truncate.
|
|
let _ = self.user.delete(p);
|
|
let Ok(h) = self.user.create(p) else { return false };
|
|
let written = self.user.write(h, &text[..n]) == Ok(n);
|
|
let _ = self.user.close(h);
|
|
written
|
|
}
|
|
}
|