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

@@ -12,35 +12,45 @@ pub enum Device {
}
/// A storage grant: a folder (or the whole disk, `b""`) and whether it may
/// be changed.
/// be changed. With `user`, the path is inside the signed-in user's folder.
#[derive(Clone, Copy)]
pub struct Scope {
pub path: &'static [u8],
pub write: bool,
pub user: bool,
}
const fn disk(path: &'static [u8], write: bool) -> Scope {
Scope { path, write, user: false }
}
const fn users(path: &'static [u8], write: bool) -> Scope {
Scope { path, write, user: true }
}
pub struct Program {
pub name: &'static str,
pub file: &'static [u8],
pub limits: Limits,
/// Its stack, in pages.
pub stack_pages: usize,
/// Serves an endpoint, which it finds in slot 0.
pub serves: bool,
/// Handed over in this order, after it starts.
pub devices: &'static [Device],
/// Authenticates the owner: init waits for the master key before
/// starting anything that touches user data.
pub unlocks: bool,
/// Earlier programs' endpoints it may call, in its next slots; each
/// program's copy carries its own badge, so a server can tell them apart.
pub uses: &'static [&'static str],
/// Storage it may reach, in the slot after `uses`.
pub storage: Option<Scope>,
/// Storage it may reach, in the slots after `uses`.
pub storage: &'static [Scope],
/// Takes the console over from init; everyone else prints through it.
pub console: bool,
/// May have init spawn programs, whose limits together stay within this.
pub spawns: Option<Limits>,
/// May halt the machine.
pub system_control: bool,
/// May sign the user out (`types::spawn::SIGN_OUT`), in the last slot.
pub signs_out: bool,
}
/// Enough for a small program: its image, stack and a little heap.
@@ -56,14 +66,15 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
name,
file,
limits,
stack_pages: crate::load::STACK_PAGES,
serves: true,
devices: &[],
unlocks: false,
uses: &[],
storage: None,
storage: &[],
console: false,
spawns: None,
system_control: false,
signs_out: false,
}
}
@@ -80,46 +91,52 @@ pub const SERVERS: &[Program] = &[
},
// Text on the display, lines from the keyboard (it takes input focus).
Program { uses: &["display"], ..server("terminal", b"bin/terminal", SMALL) },
// Nothing that touches user data starts before the owner is in. It
// holds the screen (through the terminal), its keystore and randomness,
// and exits once it has handed over the key.
Program {
uses: &["terminal"],
devices: &[Device::Keystore, Device::Random],
unlocks: true,
// argon2's 16 MiB, its image and a little more.
..server("gatekeeper", b"bin/gatekeeper", Limits { pages: 4096 + 160, regions: 6, ..SMALL })
},
// The only one that equips apps (docs/apps.md): it reaches every app's
// files and the owner's decisions, and runs apps through init.
Program {
uses: &["terminal", "display"],
storage: Some(Scope { path: b"", write: true }),
// Room for a few apps at a time.
spawns: Some(Limits { pages: 512, endpoints: 8, notifications: 8, regions: 8 }),
..server("permission", b"bin/permission", Limits { pages: 96, ..SMALL })
},
server("session", b"bin/session", SMALL),
];
/// Started for each sign-in, before anything that touches a user's files.
/// It holds the screen (through the terminal), its keystore and randomness,
/// and exits once it has handed over who is in and their key.
pub const GATEKEEPER: Program = Program {
uses: &["terminal"],
devices: &[Device::Keystore, Device::Random],
// A user's record is KiBs, and it handles a few at once.
stack_pages: 12,
// argon2's 16 MiB, its image and a little more.
..server("gatekeeper", b"bin/gatekeeper", Limits { pages: 4096 + 160, regions: 6, ..SMALL })
};
/// Started for each signed-in user, and stopped when they sign out. The
/// permission manager is the only one that equips apps (docs/apps.md): it
/// reaches the user's apps, their data and decisions, and the system's apps
/// (read-only), and runs apps through init.
pub const SESSION: &[Program] = &[Program {
uses: &["terminal", "display"],
storage: &[users(b"", true), disk(b"apps", false)],
// Room for a few apps at a time.
spawns: Some(Limits { pages: 512, endpoints: 8, notifications: 8, regions: 8 }),
..server("permission", b"bin/permission", Limits { pages: 96, ..SMALL })
}];
/// Run on the test image after the SMP self-test, instead of the desktop:
/// spawns through init, then reports and halts.
pub const TESTS: &[Program] = &[Program {
serves: false,
// The permission manager, to run an app the way the shell does.
uses: &["permission"],
storage: Some(Scope { path: b"", write: true }),
storage: &[disk(b"", true), users(b"home", true)],
spawns: Some(Limits { pages: 128, endpoints: 2, notifications: 2, regions: 2 }),
system_control: true,
..server("spawntest", b"bin/spawntest", SMALL)
}];
/// The desktop; the test image runs the SMP self-test instead. The shell
/// holds the owner's files and asks the permission manager to run apps;
/// it holds nothing to give them beyond that.
/// holds the user's files and asks the permission manager to run apps;
/// it holds nothing to give them beyond that, and may sign the user out.
pub const DESKTOP: &[Program] = &[Program {
serves: false,
uses: &["terminal", "permission"],
storage: Some(Scope { path: b"home", write: true }),
storage: &[users(b"home", true)],
signs_out: true,
..server("shell", b"bin/shell", SMALL)
}];