iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Cloud Native

關於 WebAssembly 也能變成 Container 的這檔事系列 第 14

Wasm+containerd-shim-wasm+sandbox - part 5

  • 分享至 

  • xImage
  •  

Wasm+containerd-shim-wasm+sandbox - part 5

sandbox 資料夾結構

sandbox
├── cli.rs (part 2)
├── error.rs (part 1)
├── instance.rs
├── instance_utils.rs
├── manager.rs (part 3)
├── mod.rs (part 1)
├── oci.rs (part 4)
├── shim.rs
└── stdio.rs (*)

stdio.rs

stdio 的全稱是 standard input/output,為標準輸入/輸出的意思,用來處理標準輸入(stdin)、標準輸出(stdout)、標準錯誤輸出(stderr)。
由於每個實體都有各自的 stdin、stdout、stderr,但我們不能讓其共用,還記得這個專案提供的服務是 WASM container,不同的 container 的 stdio 自然不能混用,因此這個模組是用來映射不同的實體到不同的 stdio。

use std::io::ErrorKind::NotFound;
use std::io::{Error, Result};
use std::path::Path;
use std::sync::{Arc, OnceLock};

use super::InstanceConfig;
use crate::sys::stdio::*;

#[derive(Default, Clone)]
pub struct Stdio {
    pub stdin: Stdin,
    pub stdout: Stdout,
    pub stderr: Stderr,
}

static INITIAL_STDIO: OnceLock<Stdio> = OnceLock::new();

impl Stdio {
    pub fn redirect(self) -> Result<()> {
        self.stdin.redirect()?;
        self.stdout.redirect()?;
        self.stderr.redirect()?;
        Ok(())
    }

    pub fn take(&self) -> Self {
        Self {
            stdin: self.stdin.take(),
            stdout: self.stdout.take(),
            stderr: self.stderr.take(),
        }
    }

    /// `init_from_cfg` 使用 `InstanceConfig` 中的 stdio 來初始化 `Stdio`
    pub fn init_from_cfg(cfg: &InstanceConfig<impl Send + Sync + Clone>) -> Result<Self> {
        Ok(Self {
            stdin: cfg.get_stdin().try_into()?,
            stdout: cfg.get_stdout().try_into()?,
            stderr: cfg.get_stderr().try_into()?,
        })
    }

    /// `init_from_std` 使用預設的標準輸入/輸出來初始化 `Stdio`
    pub fn init_from_std() -> Self {
        Self {
            stdin: Stdin::try_from_std().unwrap_or_default(),
            stdout: Stdout::try_from_std().unwrap_or_default(),
            stderr: Stderr::try_from_std().unwrap_or_default(),
        }
    }

    pub fn guard(self) -> impl Drop {
        StdioGuard(self)
    }
}

struct StdioGuard(Stdio);

impl Drop for StdioGuard {
    fn drop(&mut self) {
        let _ = self.0.take().redirect();
    }
}

#[derive(Clone, Default)]
pub struct StdioStream<const FD: StdioRawFd>(Arc<StdioOwnedFd>);

/// `StdioStream` 是一個用來處理標準輸入/輸出的資料結構
impl<const FD: StdioRawFd> StdioStream<FD> {
    pub fn redirect(self) -> Result<()> {
        if let Some(fd) = self.0.as_raw_fd() {
            // Before any redirection we try to keep a copy of the original stdio
            // to make sure the streams stay open
            // 在進行任何重導向之前,嘗試保留原始的 stdio 的副本,以確保資料流保持開啟
            INITIAL_STDIO.get_or_init(Stdio::init_from_std);

            if unsafe { libc::dup2(fd, FD) } == -1 {
                return Err(Error::last_os_error());
            }
        }
        Ok(())
    }

    pub fn take(&self) -> Self {
        Self(Arc::new(self.0.take()))
    }

    pub fn try_from_std() -> Result<Self> {
        let fd: i32 = unsafe { libc::dup(FD) };
        if fd == -1 {
            return Err(Error::last_os_error());
        }
        Ok(Self(Arc::new(unsafe { StdioOwnedFd::from_raw_fd(fd) })))
    }
}

/// 實作 `TryFrom` trait,用來將 `Option<P>` 轉換成 `StdioStream<FD>`
impl<P: AsRef<Path>, const FD: StdioRawFd> TryFrom<Option<P>> for StdioStream<FD> {
    type Error = Error;
    fn try_from(path: Option<P>) -> Result<Self> {
        let fd = path
            .and_then(|path| match path.as_ref() {
                // 空路徑,則回傳 `None`
                path if path.as_os_str().is_empty() => None,
                // 否則嘗試從路徑建立 `StdioOwnedFd`
                path => Some(StdioOwnedFd::try_from_path(path)),
            })
            .transpose()
            .or_else(|err| match err.kind() {
                // 如果路徑不存在,則回傳 `None`
                NotFound => Ok(None),
                // 反之,回傳拿到的錯誤
                _ => Err(err),
            })?
            .unwrap_or_default();

        Ok(Self(Arc::new(fd)))
    }
}

pub type Stdin = StdioStream<STDIN_FILENO>;
pub type Stdout = StdioStream<STDOUT_FILENO>;
pub type Stderr = StdioStream<STDERR_FILENO>;

/// 以下是測試程式碼
#[cfg(test)]
mod test {
    use std::fs::File;

    use tempfile::tempdir;

    use super::*;

    /// containerd can send an empty path or a non-existant path
    /// In both these cases we should just assume that the stdio stream was not setup (intentionally)
    /// Any other error is a real error.
    /// containerd 能夠傳送一個空路徑或一個不存在的路徑
    /// 在這兩種情況下,我們應該假設標準輸出入資料流沒有設置
    /// 任何其他錯誤都是真實的錯誤。
    #[test]
    fn test_maybe_open_stdio() -> anyhow::Result<()> {
        // None
        let s = Stdout::try_from(None::<&Path>)?;
        assert!(s.0.take().as_raw_fd().is_none());

        // empty path
        // 空路徑
        let s = Stdout::try_from(Some(""))?;
        assert!(s.0.take().as_raw_fd().is_none());

        // nonexistent path
        // 不存在的路徑
        let s = Stdout::try_from(Some("/some/nonexistent/path"))?;
        assert!(s.0.take().as_raw_fd().is_none());

        // valid path
        // 合法的路徑
        let dir = tempdir()?;
        let path = dir.path().join("testfile");
        let temp = File::create(&path)?;
        drop(temp);

        // a valid path should not fail
        // 合法的路徑不應該觸發失敗
        let s = Stdout::try_from(Some(&path))?;
        assert!(s.0.take().as_raw_fd().is_some());
        Ok(())
    }
}


上一篇
Wasm+containerd-shim-wasm+sandbox - part 4
下一篇
Wasm+containerd-shim-wasm 中場休息
系列文
關於 WebAssembly 也能變成 Container 的這檔事15
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言