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(())
}
}