Replace lexical Rust fragments with real modules

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

View file

@ -471,7 +471,11 @@ mod tests {
};
fn test_directory(name: &str) -> std::path::PathBuf {
let path = std::env::temp_dir().join(format!(
let parent = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/test-artifacts/linux-filesystem");
std::fs::create_dir_all(&parent).unwrap();
let parent = parent.canonicalize().unwrap();
let path = parent.join(format!(
"lazaret-linux-{name}-{}-{}",
std::process::id(),
super::TEMPORARY_SEQUENCE.fetch_add(1, std::sync::atomic::Ordering::Relaxed)

View file

@ -427,7 +427,19 @@ fn require_captured_parent_edge(parent_edge: ParentEdge) -> io::Result<()> {
}
}
include!("process/broker.rs");
include!("process/recovery.rs");
include!("process/launch.rs");
include!("process/tests.rs");
mod broker;
mod launch;
mod recovery;
pub use broker::{dispatch_private_process_role, verify_direct_child_pidfd};
pub use launch::{
BarrierRelease, BarrierWait, append_parent_identity, exec_target, launch_barrier,
parse_wrapper_arguments, suspend_current_process,
};
pub use recovery::{
arm_parent_death_and_recheck, current_process_identity, match_recovery_process, parse_boot_id,
read_boot_id, read_start_time, recover_process, recover_process_action, require_pidfs_magic,
};
#[cfg(test)]
mod tests;

View file

@ -1,3 +1,19 @@
use super::launch::{BarrierWait, append_parent_identity, exec_target, parse_wrapper_arguments};
use super::recovery::{
arm_parent_death_and_recheck, authority_from_pidfd, current_process_identity, read_boot_id,
read_start_time, require_default_sigchld, require_host_pid_namespace, require_reapable_sigchld,
};
use super::{
AcknowledgedChildAcquisition, AsFd, BarrierRelease, BootstrapChild, CapturedProcess,
ChildBroker, ChildStandardIo, ChildStandardIoPolicy, File, HostPidLocator, OFlags, OsString,
OwnedFd, PRIVATE_PROCESS_ROLE, ParentEdge, Pid, PidfdFlags, PidfsProcessIdentityV1, PollFd,
PollFlags, ProcessExit, ProcessOwnershipCommitAck, ProcessOwnershipContext,
ProcessStartTimeTicks, RetainedProcess, SIGCHLD_PROBE_ROLE, Signal, StagedProcessOwnership,
Stdio, TargetCommand, Timespec, WRAPPER_ROLE, WaitId, WaitIdOptions, fcntl_getfl, fcntl_setfl,
io, launch_barrier, poll, recover_process, require_captured_parent_edge,
require_parent_edge_standard_io,
};
impl ChildBroker {
/// Spawns the private Host Coordinator bootstrap with exact pipe ownership and pidfd reaping.
///
@ -437,7 +453,7 @@ fn fail_running_captured<T>(
}
#[allow(clippy::disallowed_types)]
fn private_role_command(role: &str) -> std::process::Command {
pub(super) fn private_role_command(role: &str) -> std::process::Command {
let mut command = std::process::Command::new("/proc/self/exe");
command.arg(PRIVATE_PROCESS_ROLE).arg(role);
command

View file

@ -1,3 +1,10 @@
use super::{
AsFd, BARRIER_RELEASE, BootId, FdFlags, File, HostPidLocator, OsStr, OsString, ParentEdge,
PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PipeFlags,
ProcessStartTimeTicks, Read, Signal, Stdio, TargetCommand, TargetStdinAccess, Write,
fcntl_setfd, io, pipe_with,
};
/// # Errors
/// Returns an error when the fail-closed pipe cannot be created.
pub fn launch_barrier() -> io::Result<(BarrierRelease, BarrierWait)> {
@ -10,7 +17,7 @@ pub fn launch_barrier() -> io::Result<(BarrierRelease, BarrierWait)> {
}
#[derive(Debug)]
pub struct BarrierRelease(File);
pub struct BarrierRelease(pub(super) File);
impl BarrierRelease {
/// # Errors
@ -22,7 +29,7 @@ impl BarrierRelease {
}
#[derive(Debug)]
pub struct BarrierWait(File);
pub struct BarrierWait(pub(super) File);
impl BarrierWait {
#[must_use]

View file

@ -1,3 +1,12 @@
use super::broker::private_role_command;
use super::{
AsFd, BarrierRelease, BootId, BorrowedFd, HostPidLocator, OwnedFd, PID_FS_MAGIC, Pid,
PidfdFlags, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, PidfsProcessIdentityV1,
PidfsRecoveryMatch, ProcessStartTimeTicks, RecoveryActionResult, RetainedProcess,
SIGCHLD_PROBE_ROLE, Signal, WaitId, WaitIdOptions, fstat, fstatfs, io, launch_barrier,
verify_direct_child_pidfd,
};
/// # Errors
/// Refuses before PID lookup on boot mismatch and before action on every later mismatch.
#[allow(clippy::disallowed_methods)]
@ -53,7 +62,7 @@ pub fn recover_process_action<T>(
}
}
fn match_recovery_process_with<Boot, Open, Inspect, Start>(
pub(super) fn match_recovery_process_with<Boot, Open, Inspect, Start>(
identity: PidfsProcessIdentityV1,
boot: Boot,
open: Open,
@ -132,7 +141,10 @@ pub fn current_process_identity() -> io::Result<PidfsProcessIdentityV1> {
))
}
fn authority_from_pidfd(pidfd: impl AsFd, boot_id: BootId) -> io::Result<PidfsProcessAuthorityV1> {
pub(super) fn authority_from_pidfd(
pidfd: impl AsFd,
boot_id: BootId,
) -> io::Result<PidfsProcessAuthorityV1> {
authority_from_pidfd_with(
pidfd,
boot_id,
@ -144,7 +156,7 @@ fn authority_from_pidfd(pidfd: impl AsFd, boot_id: BootId) -> io::Result<PidfsPr
)
}
fn authority_from_pidfd_with<Fs, Stat>(
pub(super) fn authority_from_pidfd_with<Fs, Stat>(
pidfd: impl AsFd,
boot_id: BootId,
filesystem: Fs,
@ -218,7 +230,7 @@ pub fn read_start_time(host_pid: u32) -> io::Result<u64> {
.ok_or_else(|| io::Error::other("process stat has no valid start time"))
}
fn require_host_pid_namespace(host_pid: u32) -> io::Result<()> {
pub(super) fn require_host_pid_namespace(host_pid: u32) -> io::Result<()> {
let status = std::fs::read_to_string(format!("/proc/{host_pid}/status"))?;
let nspid = status
.lines()
@ -233,7 +245,7 @@ fn require_host_pid_namespace(host_pid: u32) -> io::Result<()> {
}
}
fn require_default_sigchld() -> io::Result<()> {
pub(super) fn require_default_sigchld() -> io::Result<()> {
let status = std::fs::read_to_string("/proc/self/status")?;
let ignored = signal_mask(&status, "SigIgn:")?;
let caught = signal_mask(&status, "SigCgt:")?;
@ -246,7 +258,7 @@ fn require_default_sigchld() -> io::Result<()> {
}
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
fn require_reapable_sigchld() -> io::Result<()> {
pub(super) fn require_reapable_sigchld() -> io::Result<()> {
let (release, wait) = launch_barrier()?;
let mut command = private_role_command(SIGCHLD_PROBE_ROLE);
command.stdin(wait.into_stdio());
@ -300,7 +312,7 @@ fn cleanup_sigchld_probe(
result
}
fn cleanup_sigchld_probe_with<Terminate, Wait>(
pub(super) fn cleanup_sigchld_probe_with<Terminate, Wait>(
error: io::Error,
terminate: Terminate,
mut wait: Wait,

View file

@ -1,380 +1,377 @@
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::os::fd::{AsFd, AsRawFd};
use std::rc::Rc;
use std::cell::Cell;
use std::os::fd::{AsFd, AsRawFd};
use std::rc::Rc;
use super::*;
use super::recovery::{
authority_from_pidfd_with, cleanup_sigchld_probe_with, match_recovery_process_with,
};
use super::*;
#[test]
fn broker_is_process_global() {
assert!(std::ptr::eq(child_broker(), child_broker()));
}
#[test]
fn broker_is_process_global() {
assert!(std::ptr::eq(child_broker(), child_broker()));
}
#[test]
fn parent_edges_require_their_exact_stdio_policy() {
assert!(
require_parent_edge_standard_io(
ParentEdge::LifetimePipe,
ChildStandardIoPolicy::PrivateControlWithLifetimePipe,
)
.is_ok()
);
assert!(
require_parent_edge_standard_io(
ParentEdge::LifetimePipe,
ChildStandardIoPolicy::Silenced,
)
#[test]
fn parent_edges_require_their_exact_stdio_policy() {
assert!(
require_parent_edge_standard_io(
ParentEdge::LifetimePipe,
ChildStandardIoPolicy::PrivateControlWithLifetimePipe,
)
.is_ok()
);
assert!(
require_parent_edge_standard_io(ParentEdge::LifetimePipe, ChildStandardIoPolicy::Silenced,)
.is_err()
);
assert!(
require_parent_edge_standard_io(
ParentEdge::ParentDeathKill,
ChildStandardIoPolicy::Silenced,
)
.is_ok()
);
assert!(
require_parent_edge_standard_io(
ParentEdge::ParentDeathKill,
ChildStandardIoPolicy::PrivateControlWithLifetimePipe,
)
);
assert!(
require_parent_edge_standard_io(
ParentEdge::ParentDeathKill,
ChildStandardIoPolicy::Silenced,
)
.is_ok()
);
assert!(
require_parent_edge_standard_io(
ParentEdge::ParentDeathKill,
ChildStandardIoPolicy::PrivateControlWithLifetimePipe,
)
.is_err()
);
}
#[test]
fn barrier_reader_is_inheritable_and_writer_is_close_on_exec() {
let (release, wait) = launch_barrier().unwrap();
let reader_flags = rustix::io::fcntl_getfd(wait.0.as_fd()).unwrap();
let writer_flags = rustix::io::fcntl_getfd(release.0.as_fd()).unwrap();
assert!(!reader_flags.contains(FdFlags::CLOEXEC));
assert!(writer_flags.contains(FdFlags::CLOEXEC));
assert_ne!(wait.0.as_raw_fd(), release.0.as_raw_fd());
}
#[test]
fn bootstrap_child_retains_exact_pipe_owner_and_pidfd_reap() {
let (reader, writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
let target = TargetCommand::new("/bin/sh")
.argument("-c")
.argument("printf L; read -r _ || exit 0")
.with_lifetime_pipe_parent_edge();
let mut child = child_broker()
.spawn_bootstrap(&target, File::from(reader))
.unwrap();
assert_eq!(child.acquisition_status().unwrap(), b'L');
assert!(!child.has_exited().unwrap());
drop(writer);
let status = child.reap().unwrap();
assert_eq!(status.exit_code, Some(0));
assert_eq!(status.terminating_signal, None);
let (reader, _writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
assert!(
child_broker()
.spawn_bootstrap(&TargetCommand::new("/bin/true"), File::from(reader))
.is_err()
);
);
}
#[test]
fn boot_id_requires_exact_canonical_uuid_syntax() {
parse_boot_id("00112233-4455-6677-8899-aabbccddeeff\n").unwrap();
for invalid in [
"00112233445566778899aabbccddeeff",
"00112233_4455-6677-8899-aabbccddeeff",
"00112233-4455-6677-8899-aabbccddeeff-extra",
"00112233-4455-6677-8899-aabbccddeefg",
"00112233-4455-6677-8899-aabbccddeeff\n\n",
] {
assert!(parse_boot_id(invalid).is_err(), "accepted {invalid}");
}
}
#[test]
fn barrier_reader_is_inheritable_and_writer_is_close_on_exec() {
let (release, wait) = launch_barrier().unwrap();
let reader_flags = rustix::io::fcntl_getfd(wait.0.as_fd()).unwrap();
let writer_flags = rustix::io::fcntl_getfd(release.0.as_fd()).unwrap();
assert!(!reader_flags.contains(FdFlags::CLOEXEC));
assert!(writer_flags.contains(FdFlags::CLOEXEC));
assert_ne!(wait.0.as_raw_fd(), release.0.as_raw_fd());
}
#[test]
fn recovery_checks_boot_before_pid_lookup() {
let identity = PidfsProcessIdentityV1::new(
PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(1),
pidfs_st_ino: PidfsInodeId::new(1),
},
HostPidLocator::new(1),
ProcessStartTimeTicks::new(1),
);
let opened = Cell::new(false);
let result = match_recovery_process_with(
identity,
|| Ok(BootId::new([2; 16])),
|_| {
opened.set(true);
unreachable!()
},
|_, _| unreachable!(),
|_| unreachable!(),
);
assert!(matches!(result, Ok(PidfsRecoveryMatch::BootMismatch)));
assert!(!opened.get());
}
#[test]
fn bootstrap_child_retains_exact_pipe_owner_and_pidfd_reap() {
let (reader, writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
let target = TargetCommand::new("/bin/sh")
.argument("-c")
.argument("printf L; read -r _ || exit 0")
.with_lifetime_pipe_parent_edge();
let mut child = child_broker()
.spawn_bootstrap(&target, File::from(reader))
.unwrap();
assert_eq!(child.acquisition_status().unwrap(), b'L');
assert!(!child.has_exited().unwrap());
drop(writer);
let status = child.reap().unwrap();
assert_eq!(status.exit_code, Some(0));
assert_eq!(status.terminating_signal, None);
let (reader, _writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
assert!(
child_broker()
.spawn_bootstrap(&TargetCommand::new("/bin/true"), File::from(reader))
.is_err()
);
}
#[test]
fn boot_id_requires_exact_canonical_uuid_syntax() {
parse_boot_id("00112233-4455-6677-8899-aabbccddeeff\n").unwrap();
for invalid in [
"00112233445566778899aabbccddeeff",
"00112233_4455-6677-8899-aabbccddeeff",
"00112233-4455-6677-8899-aabbccddeeff-extra",
"00112233-4455-6677-8899-aabbccddeefg",
"00112233-4455-6677-8899-aabbccddeeff\n\n",
] {
assert!(parse_boot_id(invalid).is_err(), "accepted {invalid}");
}
}
#[test]
fn recovery_checks_boot_before_pid_lookup() {
let identity = PidfsProcessIdentityV1::new(
#[test]
fn recovery_refuses_each_post_open_mismatch_without_returning_authority() {
let authority = PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(7),
pidfs_st_ino: PidfsInodeId::new(9),
};
let identity = PidfsProcessIdentityV1::new(
authority,
HostPidLocator::new(41),
ProcessStartTimeTicks::new(43),
);
for (observed, start) in [
(
PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(1),
pidfs_st_ino: PidfsInodeId::new(1),
pidfs_st_dev: PidfsDeviceId::new(8),
..authority
},
HostPidLocator::new(1),
ProcessStartTimeTicks::new(1),
);
let opened = Cell::new(false);
43,
),
(
PidfsProcessAuthorityV1 {
pidfs_st_ino: PidfsInodeId::new(10),
..authority
},
43,
),
(authority, 44),
] {
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let open_events = Rc::clone(&events);
let inspect_events = Rc::clone(&events);
let start_events = Rc::clone(&events);
let result = match_recovery_process_with(
identity,
|| Ok(BootId::new([2; 16])),
|_| {
opened.set(true);
unreachable!()
|| Ok(authority.boot_id),
move |pid| {
open_events.borrow_mut().push(("open", u64::from(pid)));
Ok(File::open("/dev/null").unwrap().into())
},
|_, _| unreachable!(),
|_| unreachable!(),
);
assert!(matches!(result, Ok(PidfsRecoveryMatch::BootMismatch)));
assert!(!opened.get());
}
#[test]
fn recovery_refuses_each_post_open_mismatch_without_returning_authority() {
let authority = PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(7),
pidfs_st_ino: PidfsInodeId::new(9),
};
let identity = PidfsProcessIdentityV1::new(
authority,
HostPidLocator::new(41),
ProcessStartTimeTicks::new(43),
);
for (observed, start) in [
(
PidfsProcessAuthorityV1 {
pidfs_st_dev: PidfsDeviceId::new(8),
..authority
},
43,
),
(
PidfsProcessAuthorityV1 {
pidfs_st_ino: PidfsInodeId::new(10),
..authority
},
43,
),
(authority, 44),
] {
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let open_events = Rc::clone(&events);
let inspect_events = Rc::clone(&events);
let start_events = Rc::clone(&events);
let result = match_recovery_process_with(
identity,
|| Ok(authority.boot_id),
move |pid| {
open_events.borrow_mut().push(("open", u64::from(pid)));
Ok(File::open("/dev/null").unwrap().into())
},
move |fd, _| {
inspect_events
.borrow_mut()
.push(("inspect", u64::try_from(fd.as_raw_fd()).unwrap()));
Ok(observed)
},
move |pid| {
start_events.borrow_mut().push(("start", u64::from(pid)));
Ok(start)
},
);
assert!(result.is_err());
assert_eq!(events.borrow()[0], ("open", 41));
assert_eq!(events.borrow()[1].0, "inspect");
if observed == authority {
assert_eq!(events.borrow()[2], ("start", 41));
} else {
assert_eq!(events.borrow().len(), 2);
}
}
for failure in ["open", "inspect", "start"] {
let result = match_recovery_process_with(
identity,
|| Ok(authority.boot_id),
|_| {
if failure == "open" {
Err(io::Error::from_raw_os_error(3))
} else {
Ok(File::open("/dev/null").unwrap().into())
}
},
|_, _| {
if failure == "inspect" {
Err(io::Error::other("injected descriptor inspection error"))
} else {
Ok(authority)
}
},
|_| {
if failure == "start" {
Err(io::Error::other("injected start observation error"))
} else {
Ok(43)
}
},
);
assert!(result.is_err(), "accepted injected {failure} failure");
}
}
#[test]
fn recovery_normalizes_vanished_proc_evidence_to_esrch() {
let authority = PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(7),
pidfs_st_ino: PidfsInodeId::new(9),
};
let identity = PidfsProcessIdentityV1::new(
authority,
HostPidLocator::new(41),
ProcessStartTimeTicks::new(43),
);
for missing in ["open", "start"] {
let result = match_recovery_process_with(
identity,
|| Ok(authority.boot_id),
|_| {
if missing == "open" {
Err(io::Error::from_raw_os_error(
rustix::io::Errno::NOENT.raw_os_error(),
))
} else {
Ok(File::open("/dev/null").unwrap().into())
}
},
|_, _| Ok(authority),
|_| {
if missing == "start" {
Err(io::Error::from_raw_os_error(
rustix::io::Errno::NOENT.raw_os_error(),
))
} else {
Ok(43)
}
},
)
.unwrap_err();
assert_eq!(
result.raw_os_error(),
Some(rustix::io::Errno::SRCH.raw_os_error())
);
}
}
#[test]
fn recovery_action_reconciles_boot_without_process_action() {
let mut old_boot = *read_boot_id().unwrap().as_bytes();
old_boot[0] ^= u8::MAX;
let identity = PidfsProcessIdentityV1::new(
PidfsProcessAuthorityV1 {
boot_id: BootId::new(old_boot),
pidfs_st_dev: PidfsDeviceId::new(1),
pidfs_st_ino: PidfsInodeId::new(1),
},
HostPidLocator::new(u32::MAX),
ProcessStartTimeTicks::new(u64::MAX),
);
let action_called = Cell::new(false);
let result = recover_process_action(
identity,
|| Ok("reconciled"),
|_| {
action_called.set(true);
Ok("acted")
},
)
.unwrap();
assert_eq!(result, RecoveryActionResult::BootReconciled("reconciled"));
assert!(!action_called.get());
}
#[test]
fn pidfs_magic_precedes_same_descriptor_stat_and_errors_refuse() {
let file = File::open("/dev/null").unwrap();
let raw = file.as_raw_fd();
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let filesystem_events = Rc::clone(&events);
let stat_events = Rc::clone(&events);
let authority = authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
move |fd| {
filesystem_events
move |fd, _| {
inspect_events
.borrow_mut()
.push(("fstatfs", fd.as_raw_fd()));
Ok(PID_FS_MAGIC)
.push(("inspect", u64::try_from(fd.as_raw_fd()).unwrap()));
Ok(observed)
},
move |fd| {
stat_events.borrow_mut().push(("fstat", fd.as_raw_fd()));
Ok((7, 9))
move |pid| {
start_events.borrow_mut().push(("start", u64::from(pid)));
Ok(start)
},
)
.unwrap();
assert_eq!(&*events.borrow(), &[("fstatfs", raw), ("fstat", raw)]);
assert_eq!(authority.pidfs_st_dev.get(), 7);
assert_eq!(authority.pidfs_st_ino.get(), 9);
for filesystem_error in [false, true] {
let stat_called = Cell::new(false);
let result = authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
|_| {
if filesystem_error {
Err(io::Error::other("injected fstatfs error"))
} else {
Ok(0)
}
},
|_| {
stat_called.set(true);
Ok((1, 1))
},
);
assert!(result.is_err());
assert!(!stat_called.get());
);
assert!(result.is_err());
assert_eq!(events.borrow()[0], ("open", 41));
assert_eq!(events.borrow()[1].0, "inspect");
if observed == authority {
assert_eq!(events.borrow()[2], ("start", 41));
} else {
assert_eq!(events.borrow().len(), 2);
}
assert!(
authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
|_| Ok(PID_FS_MAGIC),
|_| Err(io::Error::other("injected fstat error")),
)
.is_err()
);
}
#[test]
fn sigchld_probe_epipe_terminates_and_reaps_exact_child() {
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let terminate_events = Rc::clone(&events);
let wait_events = Rc::clone(&events);
let result = cleanup_sigchld_probe_with(
io::Error::new(io::ErrorKind::BrokenPipe, "probe died before release"),
move || {
terminate_events.borrow_mut().push("terminate-pidfd");
Err(io::Error::from_raw_os_error(3))
for failure in ["open", "inspect", "start"] {
let result = match_recovery_process_with(
identity,
|| Ok(authority.boot_id),
|_| {
if failure == "open" {
Err(io::Error::from_raw_os_error(3))
} else {
Ok(File::open("/dev/null").unwrap().into())
}
},
move || {
wait_events.borrow_mut().push("wait-pidfd");
Ok(())
|_, _| {
if failure == "inspect" {
Err(io::Error::other("injected descriptor inspection error"))
} else {
Ok(authority)
}
},
);
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::BrokenPipe);
assert_eq!(&*events.borrow(), &["terminate-pidfd", "wait-pidfd"]);
}
#[test]
fn sigchld_probe_failed_wait_retries_cleanup_until_not_waitable() {
let waits = Cell::new(0_u8);
let result = cleanup_sigchld_probe_with(
io::Error::other("injected semantic wait failure"),
|| Ok(()),
|| {
waits.set(waits.get() + 1);
match waits.get() {
1 => Err(io::Error::from(io::ErrorKind::Interrupted)),
_ => Err(io::Error::from_raw_os_error(
rustix::io::Errno::CHILD.raw_os_error(),
)),
|_| {
if failure == "start" {
Err(io::Error::other("injected start observation error"))
} else {
Ok(43)
}
},
);
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::Other);
assert_eq!(waits.get(), 2);
assert!(result.is_err(), "accepted injected {failure} failure");
}
}
#[test]
fn recovery_normalizes_vanished_proc_evidence_to_esrch() {
let authority = PidfsProcessAuthorityV1 {
boot_id: BootId::new([1; 16]),
pidfs_st_dev: PidfsDeviceId::new(7),
pidfs_st_ino: PidfsInodeId::new(9),
};
let identity = PidfsProcessIdentityV1::new(
authority,
HostPidLocator::new(41),
ProcessStartTimeTicks::new(43),
);
for missing in ["open", "start"] {
let result = match_recovery_process_with(
identity,
|| Ok(authority.boot_id),
|_| {
if missing == "open" {
Err(io::Error::from_raw_os_error(
rustix::io::Errno::NOENT.raw_os_error(),
))
} else {
Ok(File::open("/dev/null").unwrap().into())
}
},
|_, _| Ok(authority),
|_| {
if missing == "start" {
Err(io::Error::from_raw_os_error(
rustix::io::Errno::NOENT.raw_os_error(),
))
} else {
Ok(43)
}
},
)
.unwrap_err();
assert_eq!(
result.raw_os_error(),
Some(rustix::io::Errno::SRCH.raw_os_error())
);
}
}
#[test]
fn recovery_action_reconciles_boot_without_process_action() {
let mut old_boot = *read_boot_id().unwrap().as_bytes();
old_boot[0] ^= u8::MAX;
let identity = PidfsProcessIdentityV1::new(
PidfsProcessAuthorityV1 {
boot_id: BootId::new(old_boot),
pidfs_st_dev: PidfsDeviceId::new(1),
pidfs_st_ino: PidfsInodeId::new(1),
},
HostPidLocator::new(u32::MAX),
ProcessStartTimeTicks::new(u64::MAX),
);
let action_called = Cell::new(false);
let result = recover_process_action(
identity,
|| Ok("reconciled"),
|_| {
action_called.set(true);
Ok("acted")
},
)
.unwrap();
assert_eq!(result, RecoveryActionResult::BootReconciled("reconciled"));
assert!(!action_called.get());
}
#[test]
fn pidfs_magic_precedes_same_descriptor_stat_and_errors_refuse() {
let file = File::open("/dev/null").unwrap();
let raw = file.as_raw_fd();
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let filesystem_events = Rc::clone(&events);
let stat_events = Rc::clone(&events);
let authority = authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
move |fd| {
filesystem_events
.borrow_mut()
.push(("fstatfs", fd.as_raw_fd()));
Ok(PID_FS_MAGIC)
},
move |fd| {
stat_events.borrow_mut().push(("fstat", fd.as_raw_fd()));
Ok((7, 9))
},
)
.unwrap();
assert_eq!(&*events.borrow(), &[("fstatfs", raw), ("fstat", raw)]);
assert_eq!(authority.pidfs_st_dev.get(), 7);
assert_eq!(authority.pidfs_st_ino.get(), 9);
for filesystem_error in [false, true] {
let stat_called = Cell::new(false);
let result = authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
|_| {
if filesystem_error {
Err(io::Error::other("injected fstatfs error"))
} else {
Ok(0)
}
},
|_| {
stat_called.set(true);
Ok((1, 1))
},
);
assert!(result.is_err());
assert!(!stat_called.get());
}
assert!(
authority_from_pidfd_with(
file.as_fd(),
BootId::new([1; 16]),
|_| Ok(PID_FS_MAGIC),
|_| Err(io::Error::other("injected fstat error")),
)
.is_err()
);
}
#[test]
fn sigchld_probe_epipe_terminates_and_reaps_exact_child() {
let events = Rc::new(std::cell::RefCell::new(Vec::new()));
let terminate_events = Rc::clone(&events);
let wait_events = Rc::clone(&events);
let result = cleanup_sigchld_probe_with(
io::Error::new(io::ErrorKind::BrokenPipe, "probe died before release"),
move || {
terminate_events.borrow_mut().push("terminate-pidfd");
Err(io::Error::from_raw_os_error(3))
},
move || {
wait_events.borrow_mut().push("wait-pidfd");
Ok(())
},
);
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::BrokenPipe);
assert_eq!(&*events.borrow(), &["terminate-pidfd", "wait-pidfd"]);
}
#[test]
fn sigchld_probe_failed_wait_retries_cleanup_until_not_waitable() {
let waits = Cell::new(0_u8);
let result = cleanup_sigchld_probe_with(
io::Error::other("injected semantic wait failure"),
|| Ok(()),
|| {
waits.set(waits.get() + 1);
match waits.get() {
1 => Err(io::Error::from(io::ErrorKind::Interrupted)),
_ => Err(io::Error::from_raw_os_error(
rustix::io::Errno::CHILD.raw_os_error(),
)),
}
},
);
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::Other);
assert_eq!(waits.get(), 2);
}

View file

@ -63,7 +63,7 @@ fn run_all() -> std::io::Result<()> {
}
fn target_stdin_does_not_gain_write_authority() -> std::io::Result<()> {
let path = std::env::temp_dir().join(format!(
let path = artifact_path(&format!(
"lazaret-target-stdin-authority-{}-{}",
std::process::id(),
SEQUENCE.fetch_add(1, Ordering::Relaxed)
@ -358,13 +358,20 @@ fn run_lifetime_pipe_target(marker: &std::path::Path, stderr: Stdio) -> std::io:
}
fn marker(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!(
artifact_path(&format!(
"lazaret-acquisition-{name}-{}-{}",
std::process::id(),
SEQUENCE.fetch_add(1, Ordering::Relaxed)
))
}
fn artifact_path(name: &str) -> std::path::PathBuf {
let parent = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/test-artifacts/linux-acquisition");
std::fs::create_dir_all(&parent).unwrap();
parent.canonicalize().unwrap().join(name)
}
fn launch_wrapper() -> &'static std::path::Path {
std::path::Path::new(env!("CARGO_BIN_EXE_lazaret-launch-wrapper"))
}