Replace lexical Rust fragments with real modules

This commit is contained in:
ms2k 2026-07-19 05:12:57 +09:00
commit 247819250b
4 changed files with 3302 additions and 2671 deletions

View file

@ -1,3 +1,12 @@
use super::{
AsRawFd, ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, Deserialize, DomainId,
File, FileType, GenerationId, HostProcessOwnershipSlot, MAX_RUNTIME_HANDOFF_BYTES,
NetworkSidecarId, OsStr, OsStrExt, OsString, OsStringExt, PathBuf, ProcessOwnershipContext,
Read, RuntimeArguments, RuntimeGitRoutingArguments, RuntimeGitRoutingLaunch,
RuntimeNetworkArguments, RuntimeStateAnchorArguments, RuntimeSupervisorLaunch, Serialize,
StateAnchorLaunchAuthority, decode, encode, fstat, io, require_write_sealed,
};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
enum RuntimeDescriptorKindV1 {
Directory,
@ -72,10 +81,7 @@ impl lazaret_core::records::SchemaType for RuntimeSupervisorHandoff {
const NAME: &'static str = "RuntimeSupervisorHandoff";
}
fn descriptor(
number: i32,
kind: RuntimeDescriptorKindV1,
) -> io::Result<RuntimeDescriptorV1> {
fn descriptor(number: i32, kind: RuntimeDescriptorKindV1) -> io::Result<RuntimeDescriptorV1> {
if number < 0 {
return Err(io::Error::other("Runtime Supervisor descriptor is invalid"));
}
@ -99,7 +105,7 @@ fn descriptor(
})
}
fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>> {
pub(super) fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>> {
let state_anchor = launch
.state_anchor
.as_ref()
@ -128,9 +134,7 @@ fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>
});
let encoded = encode(&handoff).map_err(io::Error::other)?;
if encoded.len() > MAX_RUNTIME_HANDOFF_BYTES {
return Err(io::Error::other(
"Runtime Supervisor handoff is oversized",
));
return Err(io::Error::other("Runtime Supervisor handoff is oversized"));
}
Ok(encoded)
}
@ -139,10 +143,7 @@ fn build_state_anchor_handoff(
authority: &StateAnchorLaunchAuthority,
) -> io::Result<RuntimeStateAnchorHandoffV1> {
Ok(RuntimeStateAnchorHandoffV1 {
lower: descriptor(
authority.lower_fd(),
RuntimeDescriptorKindV1::Directory,
)?,
lower: descriptor(authority.lower_fd(), RuntimeDescriptorKindV1::Directory)?,
lower_label: authority.lower_label().as_os_str().as_bytes().to_vec(),
persistent_root: descriptor(
authority.persistent_root_fd(),
@ -236,10 +237,7 @@ fn build_network_handoff(
.as_ref()
.map(|policy| -> io::Result<RuntimeNetworkHandoffV1> {
Ok(RuntimeNetworkHandoffV1 {
policy: descriptor(
policy.as_raw_fd(),
RuntimeDescriptorKindV1::RegularFile,
)?,
policy: descriptor(policy.as_raw_fd(), RuntimeDescriptorKindV1::RegularFile)?,
policy_digest: launch
.policy_digest
.ok_or_else(|| io::Error::other("network policy digest is missing"))?,
@ -264,7 +262,7 @@ fn build_network_handoff(
Ok(network)
}
fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments> {
pub(super) fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments> {
let [descriptor] = arguments else {
return Err(io::Error::other(
"Runtime Supervisor requires one handoff descriptor",
@ -289,12 +287,10 @@ fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments>
.runsc_arguments
.iter()
.any(|argument| argument.as_slice() == b"run")
|| handoff.runsc_arguments.iter().any(|argument| {
matches!(
argument.as_slice(),
b"-detach" | b"--detach" | b"-d"
)
})
|| handoff
.runsc_arguments
.iter()
.any(|argument| matches!(argument.as_slice(), b"-detach" | b"--detach" | b"-d"))
{
return Err(io::Error::other(
"Runtime Supervisor requires foreground attached runsc",
@ -397,9 +393,7 @@ fn runtime_state_anchor_arguments(
lower_label: PathBuf::from(OsString::from_vec(state.lower_label)),
persistent_root_fd: state.persistent_root.number,
persistent_anchor_fd: state.persistent_anchor.number,
persistent_anchor_label: PathBuf::from(OsString::from_vec(
state.persistent_anchor_label,
)),
persistent_anchor_label: PathBuf::from(OsString::from_vec(state.persistent_anchor_label)),
runtime_root_fd: state.runtime_root.number,
runtime_anchor_fd: state.runtime_anchor.number,
runtime_anchor_label: PathBuf::from(OsString::from_vec(state.runtime_anchor_label)),
@ -442,6 +436,10 @@ mod handoff_tests {
use rustix::fs::{MemfdFlags, memfd_create};
use sha2::{Digest, Sha256};
use crate::sandbox::RunscCommand;
use lazaret_linux::filesystem::sealed_memfd;
use lazaret_linux::process::TargetCommand;
use super::*;
fn minimal_handoff(arguments: Vec<Vec<u8>>) -> RuntimeSupervisorHandoff {
@ -529,8 +527,8 @@ mod handoff_tests {
assert_eq!(
Sha256::digest(&encoded).as_slice(),
[
123, 0, 18, 227, 164, 11, 96, 15, 4, 201, 99, 25, 198, 122, 175, 149, 170,
165, 12, 40, 209, 64, 119, 67, 10, 109, 128, 44, 201, 103, 14, 182,
123, 0, 18, 227, 164, 11, 96, 15, 4, 201, 99, 25, 198, 122, 175, 149, 170, 165, 12,
40, 209, 64, 119, 67, 10, 109, 128, 44, 201, 103, 14, 182,
]
);
assert_eq!(
@ -581,8 +579,7 @@ mod handoff_tests {
let second_tree = tempfile::tempdir().unwrap();
let first = File::open(first_tree.path()).unwrap();
let second = File::open(second_tree.path()).unwrap();
let first_slot =
descriptor(first.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
let first_slot = descriptor(first.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
let second_slot =
descriptor(second.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
@ -592,10 +589,7 @@ mod handoff_tests {
let mut identity_drift = state_record(first_slot);
identity_drift.lower.inode = identity_drift.lower.inode.wrapping_add(1);
identity_drift.expected_lower = (
identity_drift.lower.device,
identity_drift.lower.inode,
);
identity_drift.expected_lower = (identity_drift.lower.device, identity_drift.lower.inode);
assert!(parse_record(&with_state(identity_drift)).is_err());
let mut substitution = state_record(first_slot);
@ -631,9 +625,7 @@ mod handoff_tests {
};
assert!(build_runtime_handoff(&launch).is_err());
assert!(parse_runtime_handoff(&[]).is_err());
assert!(
parse_runtime_handoff(&[OsString::from("1"), OsString::from("2")]).is_err()
);
assert!(parse_runtime_handoff(&[OsString::from("1"), OsString::from("2")]).is_err());
}
fn state_record(slot: RuntimeDescriptorV1) -> RuntimeStateAnchorHandoffV1 {

View file

@ -1,4 +1,16 @@
fn runsc_target(arguments: &RuntimeArguments, pid_file: i32) -> io::Result<TargetCommand> {
use super::{
AsFd, ChildStandardIo, CoordinatorMessage, Duration, File, FileExt, Instant,
MAX_CONTROL_MESSAGE, NamespaceIdentity, ParentEdge, PollFd, PollFlags, PreparedNetwork,
READINESS_TIMEOUT, RetainedProcess, RuntimeArguments, RuntimeGitRouting,
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorMount, Serialize, Signal,
SupervisorEvent, TERMINATION_GRACE, TargetCommand, Timespec, child_broker, decode, encode,
inherited_directory_at, io, mount_identity, poll, read, write,
};
pub(super) fn runsc_target(
arguments: &RuntimeArguments,
pid_file: i32,
) -> io::Result<TargetCommand> {
let run = arguments
.runsc_arguments
.iter()
@ -15,7 +27,7 @@ fn runsc_target(arguments: &RuntimeArguments, pid_file: i32) -> io::Result<Targe
Ok(target.with_parent_death_kill_edge())
}
fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand> {
pub(super) fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand> {
let run = arguments
.runsc_arguments
.iter()
@ -38,7 +50,7 @@ fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand
.argument(container))
}
fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandardIo> {
pub(super) fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandardIo> {
if target.parent_edge() != ParentEdge::ParentDeathKill {
return Err(io::Error::other(
"attached runsc requires the parent-death KILL edge",
@ -47,7 +59,7 @@ fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandar
Ok(ChildStandardIo::silenced())
}
fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Result<u32> {
pub(super) fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Result<u32> {
let deadline = Instant::now() + READINESS_TIMEOUT;
let mut bytes = [0_u8; 32];
loop {
@ -72,7 +84,7 @@ fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Resul
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
fn supervise(
pub(super) fn supervise(
control: &std::io::Stdout,
process: RetainedProcess,
mut git_routing: Option<RuntimeGitRouting>,
@ -376,7 +388,7 @@ fn terminate_and_reap(process: RetainedProcess) -> io::Result<()> {
}
}
fn fail_with_runtime_cleanup(
pub(super) fn fail_with_runtime_cleanup(
process: RetainedProcess,
git_routing: Option<RuntimeGitRouting>,
network: Option<PreparedNetwork>,
@ -427,7 +439,7 @@ fn cleanup_stopped_runtime(
}
}
fn fail_runtime_components<T>(
pub(super) fn fail_runtime_components<T>(
git_routing: Option<RuntimeGitRouting>,
network: Option<PreparedNetwork>,
error: io::Error,
@ -494,7 +506,7 @@ fn stop_git_routing(routing: Option<RuntimeGitRouting>) -> io::Result<()> {
Ok(())
}
fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
pub(super) fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
let deadline = Instant::now() + TERMINATION_GRACE;
while !process.has_exited()? && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(5));
@ -509,7 +521,7 @@ fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
child_broker().reap(process)
}
fn fail_network<T>(network: Option<PreparedNetwork>, error: io::Error) -> io::Result<T> {
pub(super) fn fail_network<T>(network: Option<PreparedNetwork>, error: io::Error) -> io::Result<T> {
match stop_network(network) {
Ok(()) => Err(error),
Err(cleanup) => Err(io::Error::new(
@ -526,7 +538,7 @@ fn stop_network(network: Option<PreparedNetwork>) -> io::Result<()> {
}
}
fn require_lifetime_held() -> io::Result<()> {
pub(super) fn require_lifetime_held() -> io::Result<()> {
if lifetime_lost()? {
Err(io::Error::other("Host Coordinator lifetime lease is lost"))
} else {
@ -543,7 +555,7 @@ fn lifetime_lost() -> io::Result<bool> {
.intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR))
}
fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
pub(super) fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
descriptor: impl AsFd,
message: &T,
) -> io::Result<()> {
@ -558,7 +570,7 @@ fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
send_all(descriptor.as_fd(), &encoded)
}
fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
pub(super) fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
where
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + Serialize,
{

View file

@ -1,4 +1,20 @@
fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
use super::{
AsFd, AsRawFd, CapabilitySet, CoordinatorMessage, Duration, FdFlags, File, FileType, GitSentry,
HostProcessOwnershipSlot, Instant, MemfdFlags, Mode, MountIdentity, NamespaceIdentity,
NetworkAttestationContext, OFlags, PRIVATE_ROUTER_ROLE, PathBuf, PipeFlags, PreparedNetwork,
ProcessOwnershipContext, READINESS_TIMEOUT, Read, ResolveFlags, RetainedProcess,
RuntimeArguments, RuntimeGitRoutingArguments, RuntimeNetworkArguments,
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
RuntimeStateAnchorMount, StagedProcessOwnership, SupervisorEvent, TargetCommand,
attached_runsc_standard_io, capability, child_broker, current_process_identity,
enter_generation_filesystem_namespaces, enter_generation_network_namespace, fail_network,
fail_runtime_components, fail_with_runtime_cleanup, fcntl_setfd, fstat, io, memfd_create,
mount_identity, openat2, pipe_with, prepare_network, probe_current_network, receive_message,
require_lifetime_held, runsc_delete_target, runsc_target, send_message, supervise,
wait_and_reap, wait_for_runsc_ready,
};
pub(super) fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
require_lifetime_held()?;
let namespaces = enter_generation_filesystem_namespaces()?;
if arguments.network.is_none() {
@ -223,12 +239,12 @@ fn run_supervisor_with_state(
)
}
struct RuntimeGitRouting {
process: RetainedProcess,
shutdown: File,
sentry: GitSentry,
mountpoint: PathBuf,
mount: MountIdentity,
pub(super) struct RuntimeGitRouting {
pub(super) process: RetainedProcess,
pub(super) shutdown: File,
pub(super) sentry: GitSentry,
pub(super) mountpoint: PathBuf,
pub(super) mount: MountIdentity,
}
#[allow(clippy::too_many_lines)]
@ -523,7 +539,7 @@ fn reopen_exact(
Ok(reopened)
}
fn inherited_directory_at(path: impl AsRef<std::path::Path>) -> io::Result<File> {
pub(super) fn inherited_directory_at(path: impl AsRef<std::path::Path>) -> io::Result<File> {
let file = File::open(path)?;
if !FileType::from_raw_mode(fstat(&file)?.st_mode).is_dir() {
return Err(io::Error::other(
@ -650,4 +666,3 @@ fn prepare_runtime_network(
}
Ok(prepared)
}

View file

@ -1,235 +1,232 @@
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::ffi::OsString;
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
use lazaret_core::records::HostProcessOwnershipSlot;
use lazaret_linux::ipc::private_seqpacket_pair;
use lazaret_linux::process::ChildStandardIoPolicy;
use rustix::pipe::{PipeFlags, pipe_with};
use sha2::{Digest, Sha256};
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
use lazaret_core::records::HostProcessOwnershipSlot;
use lazaret_linux::ipc::private_seqpacket_pair;
use lazaret_linux::process::ChildStandardIoPolicy;
use rustix::pipe::{PipeFlags, pipe_with};
use sha2::{Digest, Sha256};
use super::*;
use super::*;
#[test]
fn private_control_record_vectors_are_stable() {
let namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(1),
inode: lazaret_core::identity::InodeId::new(2),
};
let mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(3),
device: lazaret_core::identity::DeviceId::new(4),
inode: lazaret_core::identity::InodeId::new(5),
};
let records = [
encode(&AttachedRunscSignal::Continue).unwrap(),
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: namespace,
mount_namespace: namespace,
mount,
observed: mount,
})
.unwrap(),
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace: namespace,
mount_namespace: namespace,
mount,
})
.unwrap(),
];
let mut corpus = Sha256::new();
for record in records {
corpus.update(record.len().to_be_bytes());
corpus.update(record);
}
assert_eq!(
corpus.finalize().as_slice(),
[
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250,
164, 101, 22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
]
);
}
#[test]
fn private_stream_framing_round_trips_typed_events() {
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
let expected = SupervisorEvent::Refused("refused".into());
send_message(&supervisor, &expected).unwrap();
assert_eq!(
CoordinatorChannel::new(coordinator).receive().unwrap(),
expected
);
}
#[test]
fn coordinator_channel_preserves_phased_resource_commands() {
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
let channel = CoordinatorChannel::new(coordinator);
channel.mount_state_anchor().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::MountStateAnchor
);
channel.start_git_routing().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StartGitRouting
);
channel.start_sidecar().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StartSidecar
);
channel.stop_sandbox().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopSandbox
);
channel.stop_git_routing().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopGitRouting
);
channel.stop_sidecar().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopSidecar
);
channel.unmount_state_anchor().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::UnmountStateAnchor
);
let user_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(11),
inode: lazaret_core::identity::InodeId::new(12),
};
let mount_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(13),
inode: lazaret_core::identity::InodeId::new(14),
};
let mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(15),
device: lazaret_core::identity::DeviceId::new(16),
inode: lazaret_core::identity::InodeId::new(17),
};
channel
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
.unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace,
mount_namespace,
mount,
}
);
channel.exit().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::Exit
);
}
#[test]
fn runtime_supervisor_and_runsc_keep_distinct_edge_and_stdio_policies() {
let ownership = ProcessOwnershipContext {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
coordinator_id: CoordinatorId::new([2; 32]),
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
};
let launch = RuntimeSupervisorLaunch {
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
handoff_file: None,
ownership,
policy_file: None,
policy_digest: None,
configuration_fingerprint: None,
runsc_command: RunscCommand::new(
"runsc",
[OsString::from("run"), OsString::from("container")],
[0; 32],
),
network_sidecar_id: None,
state_anchor: None,
git_routing: None,
};
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
let (supervisor_lifetime, _coordinator_lifetime) = pipe_with(PipeFlags::CLOEXEC).unwrap();
assert_eq!(launch.target().parent_edge(), ParentEdge::LifetimePipe);
assert_eq!(
launch
.child_standard_io(supervisor_control, supervisor_lifetime)
.unwrap()
.policy(),
ChildStandardIoPolicy::PrivateControlWithLifetimePipe
);
let runsc_arguments = RuntimeArguments {
ownership: ProcessOwnershipContext {
slot: HostProcessOwnershipSlot::SandboxAttachedRunsc,
..ownership
},
state_anchor: None,
git_routing: None,
network: None,
runsc_program: "runsc".into(),
runsc_arguments: ["-root=/runtime", "run", "container"]
.map(OsString::from)
.into(),
};
let runsc = runsc_target(&runsc_arguments, 9).unwrap();
assert_eq!(runsc.parent_edge(), ParentEdge::ParentDeathKill);
assert_eq!(
attached_runsc_standard_io(&runsc).unwrap().policy(),
ChildStandardIoPolicy::Silenced
);
let delete = runsc_delete_target(&runsc_arguments).unwrap();
assert_eq!(delete.program(), "runsc");
assert_eq!(
delete.arguments(),
["-root=/runtime", "delete", "--force", "container"]
.map(OsString::from)
.as_slice()
);
}
#[test]
fn runtime_handoff_preserves_exact_ownership_and_runsc_arguments() {
let ownership = ProcessOwnershipContext {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
coordinator_id: CoordinatorId::new([2; 32]),
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
};
let launch = RuntimeSupervisorLaunch::from_runsc_command(
ownership,
&RunscCommand::new(
"runsc",
[
OsString::from("-root=/runtime"),
OsString::from("run"),
OsString::from("container"),
],
[0; 32],
),
)
.unwrap();
assert_eq!(
launch.target().arguments().first(),
Some(&OsString::from(PRIVATE_RUNTIME_ROLE))
);
assert_eq!(launch.target().arguments().len(), 2);
let parsed = parse_runtime_handoff(&launch.target().arguments()[1..]).unwrap();
assert_eq!(parsed.ownership.domain_id, ownership.domain_id);
assert_eq!(parsed.ownership.generation_id, ownership.generation_id);
assert_eq!(parsed.ownership.coordinator_id, ownership.coordinator_id);
assert_eq!(
parsed.runsc_arguments,
["-root=/runtime", "run", "container"].map(OsString::from)
);
#[test]
fn private_control_record_vectors_are_stable() {
let namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(1),
inode: lazaret_core::identity::InodeId::new(2),
};
let mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(3),
device: lazaret_core::identity::DeviceId::new(4),
inode: lazaret_core::identity::InodeId::new(5),
};
let records = [
encode(&AttachedRunscSignal::Continue).unwrap(),
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: namespace,
mount_namespace: namespace,
mount,
observed: mount,
})
.unwrap(),
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace: namespace,
mount_namespace: namespace,
mount,
})
.unwrap(),
];
let mut corpus = Sha256::new();
for record in records {
corpus.update(record.len().to_be_bytes());
corpus.update(record);
}
assert_eq!(
corpus.finalize().as_slice(),
[
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250, 164, 101,
22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
]
);
}
#[test]
fn private_stream_framing_round_trips_typed_events() {
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
let expected = SupervisorEvent::Refused("refused".into());
send_message(&supervisor, &expected).unwrap();
assert_eq!(
CoordinatorChannel::new(coordinator).receive().unwrap(),
expected
);
}
#[test]
fn coordinator_channel_preserves_phased_resource_commands() {
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
let channel = CoordinatorChannel::new(coordinator);
channel.mount_state_anchor().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::MountStateAnchor
);
channel.start_git_routing().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StartGitRouting
);
channel.start_sidecar().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StartSidecar
);
channel.stop_sandbox().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopSandbox
);
channel.stop_git_routing().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopGitRouting
);
channel.stop_sidecar().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::StopSidecar
);
channel.unmount_state_anchor().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::UnmountStateAnchor
);
let user_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(11),
inode: lazaret_core::identity::InodeId::new(12),
};
let mount_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(13),
inode: lazaret_core::identity::InodeId::new(14),
};
let mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(15),
device: lazaret_core::identity::DeviceId::new(16),
inode: lazaret_core::identity::InodeId::new(17),
};
channel
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
.unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace,
mount_namespace,
mount,
}
);
channel.exit().unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::Exit
);
}
#[test]
fn runtime_supervisor_and_runsc_keep_distinct_edge_and_stdio_policies() {
let ownership = ProcessOwnershipContext {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
coordinator_id: CoordinatorId::new([2; 32]),
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
};
let launch = RuntimeSupervisorLaunch {
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
handoff_file: None,
ownership,
policy_file: None,
policy_digest: None,
configuration_fingerprint: None,
runsc_command: RunscCommand::new(
"runsc",
[OsString::from("run"), OsString::from("container")],
[0; 32],
),
network_sidecar_id: None,
state_anchor: None,
git_routing: None,
};
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
let (supervisor_lifetime, _coordinator_lifetime) = pipe_with(PipeFlags::CLOEXEC).unwrap();
assert_eq!(launch.target().parent_edge(), ParentEdge::LifetimePipe);
assert_eq!(
launch
.child_standard_io(supervisor_control, supervisor_lifetime)
.unwrap()
.policy(),
ChildStandardIoPolicy::PrivateControlWithLifetimePipe
);
let runsc_arguments = RuntimeArguments {
ownership: ProcessOwnershipContext {
slot: HostProcessOwnershipSlot::SandboxAttachedRunsc,
..ownership
},
state_anchor: None,
git_routing: None,
network: None,
runsc_program: "runsc".into(),
runsc_arguments: ["-root=/runtime", "run", "container"]
.map(OsString::from)
.into(),
};
let runsc = runsc_target(&runsc_arguments, 9).unwrap();
assert_eq!(runsc.parent_edge(), ParentEdge::ParentDeathKill);
assert_eq!(
attached_runsc_standard_io(&runsc).unwrap().policy(),
ChildStandardIoPolicy::Silenced
);
let delete = runsc_delete_target(&runsc_arguments).unwrap();
assert_eq!(delete.program(), "runsc");
assert_eq!(
delete.arguments(),
["-root=/runtime", "delete", "--force", "container"]
.map(OsString::from)
.as_slice()
);
}
#[test]
fn runtime_handoff_preserves_exact_ownership_and_runsc_arguments() {
let ownership = ProcessOwnershipContext {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
coordinator_id: CoordinatorId::new([2; 32]),
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
};
let launch = RuntimeSupervisorLaunch::from_runsc_command(
ownership,
&RunscCommand::new(
"runsc",
[
OsString::from("-root=/runtime"),
OsString::from("run"),
OsString::from("container"),
],
[0; 32],
),
)
.unwrap();
assert_eq!(
launch.target().arguments().first(),
Some(&OsString::from(PRIVATE_RUNTIME_ROLE))
);
assert_eq!(launch.target().arguments().len(), 2);
let parsed = parse_runtime_handoff(&launch.target().arguments()[1..]).unwrap();
assert_eq!(parsed.ownership.domain_id, ownership.domain_id);
assert_eq!(parsed.ownership.generation_id, ownership.generation_id);
assert_eq!(parsed.ownership.coordinator_id, ownership.coordinator_id);
assert_eq!(
parsed.runsc_arguments,
["-root=/runtime", "run", "container"].map(OsString::from)
);
}