duende_platform/
detect.rs

1//! Platform detection.
2//!
3//! # Toyota Way: Poka-Yoke (ポカヨケ)
4//! Auto-detect current platform with fallback chain.
5//! Fail to safest option (Native) if detection fails.
6
7use std::path::Path;
8
9/// Supported platforms.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Platform {
12    /// Linux with systemd.
13    Linux,
14    /// macOS with launchd.
15    MacOS,
16    /// Docker/OCI container.
17    Container,
18    /// Pepita microVM.
19    PepitaMicroVM,
20    /// WebAssembly OS.
21    Wos,
22    /// Native process (fallback).
23    Native,
24}
25
26impl Platform {
27    /// Returns the platform name.
28    #[must_use]
29    pub const fn name(&self) -> &'static str {
30        match self {
31            Self::Linux => "linux",
32            Self::MacOS => "macos",
33            Self::Container => "container",
34            Self::PepitaMicroVM => "pepita",
35            Self::Wos => "wos",
36            Self::Native => "native",
37        }
38    }
39}
40
41impl std::fmt::Display for Platform {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(f, "{}", self.name())
44    }
45}
46
47/// Auto-detect current platform with fallback chain.
48///
49/// # Detection Order (Poka-Yoke: fail to safest option)
50/// 1. WOS: Check for WASM runtime markers
51/// 2. pepita: Check for virtio devices
52/// 3. Container: Check for /.dockerenv or cgroup markers
53/// 4. Linux: Check for systemd
54/// 5. macOS: Check for launchd
55/// 6. Fallback: Native process
56#[must_use]
57pub fn detect_platform() -> Platform {
58    // WOS: Check for WASM runtime
59    if cfg!(target_arch = "wasm32") || std::env::var("WOS_KERNEL").is_ok() {
60        return Platform::Wos;
61    }
62
63    // pepita: Check for virtio devices or env marker
64    if Path::new("/dev/virtio-ports").exists() || std::env::var("PEPITA_VM").is_ok() {
65        return Platform::PepitaMicroVM;
66    }
67
68    // Container: Check for Docker/containerd markers
69    if is_container() {
70        return Platform::Container;
71    }
72
73    // Linux: Check for systemd
74    #[cfg(target_os = "linux")]
75    if Path::new("/run/systemd/system").exists() {
76        return Platform::Linux;
77    }
78
79    // macOS
80    #[cfg(target_os = "macos")]
81    return Platform::MacOS;
82
83    // Fallback: Native
84    Platform::Native
85}
86
87/// Checks if running inside a container.
88fn is_container() -> bool {
89    // Docker marker file
90    if Path::new("/.dockerenv").exists() {
91        return true;
92    }
93
94    // cgroup markers
95    if let Ok(content) = std::fs::read_to_string("/proc/1/cgroup")
96        && (content.contains("docker") || content.contains("containerd") || content.contains("lxc"))
97    {
98        return true;
99    }
100
101    // Container environment variable
102    if std::env::var("container").is_ok() {
103        return true;
104    }
105
106    false
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_platform_name() {
115        assert_eq!(Platform::Linux.name(), "linux");
116        assert_eq!(Platform::Native.name(), "native");
117    }
118
119    #[test]
120    fn test_platform_display() {
121        assert_eq!(Platform::Linux.to_string(), "linux");
122    }
123
124    #[test]
125    fn test_detect_platform_returns_valid() {
126        let platform = detect_platform();
127        // Should return some valid platform
128        assert!(matches!(
129            platform,
130            Platform::Linux
131                | Platform::MacOS
132                | Platform::Container
133                | Platform::PepitaMicroVM
134                | Platform::Wos
135                | Platform::Native
136        ));
137    }
138}