Decouple client contracts and close private role registries

This commit is contained in:
ms2k 2026-07-19 03:44:30 +09:00
commit 22325aa76a
5 changed files with 1041 additions and 321 deletions

View file

@ -1,7 +1,14 @@
use std::ffi::OsString;
use std::process::ExitCode;
use lazaret_linux::process::{dispatch_private_process_role, exec_target};
use lazaret_linux::private_role::{PrivateRole, PrivateRoleRegistry};
use lazaret_linux::process::{PRIVATE_PROCESS_ROLE, dispatch_private_process_role, exec_target};
static ROLES: [PrivateRole; 1] = [PrivateRole::new(
PRIVATE_PROCESS_ROLE,
dispatch_private_process_role,
)];
static PRIVATE_ROLES: PrivateRoleRegistry = PrivateRoleRegistry::new(&ROLES);
fn main() -> ExitCode {
match run() {
@ -15,8 +22,8 @@ fn main() -> ExitCode {
fn run() -> std::io::Result<()> {
let arguments = std::env::args_os().skip(1).collect::<Vec<OsString>>();
if let Some(result) = dispatch_private_process_role(&arguments) {
return result;
if let Some(outcome) = PRIVATE_ROLES.dispatch(&arguments) {
return outcome.result;
}
if arguments == [OsString::from("--trace-self-test")] {
let (release, wait) = lazaret_linux::process::launch_barrier()?;

View file

@ -5,4 +5,5 @@ pub mod capability;
pub mod filesystem;
pub mod ipc;
pub mod platform;
pub mod private_role;
pub mod process;

View file

@ -0,0 +1,108 @@
use std::ffi::OsString;
use std::io;
pub type PrivateRoleDispatch = fn(&[OsString]) -> Option<io::Result<()>>;
#[derive(Clone, Copy)]
pub struct PrivateRole {
name: &'static str,
dispatch: PrivateRoleDispatch,
}
impl PrivateRole {
#[must_use]
pub const fn new(name: &'static str, dispatch: PrivateRoleDispatch) -> Self {
Self { name, dispatch }
}
#[must_use]
pub const fn name(self) -> &'static str {
self.name
}
}
pub struct PrivateRoleResult {
pub name: &'static str,
pub result: io::Result<()>,
}
pub struct PrivateRoleRegistry {
roles: &'static [PrivateRole],
}
impl PrivateRoleRegistry {
#[must_use]
pub const fn new(roles: &'static [PrivateRole]) -> Self {
Self { roles }
}
#[must_use]
pub fn dispatch(&self, arguments: &[OsString]) -> Option<PrivateRoleResult> {
self.roles
.iter()
.find_map(|role| {
(role.dispatch)(arguments).map(|result| PrivateRoleResult {
name: role.name,
result,
})
})
.or_else(|| {
arguments
.first()
.filter(|argument| {
argument
.as_encoded_bytes()
.starts_with(b"--lazaret-private-")
})
.map(|_| PrivateRoleResult {
name: "unregistered-private-role",
result: Err(io::Error::other(
"private role is not declared by this executable",
)),
})
})
}
#[must_use]
pub fn names(&self) -> Vec<&'static str> {
self.roles.iter().map(|role| role.name).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn first(arguments: &[OsString]) -> Option<io::Result<()>> {
(arguments.first() == Some(&OsString::from("--first"))).then_some(Ok(()))
}
fn second(arguments: &[OsString]) -> Option<io::Result<()>> {
(arguments.first() == Some(&OsString::from("--second"))).then_some(Ok(()))
}
static ROLES: [PrivateRole; 2] = [
PrivateRole::new("first", first),
PrivateRole::new("second", second),
];
static REGISTRY: PrivateRoleRegistry = PrivateRoleRegistry::new(&ROLES);
#[test]
fn registry_is_ordered_closed_and_refuses_foreign_roles() {
assert_eq!(REGISTRY.names(), ["first", "second"]);
assert_eq!(
REGISTRY
.dispatch(&[OsString::from("--second")])
.unwrap()
.name,
"second"
);
assert!(REGISTRY.dispatch(&[]).is_none());
assert!(REGISTRY.dispatch(&[OsString::from("--foreign")]).is_none());
let foreign = REGISTRY
.dispatch(&[OsString::from("--lazaret-private-foreign")])
.unwrap();
assert_eq!(foreign.name, "unregistered-private-role");
assert!(foreign.result.is_err());
}
}

View file

@ -20,7 +20,7 @@ use rustix::process::{Pid, PidfdFlags, Signal, WaitId, WaitIdOptions};
pub const PID_FS_MAGIC: i64 = 0x5049_4446;
const BARRIER_RELEASE: &[u8; 32] = b"LAZARET-CAUSAL-BARRIER-RELEASE!!";
const PRIVATE_ROLE_ARGUMENT: &str = "--lazaret-private-process-role";
pub const PRIVATE_PROCESS_ROLE: &str = "--lazaret-private-process-role";
const WRAPPER_ROLE: &str = "launch-wrapper";
const SIGCHLD_PROBE_ROLE: &str = "sigchld-probe";
static CHILD_BROKER: OnceLock<ChildBroker> = OnceLock::new();

View file

@ -439,7 +439,7 @@ fn fail_running_captured<T>(
#[allow(clippy::disallowed_types)]
fn private_role_command(role: &str) -> std::process::Command {
let mut command = std::process::Command::new("/proc/self/exe");
command.arg(PRIVATE_ROLE_ARGUMENT).arg(role);
command.arg(PRIVATE_PROCESS_ROLE).arg(role);
command
}
@ -450,7 +450,7 @@ fn private_role_command(role: &str) -> std::process::Command {
pub fn dispatch_private_process_role(arguments: &[OsString]) -> Option<io::Result<()>> {
if arguments
.first()
.is_none_or(|argument| argument != PRIVATE_ROLE_ARGUMENT)
.is_none_or(|argument| argument != PRIVATE_PROCESS_ROLE)
{
return None;
}