1#[cfg(feature = "alloc")]
5use alloc::{string::String, vec::Vec};
6use core::sync::atomic::{AtomicU64, Ordering};
7
8use super::{parse_u32, parse_u64};
9use crate::error::KernelError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum OciLifecycleState {
14 Creating,
16 Created,
19 Running,
21 Stopped,
23}
24
25impl core::fmt::Display for OciLifecycleState {
26 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27 match self {
28 Self::Creating => write!(f, "creating"),
29 Self::Created => write!(f, "created"),
30 Self::Running => write!(f, "running"),
31 Self::Stopped => write!(f, "stopped"),
32 }
33 }
34}
35
36#[cfg(feature = "alloc")]
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct OciMount {
40 pub destination: String,
42 pub mount_type: String,
44 pub source: String,
46 pub options: Vec<String>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum OciNamespaceKind {
53 Pid,
54 Network,
55 Mount,
56 Ipc,
57 Uts,
58 User,
59 Cgroup,
60}
61
62impl OciNamespaceKind {
63 pub fn from_str_kind(s: &str) -> Option<Self> {
65 match s {
66 "pid" => Some(Self::Pid),
67 "network" => Some(Self::Network),
68 "mount" => Some(Self::Mount),
69 "ipc" => Some(Self::Ipc),
70 "uts" => Some(Self::Uts),
71 "user" => Some(Self::User),
72 "cgroup" => Some(Self::Cgroup),
73 _ => None,
74 }
75 }
76
77 pub fn as_str(&self) -> &'static str {
79 match self {
80 Self::Pid => "pid",
81 Self::Network => "network",
82 Self::Mount => "mount",
83 Self::Ipc => "ipc",
84 Self::Uts => "uts",
85 Self::User => "user",
86 Self::Cgroup => "cgroup",
87 }
88 }
89}
90
91#[cfg(feature = "alloc")]
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct OciNamespace {
95 pub kind: OciNamespaceKind,
96 pub path: Option<String>,
98}
99
100#[cfg(feature = "alloc")]
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct OciHook {
104 pub path: String,
106 pub args: Vec<String>,
108 pub env: Vec<String>,
110 pub timeout_secs: u32,
112}
113
114#[cfg(feature = "alloc")]
116#[derive(Debug, Clone, Default, PartialEq, Eq)]
117pub struct OciHooks {
118 pub prestart: Vec<OciHook>,
119 pub poststart: Vec<OciHook>,
120 pub poststop: Vec<OciHook>,
121}
122
123#[cfg(feature = "alloc")]
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct OciRoot {
127 pub path: String,
129 pub readonly: bool,
131}
132
133#[cfg(feature = "alloc")]
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct OciProcess {
137 pub args: Vec<String>,
139 pub env: Vec<String>,
141 pub cwd: String,
143 pub uid: u32,
145 pub gid: u32,
147 pub terminal: bool,
149}
150
151#[cfg(feature = "alloc")]
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct OciLinuxConfig {
155 pub namespaces: Vec<OciNamespace>,
157 pub cgroups_path: String,
159 pub memory_limit: u64,
161 pub cpu_shares: u32,
163 pub cpu_quota: u64,
165 pub cpu_period: u64,
167}
168
169#[cfg(feature = "alloc")]
170impl Default for OciLinuxConfig {
171 fn default() -> Self {
172 Self {
173 namespaces: Vec::new(),
174 cgroups_path: String::new(),
175 memory_limit: 0,
176 cpu_shares: 1024,
177 cpu_quota: 0,
178 cpu_period: 100_000,
179 }
180 }
181}
182
183#[cfg(feature = "alloc")]
185#[derive(Debug, Clone)]
186pub struct OciConfig {
187 pub oci_version: String,
189 pub root: OciRoot,
191 pub process: OciProcess,
193 pub mounts: Vec<OciMount>,
195 pub hostname: String,
197 pub hooks: OciHooks,
199 pub linux: OciLinuxConfig,
201}
202
203#[cfg(feature = "alloc")]
204impl OciConfig {
205 pub fn parse(input: &str) -> Result<Self, KernelError> {
216 let mut config = Self {
217 oci_version: String::from("1.0.2"),
218 root: OciRoot {
219 path: String::from("/"),
220 readonly: false,
221 },
222 process: OciProcess {
223 args: Vec::new(),
224 env: Vec::new(),
225 cwd: String::from("/"),
226 uid: 0,
227 gid: 0,
228 terminal: false,
229 },
230 mounts: Vec::new(),
231 hostname: String::new(),
232 hooks: OciHooks::default(),
233 linux: OciLinuxConfig::default(),
234 };
235
236 for line in input.lines() {
237 let line = line.trim();
238 if line.is_empty() || line.starts_with('#') {
239 continue;
240 }
241 if let Some((key, val)) = line.split_once('=') {
242 let key = key.trim();
243 let val = val.trim();
244 match key {
245 "oci_version" => config.oci_version = String::from(val),
246 "root_path" => config.root.path = String::from(val),
247 "root_readonly" => config.root.readonly = val == "true",
248 "hostname" => config.hostname = String::from(val),
249 "process_cwd" => config.process.cwd = String::from(val),
250 "process_uid" => {
251 config.process.uid = parse_u32(val).unwrap_or(0);
252 }
253 "process_gid" => {
254 config.process.gid = parse_u32(val).unwrap_or(0);
255 }
256 "process_terminal" => config.process.terminal = val == "true",
257 "process_arg" => config.process.args.push(String::from(val)),
258 "process_env" => config.process.env.push(String::from(val)),
259 "mount" => {
260 let parts: Vec<&str> = val.splitn(4, ':').collect();
262 if parts.len() >= 3 {
263 let options = if parts.len() > 3 {
264 parts[3]
265 .split(',')
266 .map(|s| String::from(s.trim()))
267 .collect()
268 } else {
269 Vec::new()
270 };
271 config.mounts.push(OciMount {
272 destination: String::from(parts[0]),
273 mount_type: String::from(parts[1]),
274 source: String::from(parts[2]),
275 options,
276 });
277 }
278 }
279 "namespace" => {
280 if let Some((kind_str, path)) = val.split_once(':') {
281 if let Some(kind) = OciNamespaceKind::from_str_kind(kind_str) {
282 config.linux.namespaces.push(OciNamespace {
283 kind,
284 path: Some(String::from(path)),
285 });
286 }
287 } else if let Some(kind) = OciNamespaceKind::from_str_kind(val) {
288 config
289 .linux
290 .namespaces
291 .push(OciNamespace { kind, path: None });
292 }
293 }
294 "cgroups_path" => config.linux.cgroups_path = String::from(val),
295 "memory_limit" => {
296 config.linux.memory_limit = parse_u64(val).unwrap_or(0);
297 }
298 "cpu_shares" => {
299 config.linux.cpu_shares = parse_u32(val).unwrap_or(1024);
300 }
301 "cpu_quota" => {
302 config.linux.cpu_quota = parse_u64(val).unwrap_or(0);
303 }
304 "cpu_period" => {
305 config.linux.cpu_period = parse_u64(val).unwrap_or(100_000);
306 }
307 "hook_prestart" | "hook_poststart" | "hook_poststop" => {
308 let hook = parse_hook(val);
309 match key {
310 "hook_prestart" => config.hooks.prestart.push(hook),
311 "hook_poststart" => config.hooks.poststart.push(hook),
312 "hook_poststop" => config.hooks.poststop.push(hook),
313 _ => {}
314 }
315 }
316 _ => {} }
318 }
319 }
320
321 Ok(config)
322 }
323
324 pub fn validate(&self) -> Result<(), KernelError> {
326 if self.root.path.is_empty() {
327 return Err(KernelError::InvalidArgument {
328 name: "root.path",
329 value: "empty",
330 });
331 }
332 if self.process.args.is_empty() {
333 return Err(KernelError::InvalidArgument {
334 name: "process.args",
335 value: "empty",
336 });
337 }
338 if self.process.cwd.is_empty() || !self.process.cwd.starts_with('/') {
339 return Err(KernelError::InvalidArgument {
340 name: "process.cwd",
341 value: "must be absolute path",
342 });
343 }
344 Ok(())
345 }
346}
347
348#[cfg(feature = "alloc")]
350#[derive(Debug)]
351pub struct OciContainer {
352 pub id: String,
354 pub state: OciLifecycleState,
356 pub config: OciConfig,
358 pub pid: u64,
360 pub bundle: String,
362 pub created_at: u64,
364}
365
366#[cfg(feature = "alloc")]
367impl OciContainer {
368 pub fn new(id: &str, bundle: &str, config: OciConfig) -> Result<Self, KernelError> {
370 config.validate()?;
371 Ok(Self {
372 id: String::from(id),
373 state: OciLifecycleState::Creating,
374 config,
375 pid: 0,
376 bundle: String::from(bundle),
377 created_at: CONTAINER_COUNTER.fetch_add(1, Ordering::Relaxed),
378 })
379 }
380
381 pub fn mark_created(&mut self) -> Result<(), KernelError> {
383 if self.state != OciLifecycleState::Creating {
384 return Err(KernelError::InvalidState {
385 expected: "creating",
386 actual: self.state_str(),
387 });
388 }
389 self.state = OciLifecycleState::Created;
390 Ok(())
391 }
392
393 pub fn start(&mut self, pid: u64) -> Result<(), KernelError> {
395 if self.state != OciLifecycleState::Created {
396 return Err(KernelError::InvalidState {
397 expected: "created",
398 actual: self.state_str(),
399 });
400 }
401 self.pid = pid;
402 self.state = OciLifecycleState::Running;
403 Ok(())
404 }
405
406 pub fn stop(&mut self) -> Result<(), KernelError> {
408 if self.state != OciLifecycleState::Running {
409 return Err(KernelError::InvalidState {
410 expected: "running",
411 actual: self.state_str(),
412 });
413 }
414 self.state = OciLifecycleState::Stopped;
415 Ok(())
416 }
417
418 fn state_str(&self) -> &'static str {
419 match self.state {
420 OciLifecycleState::Creating => "creating",
421 OciLifecycleState::Created => "created",
422 OciLifecycleState::Running => "running",
423 OciLifecycleState::Stopped => "stopped",
424 }
425 }
426
427 pub fn pivot_root(&self) -> Result<(String, String), KernelError> {
430 if self.config.root.path.is_empty() {
431 return Err(KernelError::InvalidArgument {
432 name: "root",
433 value: "empty path",
434 });
435 }
436 let old_root = String::from("/.pivot_root");
437 let new_root = self.config.root.path.clone();
438 Ok((old_root, new_root))
439 }
440}
441
442static CONTAINER_COUNTER: AtomicU64 = AtomicU64::new(1);
443
444#[cfg(feature = "alloc")]
446pub(super) fn parse_hook(val: &str) -> OciHook {
447 let (path, timeout) = if let Some((p, t)) = val.split_once(':') {
448 (p, parse_u32(t).unwrap_or(0))
449 } else {
450 (val, 0)
451 };
452 OciHook {
453 path: String::from(path),
454 args: Vec::new(),
455 env: Vec::new(),
456 timeout_secs: timeout,
457 }
458}