use std::sync::OnceLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DaemonMode {
Full,
NestedAdaptive,
Degraded,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonCapabilities {
pub is_root: bool,
pub is_nested: bool,
pub cgroup_parent: Option<String>,
pub can_write_cgroup_root: bool,
pub has_cap_net_admin: bool,
pub tun_device_available: bool,
pub effective_mode: DaemonMode,
}
static CAPS: OnceLock<DaemonCapabilities> = OnceLock::new();
impl DaemonCapabilities {
pub fn get() -> &'static Self {
CAPS.get_or_init(Self::probe)
}
pub fn seed(caps: Self) -> &'static Self {
let _ = CAPS.set(caps);
CAPS.get()
.expect("CAPS is filled after set or was already filled")
}
#[must_use]
pub fn probe() -> Self {
let is_root = zlayer_paths::is_root();
let cgroup_parent = current_cgroup_v2_path();
let is_nested = cgroup_parent.is_some();
let can_write_cgroup_root = probe_can_write_cgroup_root();
let has_cap_net_admin = probe_has_cap_net_admin();
let tun_device_available = probe_tun_device_available();
let effective_mode =
if !is_nested && can_write_cgroup_root && has_cap_net_admin && tun_device_available {
DaemonMode::Full
} else if can_write_cgroup_root || cgroup_parent.is_some() {
DaemonMode::NestedAdaptive
} else {
DaemonMode::Degraded
};
Self {
is_root,
is_nested,
cgroup_parent,
can_write_cgroup_root,
has_cap_net_admin,
tun_device_available,
effective_mode,
}
}
}
#[cfg(target_os = "linux")]
fn parse_cgroup_v2_line(content: &str) -> Option<String> {
for line in content.lines() {
if let Some(rest) = line.strip_prefix("0::") {
let trimmed = rest.trim();
if trimmed.is_empty() || trimmed == "/" {
return None;
}
return Some(trimmed.to_string());
}
}
None
}
#[cfg(target_os = "linux")]
#[must_use]
pub fn current_cgroup_v2_path() -> Option<String> {
let content = std::fs::read_to_string("/proc/self/cgroup").ok()?;
parse_cgroup_v2_line(&content)
}
#[cfg(not(target_os = "linux"))]
#[must_use]
pub fn current_cgroup_v2_path() -> Option<String> {
None
}
#[cfg(target_os = "linux")]
fn compute_target_parent(scope: &str) -> String {
let base = scope.strip_suffix("/init").unwrap_or(scope);
let base = base.trim_end_matches('/');
format!("{base}/containers")
}
#[cfg(target_os = "linux")]
#[must_use]
pub fn ensure_daemon_leaf_and_container_parent() -> Option<String> {
let scope = current_cgroup_v2_path()?;
let containers = compute_target_parent(&scope);
if scope.ends_with("/init") {
let containers_fs = format!("/sys/fs/cgroup{containers}");
match std::fs::create_dir_all(&containers_fs) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return None,
}
return Some(containers);
}
let scope = scope.trim_end_matches('/').to_string();
let mount = "/sys/fs/cgroup";
let init_dir = format!("{mount}{scope}/init");
match std::fs::create_dir_all(&init_dir) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return None,
}
let pid_path = format!("{init_dir}/cgroup.procs");
let pid_str = format!("{}", std::process::id());
if std::fs::write(&pid_path, &pid_str).is_err() {
let now = current_cgroup_v2_path()?;
if now != format!("{scope}/init") {
return None;
}
}
let after = current_cgroup_v2_path()?;
if after != format!("{scope}/init") {
return None;
}
let containers_dir = format!("{mount}{containers}");
match std::fs::create_dir_all(&containers_dir) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return None,
}
Some(containers)
}
#[cfg(not(target_os = "linux"))]
#[must_use]
pub fn ensure_daemon_leaf_and_container_parent() -> Option<String> {
None
}
#[cfg(target_os = "linux")]
fn probe_can_write_cgroup_root() -> bool {
use std::ffi::CString;
let Ok(path) = CString::new("/sys/fs/cgroup/cgroup.subtree_control") else {
return false;
};
#[allow(unsafe_code)]
let rc = unsafe { libc::access(path.as_ptr(), libc::W_OK) };
rc == 0
}
#[cfg(not(target_os = "linux"))]
fn probe_can_write_cgroup_root() -> bool {
false
}
#[cfg(target_os = "linux")]
fn probe_has_cap_net_admin() -> bool {
const CAP_NET_ADMIN_BIT: u64 = 1 << 12;
let Ok(status) = std::fs::read_to_string("/proc/self/status") else {
return false;
};
for line in status.lines() {
if let Some(hex) = line.strip_prefix("CapEff:") {
let trimmed = hex.trim();
if let Ok(eff) = u64::from_str_radix(trimmed, 16) {
return eff & CAP_NET_ADMIN_BIT != 0;
}
return false;
}
}
false
}
#[cfg(not(target_os = "linux"))]
fn probe_has_cap_net_admin() -> bool {
false
}
#[cfg(target_os = "linux")]
fn probe_tun_device_available() -> bool {
use std::os::unix::fs::OpenOptionsExt;
std::fs::OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open("/dev/net/tun")
.is_ok()
}
#[cfg(not(target_os = "linux"))]
fn probe_tun_device_available() -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_does_not_panic_and_is_nested_agrees_with_cgroup_parent() {
let caps = DaemonCapabilities::probe();
assert_eq!(caps.is_nested, caps.cgroup_parent.is_some());
}
#[cfg(target_os = "linux")]
#[test]
fn probe_has_cap_net_admin_matches_cap_eff() {
let status = std::fs::read_to_string("/proc/self/status").unwrap();
let cap_eff_line = status
.lines()
.find(|l| l.starts_with("CapEff:"))
.expect("CapEff: present in /proc/self/status");
let hex = cap_eff_line.trim_start_matches("CapEff:").trim();
let eff: u64 = u64::from_str_radix(hex, 16).unwrap();
let expected = (eff & (1u64 << 12)) != 0;
assert_eq!(super::probe_has_cap_net_admin(), expected);
}
#[allow(clippy::fn_params_excessive_bools)]
fn classify(
is_nested: bool,
can_write_cgroup_root: bool,
has_cap_net_admin: bool,
tun_device_available: bool,
cgroup_parent_is_some: bool,
) -> DaemonMode {
if !is_nested && can_write_cgroup_root && has_cap_net_admin && tun_device_available {
DaemonMode::Full
} else if can_write_cgroup_root || cgroup_parent_is_some {
DaemonMode::NestedAdaptive
} else {
DaemonMode::Degraded
}
}
#[test]
fn effective_mode_full_requires_all_four_signals() {
assert_eq!(
classify(false, true, true, true, false),
DaemonMode::Full,
"all four signals set should be Full"
);
assert_ne!(classify(true, true, true, true, true), DaemonMode::Full);
assert_ne!(classify(false, false, true, true, false), DaemonMode::Full);
assert_ne!(classify(false, true, false, true, false), DaemonMode::Full);
assert_ne!(classify(false, true, true, false, false), DaemonMode::Full);
}
#[test]
fn effective_mode_nested_adaptive_when_writable_or_has_parent() {
assert_eq!(
classify(false, true, false, false, false),
DaemonMode::NestedAdaptive
);
assert_eq!(
classify(true, false, false, false, true),
DaemonMode::NestedAdaptive
);
}
#[test]
fn effective_mode_degraded_when_no_writable_path() {
assert_eq!(
classify(false, false, false, false, false),
DaemonMode::Degraded
);
assert_eq!(
classify(true, false, false, false, false),
DaemonMode::Degraded
);
}
#[test]
fn serializes_round_trip_via_serde_json() {
let caps = DaemonCapabilities::probe();
let json = serde_json::to_string(&caps).expect("serialize");
let parsed: DaemonCapabilities = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.is_root, caps.is_root);
assert_eq!(parsed.is_nested, caps.is_nested);
assert_eq!(parsed.cgroup_parent, caps.cgroup_parent);
assert_eq!(parsed.can_write_cgroup_root, caps.can_write_cgroup_root);
assert_eq!(parsed.has_cap_net_admin, caps.has_cap_net_admin);
assert_eq!(parsed.tun_device_available, caps.tun_device_available);
assert_eq!(parsed.effective_mode, caps.effective_mode);
}
#[test]
fn daemon_mode_serde_uses_snake_case() {
assert_eq!(
serde_json::to_string(&DaemonMode::Full).unwrap(),
"\"full\""
);
assert_eq!(
serde_json::to_string(&DaemonMode::NestedAdaptive).unwrap(),
"\"nested_adaptive\""
);
assert_eq!(
serde_json::to_string(&DaemonMode::Degraded).unwrap(),
"\"degraded\""
);
}
#[cfg(target_os = "linux")]
mod target_parent {
use super::super::compute_target_parent;
#[test]
fn idempotent_when_already_under_init() {
assert_eq!(
compute_target_parent(
"/user.slice/user-1000.slice/user@1000.service/app.slice/run-p123.scope"
),
"/user.slice/user-1000.slice/user@1000.service/app.slice/run-p123.scope/containers"
);
assert_eq!(
compute_target_parent(
"/user.slice/user-1000.slice/user@1000.service/app.slice/run-p123.scope/init"
),
"/user.slice/user-1000.slice/user@1000.service/app.slice/run-p123.scope/containers"
);
assert_eq!(compute_target_parent("/foo/bar/"), "/foo/bar/containers");
assert_eq!(
compute_target_parent("/foo/bar/init"),
"/foo/bar/containers"
);
}
}
#[cfg(target_os = "linux")]
mod cgroup_parser {
use super::super::parse_cgroup_v2_line;
#[test]
fn parse_cgroup_v2_root_returns_none() {
assert_eq!(parse_cgroup_v2_line("0::/\n"), None);
}
#[test]
fn parse_cgroup_v2_path_returns_some() {
assert_eq!(
parse_cgroup_v2_line("0::/system.slice/forgejo-runner.service\n"),
Some("/system.slice/forgejo-runner.service".to_string())
);
}
#[test]
fn parse_cgroup_v2_hybrid_finds_v2_line() {
let input = "12:devices:/user.slice\n11:memory:/user.slice\n0::/foo\n";
assert_eq!(parse_cgroup_v2_line(input), Some("/foo".to_string()));
}
#[test]
fn parse_cgroup_v2_no_newline() {
assert_eq!(parse_cgroup_v2_line("0::/bar"), Some("/bar".to_string()));
}
#[test]
fn parse_cgroup_v2_missing_returns_none() {
assert_eq!(parse_cgroup_v2_line(""), None);
}
}
}