use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fmt;
use std::path::PathBuf;
use reverie::Errno;
use reverie::syscalls::Syscall;
use serde::Deserialize;
use serde::Serialize;
use crate::types::ChildWaitSpec;
use crate::types::DetInode;
use crate::types::DetPid;
use crate::types::DetTid;
use crate::types::LogicalTime;
use crate::types::MmId;
use crate::types::RcbTimeMultiplier;
use crate::types::SigWrapper;
pub(crate) const SABRE_INTERNAL_PIPE_IO_FYI: &str = "sabre-internal-pipe-io";
pub(crate) const SABRE_LOOPBACK_POLL_YIELD_FYI: &str = "sabre-loopback-poll-zero-timeout";
#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
pub struct ChaosEpochTransition {
pub logical_time: LogicalTime,
pub epoch: u64,
pub factor: RcbTimeMultiplier,
}
#[derive(
PartialEq,
Debug,
Eq,
Clone,
Copy,
Serialize,
Deserialize,
Hash,
PartialOrd,
Ord
)]
pub struct ExternalOpId {
pub tid: DetTid,
pub sequence: u64,
}
impl ExternalOpId {
pub const fn new(tid: DetTid, sequence: u64) -> Self {
Self { tid, sequence }
}
}
#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize, Hash)]
pub enum ResourceID {
FileContents(DetInode),
FileMetadata(DetInode),
DirectoryContents(DetInode),
MemAddrSpace(DetPid),
Path(PathBuf),
PathsTransitive(PathBuf),
Device(Device),
Exit {
group: bool,
process: DetPid,
mm: MmId,
},
ParentContinue { parent: DetTid, child: DetTid },
SleepUntil(LogicalTime),
InternalIOPolling,
TraceReplay,
FutexWait,
BlockingExternalIO(ExternalOpId),
BlockingVfork(ExternalOpId),
VforkFailed(ExternalOpId),
BlockedExternalContinue(ExternalOpId),
PriorityChangePoint(u64, LogicalTime, u64, Vec<ChaosEpochTransition>),
WaitChild {
parent: DetPid,
spec: ChildWaitSpec,
},
WaitPhysicalChild(DetPid),
InboundSignal(SigWrapper),
WaitidSignals(Vec<SigWrapper>),
SchedYield,
HappensBeforeCheckpoint(u64),
BlockingRtSigsuspend(ExternalOpId),
}
#[allow(clippy::enum_variant_names)]
#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize, Hash)]
pub enum Device {
ContainerStdin,
ContainerStdout,
ContainerStderr,
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Resources {
pub tid: DetTid,
pub resources: HashMap<ResourceID, Permission>,
pub poll_attempt: u32,
pub fyi: String,
#[serde(default)]
pub(crate) signal_interrupt_errno: Option<i32>,
#[serde(default)]
pub(crate) backend_runtime_bootstrap: bool,
}
impl fmt::Debug for Resources {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("Resources");
debug
.field("tid", &self.tid)
.field("resources", &self.resources)
.field("poll_attempt", &self.poll_attempt)
.field("fyi", &self.fyi);
if let Some(errno) = self.signal_interrupt_errno {
debug.field("signal_interrupt_errno", &errno);
}
if self.backend_runtime_bootstrap {
debug.field("backend_runtime_bootstrap", &true);
}
debug.finish()
}
}
impl Resources {
pub fn new(tid: DetTid) -> Resources {
Resources {
tid,
resources: HashMap::new(),
poll_attempt: 0,
fyi: String::new(),
signal_interrupt_errno: None,
backend_runtime_bootstrap: false,
}
}
pub fn union(&mut self, other: &Resources) {
assert_eq!(self.tid, other.tid);
for (id, perm2) in other.resources.iter() {
match self.resources.entry(id.clone()) {
Entry::Occupied(mut e) => {
let perm1 = e.get_mut();
*perm1 = perm1.union(perm2)
}
Entry::Vacant(e) => {
e.insert(perm2.clone());
}
}
}
match (self.signal_interrupt_errno, other.signal_interrupt_errno) {
(None, interrupt) => self.signal_interrupt_errno = interrupt,
(Some(left), Some(right)) => assert_eq!(left, right),
(Some(_), None) => {}
}
self.backend_runtime_bootstrap |= other.backend_runtime_bootstrap;
}
pub fn set_signal_interrupt_errno(&mut self, errno: Errno) {
self.signal_interrupt_errno = Some(errno.into_raw());
}
pub fn signal_interrupt_errno(&self) -> Option<i32> {
self.signal_interrupt_errno
}
pub fn insert(&mut self, new: ResourceID, perm: Permission) {
let old = self.resources.insert(new, perm);
assert_eq!(old, None);
}
pub fn fyi(&mut self, s: &str) {
if !self.fyi.is_empty() {
self.fyi.push_str(", ");
}
self.fyi.push_str(s);
}
pub fn as_exit_syscall(&self) -> Option<Syscall> {
let exit = self.resources.keys().find_map(|resource| match resource {
ResourceID::Exit { group, process, mm } => Some((*group, *process, *mm)),
_ => None,
});
if exit.is_some() && self.resources.len() > 1 {
panic!(
"is_polling_turn: not expecting an InternalIOPolling mixed in with other resource requests: {:?}",
self
);
}
if exit.is_some_and(|(group, _, _)| group) {
Some(Syscall::ExitGroup(Default::default())) } else if exit.is_some() {
Some(Syscall::Exit(Default::default())) } else {
None
}
}
pub fn exit_identity(&self) -> Option<(bool, DetPid, MmId)> {
self.resources.keys().find_map(|resource| match resource {
ResourceID::Exit { group, process, mm } => Some((*group, *process, *mm)),
_ => None,
})
}
}
#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize, Hash)]
pub enum Permission {
R,
W,
RW,
}
impl Permission {
pub fn union(&self, other: &Permission) -> Permission {
use Permission::*;
match (self, other) {
(R, R) => R,
(R, W) => RW,
(W, R) => RW,
(W, W) => W,
(RW, _) => RW,
(_, RW) => RW,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn control_tokens_do_not_alias_unrelated_operations() {
let tid1 = DetTid::from_raw(1);
let tid2 = DetTid::from_raw(2);
assert_ne!(
ResourceID::BlockingExternalIO(ExternalOpId::new(tid1, 7)),
ResourceID::BlockingExternalIO(ExternalOpId::new(tid2, 7))
);
assert_ne!(
ResourceID::BlockingExternalIO(ExternalOpId::new(tid1, 7)),
ResourceID::BlockingRtSigsuspend(ExternalOpId::new(tid1, 7))
);
assert_ne!(
ResourceID::ParentContinue {
parent: tid1,
child: tid2,
},
ResourceID::ParentContinue {
parent: tid2,
child: tid1,
}
);
}
}