containerd_shimkit/sandbox/
instance_utils.rs

1//! Common utilities for the containerd shims.
2
3use std::fs::File;
4use std::io::{Error as IoError, ErrorKind, Result as IoResult};
5use std::path::PathBuf;
6
7use serde::{Deserialize, Serialize};
8
9use super::{Error, InstanceConfig};
10use crate::sys::DEFAULT_CONTAINER_ROOT_DIR;
11use crate::sys::stdio::open;
12
13#[derive(Serialize, Deserialize)]
14struct Options {
15    root: Option<PathBuf>,
16}
17
18impl InstanceConfig {
19    /// Determine the root directory for the container runtime.
20    ///
21    /// If the `bundle` directory contains an `options.json` file, the root directory is read from the
22    /// file. Otherwise, the root directory is determined by `{DEFAULT_CONTAINER_ROOT_DIR}/{runtime}/{namespace}`.
23    ///
24    /// The default root directory is `/run/containerd/<wasm engine name>/<namespace>`.
25    #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))]
26    pub fn determine_rootdir(
27        &self,
28        runtime: impl AsRef<str> + std::fmt::Debug,
29    ) -> Result<PathBuf, Error> {
30        let rootdir = DEFAULT_CONTAINER_ROOT_DIR.join(runtime.as_ref());
31        let file = match File::open(self.bundle.join("options.json")) {
32            Ok(f) => f,
33            Err(e) if e.kind() == ErrorKind::NotFound => return Ok(rootdir.join(&self.namespace)),
34            Err(e) => return Err(e.into()),
35        };
36        let path = serde_json::from_reader::<_, Options>(file)?
37            .root
38            .unwrap_or(rootdir)
39            .join(&self.namespace);
40        log::info!("container runtime root path is {path:?}");
41        Ok(path)
42    }
43
44    pub fn open_stdin(&self) -> IoResult<File> {
45        if self.stdin.as_os_str().is_empty() {
46            return Err(IoError::new(ErrorKind::NotFound, "File not found"));
47        }
48        open(&self.stdin)
49    }
50
51    pub fn open_stdout(&self) -> IoResult<File> {
52        if self.stdout.as_os_str().is_empty() {
53            return Err(IoError::new(ErrorKind::NotFound, "File not found"));
54        }
55        open(&self.stdout)
56    }
57
58    pub fn open_stderr(&self) -> IoResult<File> {
59        if self.stderr.as_os_str().is_empty() {
60            return Err(IoError::new(ErrorKind::NotFound, "File not found"));
61        }
62        open(&self.stderr)
63    }
64}
65
66#[cfg(unix)]
67#[cfg(test)]
68mod tests {
69    use tempfile::tempdir;
70
71    use super::*;
72
73    #[test]
74    fn test_determine_rootdir_with_options_file() -> Result<(), Error> {
75        let namespace = "test_namespace";
76        let dir = tempdir()?;
77        let rootdir = dir.path().join("runwasi");
78        let opts = Options {
79            root: Some(rootdir.clone()),
80        };
81        std::fs::write(
82            dir.path().join("options.json"),
83            serde_json::to_string(&opts)?,
84        )?;
85        let cfg = InstanceConfig {
86            bundle: dir.path().to_path_buf(),
87            namespace: namespace.to_string(),
88            ..Default::default()
89        };
90        let root = cfg.determine_rootdir("runtime")?;
91        assert_eq!(root, rootdir.join(namespace));
92        Ok(())
93    }
94
95    #[test]
96    fn test_determine_rootdir_without_options_file() -> Result<(), Error> {
97        let dir = tempdir()?;
98        let namespace = "test_namespace";
99        let cfg = InstanceConfig {
100            bundle: dir.path().to_path_buf(),
101            namespace: namespace.to_string(),
102            ..Default::default()
103        };
104        let root = cfg.determine_rootdir("runtime")?;
105        assert!(root.is_absolute());
106        assert_eq!(
107            root,
108            PathBuf::from("/run/containerd/runtime").join(namespace)
109        );
110        Ok(())
111    }
112}