feat(users): per-user sealed folders users/<id>/{home,apps,data,permissions}; storage SET_USER/LOCK/PURGE; init sign-in/sign-out loop (purge fresh/removed/guest, seed from factory, session start/stop); shell logout; pm user+system app shelves; terminal SIGNED_OUT; per-program stack (gatekeeper 12p); 128K load scratch
This commit is contained in:
163
crates/init/src/session.rs
Normal file
163
crates/init/src/session.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
//! Signing in and out (see the vision's Users note). The gatekeeper says
|
||||
//! who is in; init clears away what must go (everyone's files on a new
|
||||
//! keystore, a removed user's, the last guest's), hands storage the user
|
||||
//! and their key, readies their folder and starts their session. Signing
|
||||
//! out stops everything the session ran, locks storage, and the gatekeeper
|
||||
//! runs again.
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::syscall;
|
||||
use types::bounded::CapSlot;
|
||||
use types::gatekeeper::{KEY, NAME, REMOVED, SEED, UNLOCK, reply, unlocked};
|
||||
use types::user::UserFolder;
|
||||
|
||||
use crate::launch::Launcher;
|
||||
use crate::manifest::{GATEKEEPER, SESSION};
|
||||
|
||||
/// Users a keystore holds, so removed at one sign-in at most.
|
||||
const MAX_REMOVED: usize = 8;
|
||||
const LONGEST_NAME: usize = 16;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct User {
|
||||
pub(crate) folder: UserFolder,
|
||||
pub(crate) guest: bool,
|
||||
}
|
||||
|
||||
/// All the gatekeeper hands over.
|
||||
struct Handover {
|
||||
flags: usize,
|
||||
id: u64,
|
||||
key: [usize; 4],
|
||||
seed: [usize; 4],
|
||||
name: [u8; LONGEST_NAME],
|
||||
name_len: usize,
|
||||
removed: [Option<u64>; MAX_REMOVED],
|
||||
}
|
||||
|
||||
impl Drop for Handover {
|
||||
fn drop(&mut self) {
|
||||
self.key.fill(0);
|
||||
self.seed.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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] };
|
||||
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")?;
|
||||
if let Some((text, len)) = types::log::text_of(name) {
|
||||
h.name_len = len.min(LONGEST_NAME);
|
||||
h.name[..h.name_len].copy_from_slice(&text[..h.name_len]);
|
||||
}
|
||||
for slot in h.removed.iter_mut() {
|
||||
match syscall::request(ep, [REMOVED, 0, 0, 0]) {
|
||||
Ok([reply::OK, id, ..]) => *slot = Some(id as u64),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn user_path(folder: UserFolder, buf: &mut [u8; 32]) -> &[u8] {
|
||||
buf[..6].copy_from_slice(b"users/");
|
||||
let f = folder.as_bytes();
|
||||
buf[6..6 + f.len()].copy_from_slice(f);
|
||||
&buf[..6 + f.len()]
|
||||
}
|
||||
|
||||
/// Removes `folder`'s files; none there is fine.
|
||||
fn purge(fs: &mut Storage, folder: UserFolder) -> Result<(), &'static str> {
|
||||
fs.purge(user_path(folder, &mut [0; 32])).map_err(|_| "cannot remove a user's files")
|
||||
}
|
||||
|
||||
/// Runs the gatekeeper until someone is in and starts their session.
|
||||
pub(crate) fn sign_in(l: &mut Launcher) -> Result<(), &'static str> {
|
||||
libkos::print!("init: loading gatekeeper... ");
|
||||
let gatekeeper = l.launch(&GATEKEEPER)?;
|
||||
libkos::println!("ok{}", gatekeeper.note);
|
||||
let handover = gatekeeper.ep.ok_or("the gatekeeper serves nothing").and_then(ask);
|
||||
// It exits on its own once it has handed over.
|
||||
syscall::drop_cap(gatekeeper.process);
|
||||
let h = handover?;
|
||||
let guest = h.flags & unlocked::GUEST != 0;
|
||||
let folder = if guest { UserFolder::GUEST } else { UserFolder::of(h.id) };
|
||||
let (fs, scratch) = l.storage();
|
||||
// While storage is locked: it removes trees only then.
|
||||
if h.flags & unlocked::FRESH != 0 {
|
||||
fs.purge(b"users").map_err(|_| "cannot clear the users of an old keystore")?;
|
||||
libkos::println!("init: a new keystore, every earlier user's files removed");
|
||||
}
|
||||
for id in h.removed.iter().flatten() {
|
||||
purge(fs, UserFolder::of(*id))?;
|
||||
libkos::println!("init: a removed user's files removed");
|
||||
}
|
||||
if guest {
|
||||
purge(fs, UserFolder::GUEST)?;
|
||||
}
|
||||
let handed = fs.set_user(h.id, guest).is_ok()
|
||||
&& [(false, h.key), (true, h.seed)].iter().all(|(seed, w)| (0..2).all(|half| fs.set_half(*seed, half, [w[half * 2], w[half * 2 + 1]]).is_ok()));
|
||||
if !handed {
|
||||
return Err("storage would not take the key");
|
||||
}
|
||||
crate::factory::prepare(fs, folder, scratch)?;
|
||||
let name = core::str::from_utf8(&h.name[..h.name_len]).unwrap_or("someone");
|
||||
libkos::println!("init: {} is in", name);
|
||||
l.user = Some(User { folder, guest });
|
||||
start_session(l);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Starts what runs for a signed-in user, keeping each process to stop
|
||||
/// at sign-out.
|
||||
fn start_session(l: &mut Launcher) {
|
||||
let desktop = l.desktop;
|
||||
for p in SESSION.iter().chain(desktop) {
|
||||
libkos::print!("init: loading {}... ", p.name);
|
||||
match l.launch(p) {
|
||||
Ok(started) => {
|
||||
libkos::println!("ok{}", started.note);
|
||||
match l.running.iter_mut().find(|r| r.is_none()) {
|
||||
Some(free) => *free = Some(started.process),
|
||||
None => {
|
||||
// Untracked, it could not be stopped at sign-out.
|
||||
let _ = syscall::kill(started.process);
|
||||
syscall::drop_cap(started.process);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => libkos::println!("SKIP ({})", why),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the session, locks storage, and lets the next one in.
|
||||
pub(crate) fn sign_out(l: &mut Launcher) {
|
||||
l.spawner.clear();
|
||||
for process in l.running.iter_mut().filter_map(Option::take) {
|
||||
let _ = syscall::kill(process);
|
||||
syscall::drop_cap(process);
|
||||
}
|
||||
if let Some(terminal) = l.served("terminal") {
|
||||
let _ = syscall::request(terminal, [types::terminal::SIGNED_OUT, 0, 0, 0]);
|
||||
}
|
||||
let user = l.user.take();
|
||||
let (fs, _) = l.storage();
|
||||
let locked = fs.lock().is_ok();
|
||||
// A guest leaves nothing behind.
|
||||
let cleared = !user.is_some_and(|u| u.guest) || purge(fs, UserFolder::GUEST).is_ok();
|
||||
libkos::println!("init: signed out");
|
||||
let next = match locked && cleared {
|
||||
true => sign_in(l),
|
||||
false => Err("storage would not lock"),
|
||||
};
|
||||
if let Err(why) = next {
|
||||
libkos::println!("init: STOP ({})", why);
|
||||
crate::idle();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user