⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/virt/
container.rs

1//! Container management using namespace isolation
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::{
8    collections::BTreeMap,
9    string::{String, ToString},
10    vec::Vec,
11};
12use core::sync::atomic::{AtomicU64, Ordering};
13
14use super::namespace::NamespaceSet;
15use crate::{error::KernelError, process::ProcessId};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ContainerState {
19    Created,
20    Running,
21    Stopped,
22}
23
24impl core::fmt::Display for ContainerState {
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        match self {
27            Self::Created => write!(f, "Created"),
28            Self::Running => write!(f, "Running"),
29            Self::Stopped => write!(f, "Stopped"),
30        }
31    }
32}
33
34#[cfg(feature = "alloc")]
35#[derive(Debug, Clone)]
36pub struct ContainerInfo {
37    pub id: u64,
38    pub name: String,
39    pub state: ContainerState,
40    pub root_pid: Option<ProcessId>,
41    pub process_count: usize,
42    pub hostname: String,
43}
44
45#[cfg(feature = "alloc")]
46pub struct Container {
47    pub id: u64,
48    pub name: String,
49    pub namespaces: NamespaceSet,
50    pub root_process: Option<ProcessId>,
51    pub state: ContainerState,
52    pub init_program: String,
53}
54
55#[cfg(feature = "alloc")]
56impl Container {
57    pub fn info(&self) -> ContainerInfo {
58        ContainerInfo {
59            id: self.id,
60            name: self.name.clone(),
61            state: self.state,
62            root_pid: self.root_process,
63            process_count: self.namespaces.pid.process_count(),
64            hostname: self.namespaces.uts.hostname().to_string(),
65        }
66    }
67}
68
69static NEXT_CONTAINER_ID: AtomicU64 = AtomicU64::new(1);
70pub(crate) static CONTAINER_MGR: spin::Mutex<Option<ContainerManager>> = spin::Mutex::new(None);
71
72#[cfg(feature = "alloc")]
73pub struct ContainerManager {
74    containers: BTreeMap<u64, Container>,
75}
76
77#[cfg(feature = "alloc")]
78impl ContainerManager {
79    pub fn new() -> Self {
80        Self {
81            containers: BTreeMap::new(),
82        }
83    }
84
85    pub fn create(&mut self, name: &str) -> Result<u64, KernelError> {
86        let id = NEXT_CONTAINER_ID.fetch_add(1, Ordering::Relaxed);
87        self.containers.insert(
88            id,
89            Container {
90                id,
91                name: String::from(name),
92                namespaces: NamespaceSet::new(name),
93                root_process: None,
94                state: ContainerState::Created,
95                init_program: String::new(),
96            },
97        );
98        crate::println!("  [container] Created container {} (id={})", name, id);
99        Ok(id)
100    }
101
102    pub fn start(&mut self, id: u64, program: &str) -> Result<(), KernelError> {
103        let container = self.containers.get_mut(&id).ok_or(KernelError::NotFound {
104            resource: "container",
105            id,
106        })?;
107        if container.state != ContainerState::Created {
108            return Err(KernelError::InvalidState {
109                expected: "Created",
110                actual: match container.state {
111                    ContainerState::Running => "Running",
112                    ContainerState::Stopped => "Stopped",
113                    ContainerState::Created => "Created",
114                },
115            });
116        }
117        container.init_program = String::from(program);
118        let placeholder_pid = ProcessId(1000 + id);
119        let cpid = container.namespaces.pid.add_process(placeholder_pid);
120        container.root_process = Some(placeholder_pid);
121        container.state = ContainerState::Running;
122        crate::println!(
123            "  [container] Started {} (id={}, root_pid={}, container_pid={})",
124            container.name,
125            id,
126            placeholder_pid.0,
127            cpid
128        );
129        Ok(())
130    }
131
132    pub fn stop(&mut self, id: u64) -> Result<(), KernelError> {
133        let container = self.containers.get_mut(&id).ok_or(KernelError::NotFound {
134            resource: "container",
135            id,
136        })?;
137        if container.state != ContainerState::Running {
138            return Err(KernelError::InvalidState {
139                expected: "Running",
140                actual: match container.state {
141                    ContainerState::Created => "Created",
142                    ContainerState::Stopped => "Stopped",
143                    ContainerState::Running => "Running",
144                },
145            });
146        }
147        if let Some(root_pid) = container.root_process.take() {
148            container.namespaces.pid.remove_process(root_pid);
149        }
150        container.state = ContainerState::Stopped;
151        crate::println!("  [container] Stopped {} (id={})", container.name, id);
152        Ok(())
153    }
154
155    pub fn destroy(&mut self, id: u64) -> Result<(), KernelError> {
156        let container = self.containers.get(&id).ok_or(KernelError::NotFound {
157            resource: "container",
158            id,
159        })?;
160        if container.state == ContainerState::Running {
161            return Err(KernelError::InvalidState {
162                expected: "Created or Stopped",
163                actual: "Running",
164            });
165        }
166        let name = container.name.clone();
167        self.containers.remove(&id);
168        crate::println!("  [container] Destroyed {} (id={})", name, id);
169        Ok(())
170    }
171
172    pub fn list(&self) -> Vec<ContainerInfo> {
173        self.containers.values().map(|c| c.info()).collect()
174    }
175    pub fn container_count(&self) -> usize {
176        self.containers.len()
177    }
178    pub fn get(&self, id: u64) -> Option<&Container> {
179        self.containers.get(&id)
180    }
181    pub fn get_mut(&mut self, id: u64) -> Option<&mut Container> {
182        self.containers.get_mut(&id)
183    }
184}
185
186#[cfg(feature = "alloc")]
187impl Default for ContainerManager {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193pub fn init() {
194    #[cfg(feature = "alloc")]
195    {
196        let mut mgr = CONTAINER_MGR.lock();
197        *mgr = Some(ContainerManager::new());
198    }
199    crate::println!("  [container] Container manager initialized");
200}
201
202#[cfg(feature = "alloc")]
203pub fn with_container_manager<R, F: FnOnce(&mut ContainerManager) -> R>(
204    f: F,
205) -> Result<R, KernelError> {
206    let mut mgr = CONTAINER_MGR.lock();
207    match mgr.as_mut() {
208        Some(m) => Ok(f(m)),
209        None => Err(KernelError::NotInitialized {
210            subsystem: "container manager",
211        }),
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_container_lifecycle() {
221        let mut mgr = ContainerManager::new();
222        let id = mgr.create("test").unwrap();
223        assert_eq!(mgr.container_count(), 1);
224        assert!(mgr.start(id, "/bin/init").is_ok());
225        assert_eq!(mgr.get(id).unwrap().state, ContainerState::Running);
226        assert!(mgr.stop(id).is_ok());
227        assert_eq!(mgr.get(id).unwrap().state, ContainerState::Stopped);
228        assert!(mgr.destroy(id).is_ok());
229        assert_eq!(mgr.container_count(), 0);
230    }
231
232    #[test]
233    fn test_container_destroy_running_fails() {
234        let mut mgr = ContainerManager::new();
235        let id = mgr.create("test").unwrap();
236        mgr.start(id, "/bin/init").unwrap();
237        assert!(mgr.destroy(id).is_err());
238    }
239
240    #[test]
241    fn test_container_list() {
242        let mut mgr = ContainerManager::new();
243        mgr.create("web").unwrap();
244        mgr.create("db").unwrap();
245        assert_eq!(mgr.list().len(), 2);
246    }
247
248    #[test]
249    fn test_container_state_display() {
250        assert_eq!(alloc::format!("{}", ContainerState::Created), "Created");
251        assert_eq!(alloc::format!("{}", ContainerState::Running), "Running");
252        assert_eq!(alloc::format!("{}", ContainerState::Stopped), "Stopped");
253    }
254}