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

@@ -24,3 +24,4 @@ pub mod storage;
pub mod syscall_abi;
pub mod terminal;
pub mod time;
pub mod user;

View File

@@ -17,6 +17,12 @@ pub const STACK_TOP: usize = 0x10000;
/// A slot word meaning "none".
pub const NONE: usize = usize::MAX;
/// Sent one way (no reply: the sender is about to be stopped) through the
/// endpoint copy init gives a session's shell: the user signs out.
pub const SIGN_OUT: usize = 1;
/// The badge on that copy; spawn clients' badges are small indices.
pub const SESSION_BADGE: usize = 0x5E55;
pub mod reply {
pub const OK: usize = 0;
/// The grant names no file init can read.

View File

@@ -41,6 +41,17 @@ pub mod op {
pub const SET_SEED: usize = 14;
/// Removes the (empty) directory at the path in the buffer.
pub const RMDIR: usize = 15;
/// Whose areas the key opens (init only, before the key): word 1 the
/// user id as the gatekeeper sent it, word 2 non-zero for a guest. See
/// `types::user`.
pub const SET_USER: usize = 16;
/// The user signed out (init only): every grant is taken back, every
/// file closed, the key forgotten; a new user, key and seed may follow.
pub const LOCK: usize = 17;
/// Removes the tree at the path in the buffer, which must be in
/// `users/`, without its key (init only, while locked): a removed
/// user's or a guest's files.
pub const PURGE: usize = 18;
}
/// Reply tags (word 0).

View File

@@ -12,8 +12,11 @@ pub const READ_SECRET: usize = 14;
/// that drew on the screen itself.
pub const REDRAW: usize = 12;
/// Carries a notification the terminal signals on Ctrl+C while no line is
/// being read. Accepted once.
/// being read. Accepted once a session (see `SIGNED_OUT`).
pub const ON_INTERRUPT: usize = 13;
/// From the unbadged holder (init) only, at sign-out: forgets who
/// `ON_INTERRUPT` signals, so the next session's shell may register.
pub const SIGNED_OUT: usize = 15;
/// What Ctrl+C types.
pub const CTRL_C: u8 = 3;

45
crates/types/src/user.rs Normal file
View File

@@ -0,0 +1,45 @@
//! Where a user's files live: `users/<folder>/`, the folder named by the
//! user's id in hex (as the gatekeeper hands it over), or `guest`. Shared by
//! init and storage so the two name the same place.
/// A user folder's name.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct UserFolder {
bytes: [u8; 16],
len: usize,
}
impl UserFolder {
pub const GUEST: Self = Self {
bytes: *b"guest\0\0\0\0\0\0\0\0\0\0\0",
len: 5,
};
/// The folder of the user whose id the gatekeeper sent as `id`.
pub fn of(id: u64) -> Self {
let raw = id.to_le_bytes();
let digits = b"0123456789abcdef";
let bytes =
core::array::from_fn(|i| digits[(raw[i / 2] >> (4 * (1 - i % 2)) & 0xF) as usize]);
Self { bytes, len: 16 }
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn folders_are_hex_ids_or_guest() {
assert_eq!(
UserFolder::of(0x0123_4567_89ab_cdef).as_bytes(),
b"efcdab8967452301"
);
assert_eq!(UserFolder::GUEST.as_bytes(), b"guest");
assert_ne!(UserFolder::of(1), UserFolder::of(256));
}
}