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:
2026-10-28 01:08:50 +01:00
parent 29a25bd8c2
commit 91f7d28834
38 changed files with 1156 additions and 378 deletions

View File

@@ -7,6 +7,8 @@ use crate::grants::Grants;
use crate::protocol::{DIR_ENTRY_SIZE, DIR_NAME_SIZE, MAX_PATH, StorageRequest, StorageResponse};
use crate::scope::Scope;
mod session;
const MAX_CLIENTS: usize = 8;
/// Handles at or past this are never valid (the file systems hand out
/// fewer), so ownership fits a small table.
@@ -54,6 +56,8 @@ pub struct StorageServer<F: FileSystem, H: Host> {
/// init; handed to the file system (and forgotten here) once all in.
key: [Option<[usize; 2]>; 2],
seed: [Option<[usize; 2]>; 2],
/// Whose areas they open, said first.
user: Option<types::user::UserFolder>,
unlocked: bool,
}
@@ -68,6 +72,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
log: None,
key: [None; 2],
seed: [None; 2],
user: None,
unlocked: false,
}
}
@@ -91,27 +96,6 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
}
}
/// Only the unbadged holder (init), each half only once: nobody can
/// replace the key or the seed later.
fn set_half(&mut self, badge: usize, seed: bool, half: usize, words: [usize; 2]) -> StorageResponse {
let halves = if seed { &mut self.seed } else { &mut self.key };
match (badge, self.unlocked, halves.get_mut(half)) {
(0, false, Some(slot @ None)) => *slot = Some(words),
_ => return StorageResponse::Error,
}
if let ([Some(k0), Some(k1)], [Some(s0), Some(s1)]) = (self.key, self.seed) {
let bytes = |w: [usize; 4]| -> [u8; 32] { core::array::from_fn(|i| w[i / 8].to_le_bytes()[i % 8]) };
let (mut master, mut seed) = (bytes([k0[0], k0[1], k1[0], k1[1]]), bytes([s0[0], s0[1], s1[0], s1[1]]));
self.fs.unlock(&master, &seed);
master.fill(0);
seed.fill(0);
self.key = [None; 2];
self.seed = [None; 2];
self.unlocked = true;
}
StorageResponse::Ok
}
/// Whether the encrypted areas are open: the key and seed are in.
pub fn unlocked(&self) -> bool {
self.unlocked
@@ -300,7 +284,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
None => R::NotFound,
};
let response = match request {
Attach | SetLog | SetKey { .. } | SetSeed { .. } | Release => R::Error,
Attach | SetLog | SetKey { .. } | SetSeed { .. } | SetUser { .. } | Lock | Purge { .. } | Release => R::Error,
Grant { path_len, write } => {
let narrower = scope.narrow(&buf[sent(path_len)], write);
return match narrower.and_then(|s| grants.add(badge, s, |b| host.mint(b))) {
@@ -412,6 +396,9 @@ impl<F: FileSystem, H: Host> Server for StorageServer<F, H> {
Some(StorageRequest::SetLog) => (self.set_log(badge, cap).encode(), None),
Some(StorageRequest::SetKey { half, words }) => (self.set_half(badge, false, half, words).encode(), None),
Some(StorageRequest::SetSeed { half, words }) => (self.set_half(badge, true, half, words).encode(), None),
Some(StorageRequest::SetUser { id, guest }) => (self.set_user(badge, id, guest).encode(), None),
Some(StorageRequest::Lock) => (self.lock(badge).encode(), None),
Some(StorageRequest::Purge { path_len }) => (self.purge(badge, path_len).encode(), None),
// A badge storage never issued, or one it has taken back.
Some(request) => match self.grants.scope(badge) {
None => (StorageResponse::Denied.encode(), None),