Replace lexical Rust fragments with real modules
This commit is contained in:
parent
f8849cdb5f
commit
247819250b
7 changed files with 3302 additions and 2671 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue