feat(apps): permission manager equips apps (manifest+consent+decisions in /system), launches via init w/ file grant; shell unprivileged (/home); install/apps/allow/revoke; revoke stops running app; app-image tool; counter app
This commit is contained in:
64
crates/tools/app-image/src/main.rs
Normal file
64
crates/tools/app-image/src/main.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
//! Puts apps on a disk image being built (see docs/apps.md), checked by
|
||||
//! the same strict parser the permission manager uses:
|
||||
//!
|
||||
//! app-image install <root> <manifest.toml> <binary>
|
||||
//! an installed, pre-approved app: /apps/<id>/, its decision granting
|
||||
//! everything it asks for, and /data/<id>/ if that includes storage
|
||||
//! app-image stage <folder> <manifest.toml> <binary>
|
||||
//! an app folder the owner can install later (`install <folder>`)
|
||||
|
||||
use manifest::{Capability, Decision, MAX_DECISION_TEXT, Manifest};
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
use std::{env, fs};
|
||||
|
||||
fn read(manifest: &Path) -> Result<(Manifest, String), String> {
|
||||
let text = fs::read_to_string(manifest).map_err(|e| format!("{}: {e}", manifest.display()))?;
|
||||
let m = Manifest::parse(&text).map_err(|e| format!("{}: {e:?}", manifest.display()))?;
|
||||
Ok((m, text))
|
||||
}
|
||||
|
||||
/// The manifest and the binary (under the name the manifest gives it).
|
||||
fn put_app(folder: &Path, m: &Manifest, text: &str, binary: &Path) -> Result<(), String> {
|
||||
fs::create_dir_all(folder).map_err(|e| format!("{}: {e}", folder.display()))?;
|
||||
fs::write(folder.join("manifest.toml"), text).map_err(|e| e.to_string())?;
|
||||
fs::copy(binary, folder.join(m.binary.as_str()))
|
||||
.map_err(|e| format!("{}: {e}", binary.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install(root: &Path, manifest: &Path, binary: &Path) -> Result<(), String> {
|
||||
let (m, text) = read(manifest)?;
|
||||
put_app(&root.join("apps").join(m.id.as_str()), &m, &text, binary)?;
|
||||
// Whoever builds the image approves what its apps ask for.
|
||||
let decision = Decision {
|
||||
granted: m.requested(),
|
||||
};
|
||||
let mut out = [0u8; MAX_DECISION_TEXT];
|
||||
let n = decision.write(&mut out);
|
||||
let decisions = root.join("system/permissions");
|
||||
fs::create_dir_all(&decisions).map_err(|e| e.to_string())?;
|
||||
fs::write(decisions.join(m.id.as_str()), &out[..n]).map_err(|e| e.to_string())?;
|
||||
if decision.granted.contains(Capability::Storage) {
|
||||
fs::create_dir_all(root.join("data").join(m.id.as_str())).map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
let done = match args.iter().map(String::as_str).collect::<Vec<_>>().as_slice() {
|
||||
["install", root, manifest, binary] => install(Path::new(root), Path::new(manifest), Path::new(binary)),
|
||||
["stage", folder, manifest, binary] => {
|
||||
read(Path::new(manifest)).and_then(|(m, text)| put_app(Path::new(folder), &m, &text, Path::new(binary)))
|
||||
}
|
||||
_ => Err("usage: app-image install <root> <manifest> <binary> | stage <folder> <manifest> <binary>".into()),
|
||||
};
|
||||
match done {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("app-image: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user