feat(settings): settings app: apps list + per-capability allow/revoke, about (who/role), sign out; system-only caps permissions/session (install refused); pm MANAGE_BADGE copy (list/apps/allow/revoke only), APPS op; init WHO + session pass-on; factory decisions seeded for new system apps; k-ui list_item/toggle, screens/back/scroll

This commit is contained in:
2026-10-28 13:14:35 +01:00
parent a2d4a7d085
commit 29becf297c
38 changed files with 947 additions and 85 deletions

View File

@@ -109,6 +109,19 @@ fn copy_tree(fs: &mut Storage, from: &Path, to: &Path, buf: &mut [u8]) -> Result
Ok(())
}
/// Copies the files in `from` that `to` lacks (no folders: decisions are
/// one file per app).
fn add_missing(fs: &mut Storage, from: &Path, to: &Path, buf: &mut [u8]) -> Result<(), &'static str> {
let Some((list, n)) = entries(fs, from) else { return Ok(()) };
for entry in list[..n].iter().filter(|e| !e.is_dir) {
let dst = to.join(entry.name()).ok_or("path too long")?;
if fs.open(dst.as_bytes()).and_then(|h| fs.close(h)).is_err() {
copy_file(fs, &from.join(entry.name()).ok_or("path too long")?, &dst, buf)?;
}
}
Ok(())
}
/// Readies `folder`'s areas once storage has its key.
pub(crate) fn prepare(fs: &mut Storage, folder: types::user::UserFolder, buf: &mut [u8]) -> Result<(), &'static str> {
let name = core::str::from_utf8(folder.as_bytes()).map_err(|_| "a user folder named oddly")?;
@@ -121,6 +134,11 @@ pub(crate) fn prepare(fs: &mut Storage, folder: types::user::UserFolder, buf: &m
for (area, factory) in AREAS {
let path = base.join(area.as_bytes()).ok_or("path too long")?;
if fs.list_dir(path.as_bytes(), |_| {}).is_ok() {
if area == "permissions" {
// A system app new since this folder was made comes with
// its decision; one the user changed is left as it is.
add_missing(fs, &Path::new(factory), &path, buf)?;
}
continue;
}
fs.mkdir(path.as_bytes()).map_err(|_| "cannot make an area")?;

View File

@@ -164,11 +164,15 @@ impl<'a> Launcher<'a> {
let control = crate::devices::system_control().ok_or("no SystemControl to give")?;
syscall::delegate(control, child, perms(&[Permission::Execute])).ok_or("cannot give SystemControl")?;
}
if p.signs_out {
if p.session != crate::manifest::Session::No {
let badge = Badge::new(types::spawn::SESSION_BADGE);
// Delegate only to hand it over: the shell's copy cannot pass it on.
let copy = syscall::mint(self.spawn_ep, badge, perms(&[Permission::Execute, Permission::Delegate])).map_err(|_| "cannot mint")?;
let given = syscall::delegate(copy, child, perms(&[Permission::Execute]));
// Only a program that passes it on may.
let rights = match p.session {
crate::manifest::Session::PassOn => perms(&[Permission::Execute, Permission::Delegate]),
_ => perms(&[Permission::Execute]),
};
let given = syscall::delegate(copy, child, rights);
syscall::drop_cap(copy);
given.ok_or("cannot give signing out")?;
}

View File

@@ -2,6 +2,16 @@
use types::budget::Limits;
/// Whether a program holds the session: who is signed in, signing out.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Session {
No,
/// For itself only (the shell).
Own,
/// To pass on to apps granted `session` (the permission manager).
PassOn,
}
/// A board device init hands to a program over its endpoint.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Device {
@@ -49,8 +59,8 @@ pub struct Program {
pub spawns: Option<Limits>,
/// May halt the machine.
pub system_control: bool,
/// May sign the user out (`types::spawn::SIGN_OUT`), in the next slot.
pub signs_out: bool,
/// The session (`types::spawn::SIGN_OUT`, `WHO`), in the next slot.
pub session: Session,
/// Takes Ctrl+C: a terminal copy with `types::terminal::CONTROL`, last.
pub takes_ctrl_c: bool,
/// Started again if it dies (see init's supervise.rs).
@@ -84,7 +94,7 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
console: false,
spawns: None,
system_control: false,
signs_out: false,
session: Session::No,
takes_ctrl_c: false,
restart: true,
restarts_clients: false,
@@ -141,6 +151,7 @@ pub const SESSION: &[Program] = &[Program {
spawns: Some(Limits { pages: 2048, endpoints: 8, notifications: 8, regions: 8 }),
// The shell holds handles to the apps it ran, which die with it.
restarts_clients: true,
session: Session::PassOn,
// Installing waits for the user's consent.
pinged: false,
..server("permission", b"bin/permission", Limits { pages: 96, ..SMALL })
@@ -167,7 +178,7 @@ pub const DESKTOP: &[Program] = &[Program {
serves: false,
uses: &["terminal", "permission"],
storage: &[users(b"home", true)],
signs_out: true,
session: Session::Own,
takes_ctrl_c: true,
..server("shell", b"bin/shell", SMALL)
}];

View File

@@ -22,12 +22,25 @@ const LONGEST_NAME: usize = 16;
pub(crate) struct User {
pub(crate) folder: UserFolder,
pub(crate) guest: bool,
name: [u8; LONGEST_NAME],
name_len: usize,
/// As `types::spawn::role` says.
role: usize,
}
impl User {
/// The reply to `types::spawn::WHO`.
pub(crate) fn who(&self) -> [usize; 4] {
let [a, b, c] = types::name::pack(&self.name[..self.name_len]);
[self.role, a, b, c]
}
}
/// All the gatekeeper hands over.
struct Handover {
flags: usize,
id: u64,
role: usize,
key: [usize; 4],
seed: [usize; 4],
name: [u8; LONGEST_NAME],
@@ -44,10 +57,10 @@ impl Drop for Handover {
/// Waits for the gatekeeper behind `ep` to let someone in.
fn ask(ep: CapSlot) -> Result<Handover, &'static str> {
let [reply::OK, flags, id, _role] = syscall::request(ep, [UNLOCK, 0, 0, 0]).map_err(|_| "the gatekeeper is gone")? else {
let [reply::OK, flags, id, role] = syscall::request(ep, [UNLOCK, 0, 0, 0]).map_err(|_| "the gatekeeper is gone")? else {
return Err("the gatekeeper could not unlock");
};
let mut h = Handover { flags, id: id as u64, key: [0; 4], seed: [0; 4], name: [0; LONGEST_NAME], name_len: 0, removed: [None; MAX_REMOVED] };
let mut h = Handover { flags, id: id as u64, role, key: [0; 4], seed: [0; 4], name: [0; LONGEST_NAME], name_len: 0, removed: [None; MAX_REMOVED] };
h.key = syscall::request(ep, [KEY, 0, 0, 0]).map_err(|_| "the gatekeeper gave no key")?;
h.seed = syscall::request(ep, [SEED, 0, 0, 0]).map_err(|_| "the gatekeeper gave no seed")?;
let name = syscall::request(ep, [NAME, 0, 0, 0]).map_err(|_| "the gatekeeper gave no name")?;
@@ -111,7 +124,12 @@ pub(crate) fn sign_in(l: &mut Launcher) -> Result<(), &'static str> {
if let Some(display) = l.endpoint("display") {
crate::output::post(display, libkos::display::set_user_words(name.as_bytes()));
}
l.user = Some(User { folder, guest });
let role = match (guest, h.role) {
(true, _) => types::spawn::role::GUEST,
(_, unlocked::OWNER) => types::spawn::role::OWNER,
_ => types::spawn::role::STANDARD,
};
l.user = Some(User { folder, guest, name: h.name, name_len: h.name_len, role });
start_session(l);
Ok(())
}

View File

@@ -182,10 +182,14 @@ impl Server for Launcher<'_> {
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
if badge == types::spawn::SESSION_BADGE {
if words[0] == types::spawn::SIGN_OUT {
crate::session::sign_out(self);
}
return ([MALFORMED, 0, 0, 0], None);
return match words[0] {
types::spawn::SIGN_OUT => {
crate::session::sign_out(self);
([MALFORMED, 0, 0, 0], None)
}
types::spawn::WHO => (self.user.map_or([MALFORMED, 0, 0, 0], |u| u.who()), None),
_ => ([MALFORMED, 0, 0, 0], None),
};
}
match (Request::decode(words), cap) {
(Some(r), Some(binary)) => {