use crate::parent_death::ParentDeathCleanup;
#[cfg(feature = "process-control")]
use crate::soft_stop::SoftStopScope;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Mechanism {
JobObject,
CgroupV2,
ProcessGroup,
}
impl Mechanism {
pub fn name(&self) -> &'static str {
match self {
Mechanism::JobObject => "job_object",
Mechanism::CgroupV2 => "cgroup_v2",
Mechanism::ProcessGroup => "process_group",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"job_object" => Some(Mechanism::JobObject),
"cgroup_v2" => Some(Mechanism::CgroupV2),
"process_group" => Some(Mechanism::ProcessGroup),
_ => None,
}
}
pub(crate) fn detect() -> Mechanism {
crate::sys::detect_mechanism()
}
}
#[cfg_attr(
feature = "process-control",
doc = "- [`soft_stop_scope`](Self::soft_stop_scope) *(needs `process-control`)* — how far",
doc = " a **soft stop** ([`ProcessGroup::signal`](crate::ProcessGroup::signal) with",
doc = " [`Signal::Term`](crate::Signal::Term) / [`Int`](crate::Signal::Int)) can reach",
doc = " on this host's mechanism: [`WholeTree`](crate::SoftStopScope::WholeTree) on the",
doc = " Unix backends, [`OptInMembers`](crate::SoftStopScope::OptInMembers) on Windows",
doc = " (a Job Object soft-stops only a console-CTRL leader or a windowed member). This",
doc = " is the **host-level maximum** the mechanism can achieve; a *specific* group's",
doc = " actual reach is still read per-group from",
doc = " [`ProcessGroup::soft_stop_scope`](crate::ProcessGroup::soft_stop_scope), which",
doc = " on Windows narrows to [`Unsupported`](crate::SoftStopScope::Unsupported) for a",
doc = " group holding no such member."
)]
#[cfg_attr(
not(feature = "process-control"),
doc = "- `soft_stop_scope` *(needs the `process-control` feature)* — how far a **soft",
doc = " stop** (a `Term` / `Int` signal) can reach on this host's mechanism: the whole",
doc = " tree on the Unix backends, only opt-in members on Windows (a Job Object",
doc = " soft-stops only a console-CTRL leader or a windowed member). This is the",
doc = " **host-level maximum** the mechanism can achieve; a *specific* group's actual",
doc = " reach is still read per-group from its own `soft_stop_scope`, which on Windows",
doc = " narrows to `Unsupported` for a group holding no such member."
)]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostContainment {
mechanism: Mechanism,
#[cfg(feature = "process-control")]
soft_stop_scope: SoftStopScope,
parent_death_cleanup: ParentDeathCleanup,
crate_version: &'static str,
}
impl HostContainment {
pub(crate) fn probe() -> Self {
let mechanism = Mechanism::detect();
Self {
mechanism,
#[cfg(feature = "process-control")]
soft_stop_scope: host_soft_stop_scope(mechanism),
parent_death_cleanup: crate::Command::kill_on_parent_death_scope(),
crate_version: env!("CARGO_PKG_VERSION"),
}
}
pub fn mechanism(&self) -> Mechanism {
self.mechanism
}
#[cfg(feature = "process-control")]
pub fn soft_stop_scope(&self) -> SoftStopScope {
self.soft_stop_scope
}
pub fn parent_death_cleanup(&self) -> ParentDeathCleanup {
self.parent_death_cleanup
}
pub fn crate_version(&self) -> &'static str {
self.crate_version
}
}
#[cfg(feature = "process-control")]
fn host_soft_stop_scope(mechanism: Mechanism) -> SoftStopScope {
match mechanism {
Mechanism::CgroupV2 | Mechanism::ProcessGroup => SoftStopScope::WholeTree,
Mechanism::JobObject => SoftStopScope::OptInMembers,
}
}
#[cfg(test)]
mod tests {
use super::Mechanism;
const ALL: &[Mechanism] = &[
Mechanism::JobObject,
Mechanism::CgroupV2,
Mechanism::ProcessGroup,
];
#[test]
fn name_pins_each_variant() {
assert_eq!(Mechanism::JobObject.name(), "job_object");
assert_eq!(Mechanism::CgroupV2.name(), "cgroup_v2");
assert_eq!(Mechanism::ProcessGroup.name(), "process_group");
}
#[test]
fn name_from_name_round_trips_every_variant() {
for &m in ALL {
assert_eq!(Mechanism::from_name(m.name()), Some(m));
}
}
#[test]
fn from_name_rejects_unknown_without_defaulting() {
assert_eq!(Mechanism::from_name("JobObject"), None);
assert_eq!(Mechanism::from_name("jobobject"), None);
assert_eq!(Mechanism::from_name(""), None);
assert_eq!(Mechanism::from_name("cgroup"), None);
}
#[test]
fn host_report_mechanism_matches_the_read_only_detection() {
let report = super::HostContainment::probe();
assert_eq!(report.mechanism(), Mechanism::detect());
assert!(matches!(
report.mechanism(),
Mechanism::JobObject | Mechanism::CgroupV2 | Mechanism::ProcessGroup
));
}
#[test]
fn host_report_parent_death_reuses_the_command_query() {
assert_eq!(
super::HostContainment::probe().parent_death_cleanup(),
crate::Command::kill_on_parent_death_scope(),
);
}
#[test]
fn host_report_carries_this_crate_version() {
assert_eq!(
super::HostContainment::probe().crate_version(),
env!("CARGO_PKG_VERSION"),
);
}
#[test]
fn host_report_is_clonable_and_comparable() {
let report = super::HostContainment::probe();
assert_eq!(report.clone(), report);
}
#[cfg(feature = "process-control")]
#[test]
fn host_soft_stop_scope_maps_each_mechanism() {
use crate::SoftStopScope;
assert_eq!(
super::host_soft_stop_scope(Mechanism::CgroupV2),
SoftStopScope::WholeTree
);
assert_eq!(
super::host_soft_stop_scope(Mechanism::ProcessGroup),
SoftStopScope::WholeTree
);
assert_eq!(
super::host_soft_stop_scope(Mechanism::JobObject),
SoftStopScope::OptInMembers
);
}
#[cfg(feature = "process-control")]
#[test]
fn host_report_soft_stop_scope_is_consistent_with_its_mechanism() {
let report = super::HostContainment::probe();
assert_eq!(
report.soft_stop_scope(),
super::host_soft_stop_scope(report.mechanism()),
);
}
}