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