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 (*)
├── shim.rs
└── stdio.rs
oci.rs
由於每個 runtime 的實作都會需要利用到 OCI 規範中的資訊,因此 oci.rs
將這些資訊抽象化成一個模組,讓每個 runtime 都可以共用,而不是各自實作自己的 OCI 處理函式。
//! Generic helpers for working with OCI specs that can be consumed by any runtime.
//! 通用的 OCI 規格輔助工具,確保 OCI 規格可以被任何 runtime 所消耗。
use std::collections::HashMap;
use std::io::{ErrorKind, Write};
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::process;
use anyhow::Context;
pub use oci_spec::runtime::Spec;
use super::error::Result;
// 處理環境變數
fn parse_env(envs: &[String]) -> HashMap<String, String> {
// make NAME=VALUE to HashMap<NAME, VALUE>.
// 將 NAME=VALUE 轉換成 HashMap<NAME, VALUE>
// 例如: ["PATH=/usr/bin", "HOME=/home"] => {"PATH": "/usr/bin", "HOME": "/home"}
envs.iter()
.filter_map(|e| {
let mut split = e.split('='); // 環境變數的分隔符號必定為 "="
split.next().map(|key| {
let value = split.collect::<Vec<&str>>().join("=");
(key.into(), value)
})
})
.collect()
}
// 設定 prestart hooks
pub(crate) fn setup_prestart_hooks(hooks: &Option<oci_spec::runtime::Hooks>) -> Result<()> {
if let Some(hooks) = hooks {
let prestart_hooks = hooks.prestart().as_ref().unwrap();
for hook in prestart_hooks {
let mut hook_command = process::Command::new(hook.path());
// Based on OCI spec, the first argument of the args vector is the
// arg0, which can be different from the path. For example, path
// may be "/usr/bin/true" and arg0 is set to "true". However, rust
// command differentiates arg0 from args, where rust command arg
// doesn't include arg0. So we have to make the split arg0 from the
// rest of args.
// 根據 OCI 規範,第一個參數 arg0 可以與路徑相異。
// 例如: 路徑可能是 "/usr/bin/true",而 arg0 可能是 "true"。
// 然而,rust command 會將 arg0 與 args 區分開來,在 rust command 的 arg 將不包含 arg0。
// 因此我們必須在此將 arg0 與 args 區分開來,以符合規範。
if let Some((arg0, args)) = hook.args().as_ref().and_then(|a| a.split_first()) {
log::debug!("run_hooks arg0: {:?}, args: {:?}", arg0, args);
// 在 Unix-like 系統中,可以使用 Command::arg0() 來設定 arg0
#[cfg(unix)]
{
hook_command.arg0(arg0).args(args);
}
#[cfg(windows)]
{
// 檢查 arg0 是否與執行檔名相同,若不同則回傳錯誤
if !&hook.path().ends_with(arg0) {
return Err(crate::sandbox::Error::InvalidArgument("Running with arg0 as different name than executable is not supported on Windows due to rust std library process implementation.".to_string()));
}
hook_command.args(args);
}
} else {
#[cfg(unix)]
hook_command.arg0(hook.path());
};
// 處理環境變數
let envs: HashMap<String, String> = if let Some(env) = hook.env() {
parse_env(env)
} else {
HashMap::new()
};
log::debug!("run_hooks envs: {:?}", envs);
// 執行 prestart hooks
let mut hook_process = hook_command
.env_clear()
.envs(envs)
.stdin(process::Stdio::piped())
.spawn()
.with_context(|| "Failed to execute hook")?;
if let Some(stdin) = &mut hook_process.stdin {
// We want to ignore BrokenPipe here. A BrokenPipe indicates
// either the hook is crashed/errored or it ran successfully.
// Either way, this is an indication that the hook command
// finished execution. If the hook command was successful,
// which we will check later in this function, we should not
// fail this step here. We still want to check for all the other
// error, in the case that the hook command is waiting for us to
// write to stdin.
// 我們想要在這裡忽略 BrokenPipe。BrokenPipe 表示 hook 已經崩潰/發生錯誤、或者已經成功執行
// 無論如何,這代表了 hook 已經執行完畢。
// 若 hook command 成功執行,後續在這個韓世忠會進行檢查,在這一步不應該讓他觸發失敗。
// 我們仍然想要檢查所有其他錯誤,比如在 hook command 正在等待我們寫入標準輸入(stdin)的情況下。
let state = format!("{{ \"pid\": {} }}", std::process::id());
if let Err(e) = stdin.write_all(state.as_bytes()) {
if e.kind() != ErrorKind::BrokenPipe {
// Not a broken pipe. The hook command may be waiting
// for us.
// 不是 BrokenPipe 錯誤,可能是 hook command 正在等待我們寫入。
let _ = hook_process.kill();
}
}
}
hook_process.wait()?;
}
}
Ok(())
}