execgo-runtime 1.0.0-b1

Adaptive data-plane runtime for ExecGo with HTTP API, CLI, capability negotiation, and local resource ledger
Documentation
use std::{
    collections::BTreeMap,
    fs,
    path::{Path, PathBuf},
};

use chrono::Utc;

use crate::types::{
    ExecutionCapabilities, NamespaceCapabilities, ResourceCapabilities, ResourceCapacity,
    RuntimeCapabilities, RuntimePlatform, SandboxCapabilities, StorageCapabilities,
};

#[derive(Debug, Clone)]
pub struct CapabilityProbeInput {
    pub runtime_id: String,
    pub data_dir: PathBuf,
    pub cgroup_root: PathBuf,
    pub max_running_tasks: usize,
    pub disable_linux_sandbox: bool,
    pub disable_cgroup: bool,
    pub capacity_memory_bytes: Option<u64>,
    pub capacity_pids: Option<u64>,
}

pub fn probe_runtime_capabilities(input: &CapabilityProbeInput) -> RuntimeCapabilities {
    let mut warnings = Vec::new();
    let mut overrides = BTreeMap::new();

    if input.disable_linux_sandbox {
        overrides.insert("linux_sandbox".into(), "disabled".into());
    }
    if input.disable_cgroup {
        overrides.insert("cgroup".into(), "disabled".into());
    }
    if let Some(value) = input.capacity_memory_bytes {
        overrides.insert("capacity_memory_bytes".into(), value.to_string());
    }
    if let Some(value) = input.capacity_pids {
        overrides.insert("capacity_pids".into(), value.to_string());
    }

    let data_dir_writable = data_dir_writable(&input.data_dir);
    if !data_dir_writable {
        warnings.push(format!(
            "data-dir is not writable: {}",
            input.data_dir.to_string_lossy()
        ));
    }

    let platform = RuntimePlatform {
        os: std::env::consts::OS.to_string(),
        arch: std::env::consts::ARCH.to_string(),
        containerized: detect_containerized(),
        kubernetes: detect_kubernetes(),
    };

    let linux = cfg!(target_os = "linux");
    let root_user = current_euid() == Some(0);
    let linux_sandbox = linux && root_user && !input.disable_linux_sandbox;
    if !linux_sandbox {
        warnings.push(if linux {
            "linux_sandbox is unavailable without root-equivalent namespace permissions".into()
        } else {
            "linux_sandbox is unavailable on this host".into()
        });
    }

    let cgroup_v2 = linux && Path::new("/sys/fs/cgroup/cgroup.controllers").exists();
    let cgroup_writable =
        cgroup_v2 && !input.disable_cgroup && path_likely_writable(&input.cgroup_root);
    if cgroup_v2 && !cgroup_writable {
        warnings.push(format!(
            "cgroup v2 detected but cgroup root is not writable: {}",
            input.cgroup_root.to_string_lossy()
        ));
    }

    let memory_capacity = input.capacity_memory_bytes.or_else(detect_memory_bytes);
    let pids_capacity = input.capacity_pids.or_else(detect_pids_capacity);

    let resources = ResourceCapabilities {
        rlimit_cpu: cfg!(unix),
        rlimit_memory: cfg!(unix),
        cgroup_v2,
        cgroup_writable,
        memory_limit: cfg!(unix),
        pids_limit: cgroup_writable,
        oom_detection: cgroup_writable,
        cpu_quota: false,
        ledger: true,
        capacity: ResourceCapacity {
            task_slots: input.max_running_tasks as u64,
            memory_bytes: memory_capacity,
            pids: pids_capacity,
        },
    };

    RuntimeCapabilities {
        runtime_id: input.runtime_id.clone(),
        snapshot_version: RuntimeCapabilities::snapshot_version().to_string(),
        collected_at: Utc::now(),
        platform,
        execution: ExecutionCapabilities {
            command: true,
            script: true,
            process_group: cfg!(unix),
        },
        sandbox: SandboxCapabilities {
            process: true,
            linux_sandbox,
            chroot: linux_sandbox,
            namespaces: NamespaceCapabilities {
                mount: linux_sandbox,
                pid: linux_sandbox,
                uts: linux_sandbox,
                ipc: linux_sandbox,
                net: linux_sandbox,
            },
        },
        storage: StorageCapabilities { data_dir_writable },
        resources,
        stable_semantics: stable_semantics(),
        enhanced_semantics: enhanced_semantics(linux_sandbox, cgroup_writable),
        degraded: !warnings.is_empty(),
        warnings,
        overrides,
    }
}

