use std::os::unix::fs::MetadataExt as _;
use std::path::{Path, PathBuf};
static KIDS: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
pub fn kids_path() -> Option<&'static Path> {
KIDS.get().map(|p| p.as_path())
}
#[derive(Debug, PartialEq, Eq)]
pub enum Partition {
Disabled,
Unavailable(&'static str),
Ok,
}
pub fn setup() -> &'static str {
let result = outcome(enabled(), our_cgroup_root());
label_and_publish(result)
}
fn outcome(enabled: bool, root: Option<PathBuf>) -> Partition {
match root {
None => Partition::Unavailable("no-v2-cgroup"),
Some(root) => setup_in(&root, !enabled),
}
}
fn label_and_publish(result: Partition) -> &'static str {
let label = match &result {
Partition::Ok => "ok",
Partition::Disabled => "disabled",
Partition::Unavailable(why) => {
tracing::debug!("cgroup partition unavailable: {why}");
"unavailable"
}
};
ocserve_metrics::labeled_counter(
"ocserve_cgroup_partition_total",
&format!("result=\"{label}\""),
1,
);
match &result {
Partition::Ok => tracing::info!(
"cgroup partition: kids ceiling active ({:?})",
KIDS.get()
.map(|p| p.display().to_string())
.unwrap_or_default()
),
Partition::Disabled => tracing::debug!("cgroup partition disabled"),
Partition::Unavailable(_) => {}
}
label
}
fn enabled() -> bool {
enabled_from(
std::env::var("OCSERVE_CGROUP_PARTITION").ok().as_deref(),
std::env::var("INVOCATION_ID").ok().as_deref(),
)
}
fn enabled_from(env: Option<&str>, invocation_id: Option<&str>) -> bool {
match env {
Some("0") => false,
Some("1") => true,
_ => invocation_id.is_some(),
}
}
fn our_cgroup_root() -> Option<PathBuf> {
let cg = std::fs::read_to_string("/proc/self/cgroup").ok()?;
let rel = cg.lines().find_map(|l| l.strip_prefix("0::"))?.trim();
let rel = rel.trim_start_matches('/');
if rel.is_empty() {
return None;
}
Some(PathBuf::from("/sys/fs/cgroup").join(rel))
}
pub fn setup_in(root: &Path, disabled: bool) -> Partition {
if disabled {
return Partition::Disabled;
}
let Ok(md) = std::fs::metadata(root) else {
return Partition::Unavailable("root-missing");
};
if !md.is_dir() {
return Partition::Unavailable("root-not-dir");
}
let Some(uid) = our_uid_opt() else {
return Partition::Unavailable("uid-unknown");
};
if md.uid() != uid {
return Partition::Unavailable("not-owned-by-us");
}
let Ok(controllers) = std::fs::read_to_string(root.join("cgroup.controllers")) else {
return Partition::Unavailable("no-controllers-file");
};
if !controllers.split_whitespace().any(|c| c == "memory") {
return Partition::Unavailable("no-memory-controller");
}
if std::fs::OpenOptions::new()
.write(true)
.open(root.join("cgroup.subtree_control"))
.is_err()
{
return Partition::Unavailable("subtree-not-writable");
}
if let Err(e) = std::fs::create_dir_all(root.join("main")) {
tracing::warn!("partition: mkdir main failed: {e}");
return Partition::Unavailable("mkdir-main");
}
if let Err(e) = std::fs::create_dir_all(root.join("kids")) {
tracing::warn!("partition: mkdir kids failed: {e}");
return Partition::Unavailable("mkdir-kids");
}
let pid = std::process::id();
if let Err(e) = std::fs::write(root.join("main/cgroup.procs"), pid.to_string()) {
tracing::warn!("partition: self-move failed: {e}");
return Partition::Unavailable("self-move");
}
let sc = root.join("cgroup.subtree_control");
let existing = std::fs::read_to_string(&sc).unwrap_or_default();
if !existing
.split_whitespace()
.any(|c| c.trim_start_matches('+') == "memory")
{
let next = if existing.trim().is_empty() {
"+memory".to_string()
} else {
format!("{} +memory", existing.trim())
};
if let Err(e) = std::fs::write(&sc, next) {
tracing::warn!("partition: enable +memory failed: {e}");
return Partition::Unavailable("enable-memory");
}
}
let mb: u64 = std::env::var("OCSERVE_KIDS_MEMORY_MAX_MB")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(700);
let max_path = root.join("kids/memory.max");
if let Err(e) = std::fs::write(&max_path, (mb * 1024 * 1024).to_string()) {
tracing::warn!("partition: kids memory.max failed: {e}");
return Partition::Unavailable("kids-cap");
}
let kids = root.join("kids");
let _ = KIDS.set(kids.clone());
ocserve_plugin::set_kids_cgroup(&kids);
ocserve_mcp::set_kids_cgroup(&kids);
Partition::Ok
}
fn our_uid_opt() -> Option<u32> {
let st = std::fs::read_to_string("/proc/self/status").ok()?;
let line = st.lines().find(|l| l.starts_with("Uid:"))?;
line.split_whitespace().nth(1)?.parse().ok()
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture(memory: bool) -> PathBuf {
static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let seq = N.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let root =
std::env::temp_dir().join(format!("ocserve-partition-{}-{seq}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
std::fs::write(
root.join("cgroup.controllers"),
if memory {
"cpuset memory pids"
} else {
"cpuset pids"
},
)
.unwrap();
std::fs::write(root.join("cgroup.subtree_control"), "").unwrap();
root
}
#[test]
fn happy_path_dance_and_caps() {
let root = fixture(true);
let p = setup_in(&root, false);
assert_eq!(p, Partition::Ok);
assert_eq!(
std::fs::read_to_string(root.join("kids/memory.max")).unwrap(),
(700u64 * 1024 * 1024).to_string(),
"kids ceiling must be 700M in bytes (kernel takes plain bytes)"
);
let sc = std::fs::read_to_string(root.join("cgroup.subtree_control")).unwrap();
assert!(
sc.contains("+memory"),
"subtree_control must enable memory: {sc}"
);
let procs = std::fs::read_to_string(root.join("main/cgroup.procs")).unwrap();
assert_eq!(
procs.trim(),
std::process::id().to_string(),
"self must be in main/"
);
assert!(setup_in(&root, false) == Partition::Ok, "idempotent re-run"); let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn glue_maps_enabled_to_dance_and_disabled_to_no_mutation() {
let root = fixture(true);
assert_eq!(outcome(true, Some(root.clone())), Partition::Ok);
assert!(root.join("kids/memory.max").exists());
let _ = std::fs::remove_dir_all(&root);
let root = fixture(true);
assert_eq!(outcome(false, Some(root.clone())), Partition::Disabled);
assert!(!root.join("main").exists(), "disabled must not mutate");
let _ = std::fs::remove_dir_all(&root);
assert_eq!(outcome(true, None), Partition::Unavailable("no-v2-cgroup"));
}
#[test]
fn kill_switch_disables() {
let root = fixture(true);
assert_eq!(setup_in(&root, true), Partition::Disabled);
assert!(!root.join("main").exists(), "disabled = zero mutation");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn probe_failures_degrade_without_mutation() {
let root = fixture(true);
assert_eq!(
setup_in(&root.join("nope"), false),
Partition::Unavailable("root-missing")
);
let _ = std::fs::remove_dir_all(&root);
let root = fixture(false);
assert_eq!(
setup_in(&root, false),
Partition::Unavailable("no-memory-controller")
);
assert!(!root.join("main").exists(), "failed probe must not mutate");
let _ = std::fs::remove_dir_all(&root);
let root = fixture(true);
std::fs::remove_file(root.join("cgroup.controllers")).unwrap();
assert_eq!(
setup_in(&root, false),
Partition::Unavailable("no-controllers-file")
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn subtree_readonly_degrades() {
let root = fixture(true);
let sc = root.join("cgroup.subtree_control");
let f = std::fs::File::create(&sc).unwrap();
drop(f);
use std::os::unix::fs::PermissionsExt;
let mut ro = std::fs::metadata(&sc).unwrap().permissions();
ro.set_mode(0o444);
std::fs::set_permissions(&sc, ro).unwrap();
assert_eq!(
setup_in(&root, false),
Partition::Unavailable("subtree-not-writable")
);
assert!(!root.join("main").exists());
let mut rw = std::fs::metadata(&sc).unwrap().permissions();
rw.set_mode(0o644);
std::fs::set_permissions(&sc, rw).unwrap();
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn enabled_requires_force_or_systemd_invocation() {
assert!(!enabled_from(None, None));
assert!(!enabled_from(Some("0"), Some("some-invocation")));
assert!(enabled_from(Some("1"), None));
assert!(enabled_from(None, Some("some-invocation")));
assert!(!enabled_from(Some("junk"), None));
assert!(enabled_from(Some("junk"), Some("id")));
}
}