fn stable_semantics() -> Vec<String> {
    [
        "submit",
        "status",
        "events",
        "stdout_stderr",
        "timeout",
        "kill",
        "artifacts",
        "result_persistence",
        "recovery",
    ]
    .into_iter()
    .map(str::to_string)
    .collect()
}

fn enhanced_semantics(linux_sandbox: bool, cgroup_writable: bool) -> Vec<String> {
    let mut items = vec!["resource_ledger".to_string()];
    if linux_sandbox {
        items.extend([
            "linux_sandbox".to_string(),
            "namespaces".to_string(),
            "chroot".to_string(),
        ]);
    }
    if cgroup_writable {
        items.extend([
            "cgroup_memory".to_string(),
            "cgroup_pids".to_string(),
            "oom_detection".to_string(),
        ]);
    }
    items
}

fn data_dir_writable(path: &Path) -> bool {
    let probe_path = path.join(format!(".execgo-runtime-probe-{}", std::process::id()));
    match fs::write(&probe_path, b"probe") {
        Ok(()) => {
            let _ = fs::remove_file(probe_path);
            true
        }
        Err(_) => false,
    }
}

fn path_likely_writable(path: &Path) -> bool {
    let candidate = if path.exists() {
        path
    } else {
        path.parent().unwrap_or(path)
    };
    fs::metadata(candidate)
        .map(|metadata| !metadata.permissions().readonly())
        .unwrap_or(false)
}

fn detect_containerized() -> bool {
    Path::new("/.dockerenv").exists()
        || Path::new("/run/.containerenv").exists()
        || read_to_string("/proc/1/cgroup").is_some_and(|contents| {
            contents.contains("docker")
                || contents.contains("containerd")
                || contents.contains("kubepods")
        })
}

fn detect_kubernetes() -> bool {
    std::env::var_os("KUBERNETES_SERVICE_HOST").is_some()
        || read_to_string("/proc/1/cgroup").is_some_and(|contents| contents.contains("kubepods"))
}

fn detect_memory_bytes() -> Option<u64> {
    #[cfg(unix)]
    unsafe {
        let pages = libc::sysconf(libc::_SC_PHYS_PAGES);
        let page_size = libc::sysconf(libc::_SC_PAGESIZE);
        if pages > 0 && page_size > 0 {
            return Some((pages as u64).saturating_mul(page_size as u64));
        }
    }
    None
}

fn detect_pids_capacity() -> Option<u64> {
    #[cfg(target_os = "linux")]
    {
        read_to_string("/proc/sys/kernel/pid_max")
            .and_then(|value| value.trim().parse::<u64>().ok())
    }
    #[cfg(not(target_os = "linux"))]
    {
        None
    }
}

fn current_euid() -> Option<u32> {
    #[cfg(unix)]
    unsafe {
        Some(libc::geteuid())
    }
    #[cfg(not(unix))]
    {
        None
    }
}

fn read_to_string(path: &str) -> Option<String> {
    fs::read_to_string(path).ok()
}

#[cfg(test)]
mod tests {
    use tempfile::TempDir;

    use super::*;

    #[test]
    fn probe_returns_stable_shape() {
        let temp = TempDir::new().expect("tempdir");
        let capabilities = probe_runtime_capabilities(&CapabilityProbeInput {
            runtime_id: "test-runtime".into(),
            data_dir: temp.path().to_path_buf(),
            cgroup_root: temp.path().join("cgroup"),
            max_running_tasks: 3,
            disable_linux_sandbox: true,
            disable_cgroup: true,
            capacity_memory_bytes: Some(1024),
            capacity_pids: Some(64),
        });

        assert_eq!(capabilities.runtime_id, "test-runtime");
        assert_eq!(capabilities.resources.capacity.task_slots, 3);
        assert_eq!(capabilities.resources.capacity.memory_bytes, Some(1024));
        assert!(!capabilities.sandbox.linux_sandbox);
        assert_eq!(
            capabilities
                .overrides
                .get("linux_sandbox")
                .map(String::as_str),
            Some("disabled")
        );
    }
}