Skip to content

Commit

Permalink
add simplified libcontainer instance api
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
  • Loading branch information
jprendes committed Aug 18, 2023
1 parent 6287dff commit 243c034
Show file tree
Hide file tree
Showing 14 changed files with 487 additions and 567 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use oci_spec::runtime::Spec;

use crate::sandbox::oci;

use super::stdio::Stdio;

pub struct RuntimeContext {
pub module: Option<String>,
pub method: String,
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
pub stdio: Stdio,
}

impl From<&Spec> for RuntimeContext {
fn from(spec: &Spec) -> Self {
let (module, method) = oci::get_module(spec);

Self {
module,
method,
args: oci::get_args(spec).to_vec(),
envs: envs_from_spec(spec),
stdio: Stdio::default(),
}
}
}

fn envs_from_spec(spec: &Spec) -> Vec<(String, String)> {
spec.process()
.as_ref()
.and_then(|p| p.env().clone())
.unwrap_or_default()
.into_iter()
.map(|v| match v.split_once('=') {
None => (v, "".to_string()),
Some((k, v)) => (k.to_string(), v.to_string()),
})
.collect()
}
123 changes: 123 additions & 0 deletions crates/containerd-shim-wasm/src/libcontainer_instance/easy/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::{
fs::OpenOptions,
io::Read,
os::unix::prelude::PermissionsExt,
path::{Path, PathBuf},
};

use libcontainer::workload::Executor;
use oci_spec::runtime::Spec;

use super::context::RuntimeContext;

pub trait Engine: Send + Sync + 'static {
fn name() -> &'static str;
fn run(&self, ctx: RuntimeContext, spec: &Spec) -> anyhow::Result<i32>;
fn can_handle(&self, ctx: RuntimeContext, _spec: &Spec) -> bool {
is_wasm_binary(ctx.module)
}
}

#[derive(Default)]
pub struct LinuxContainerEngine(libcontainer::workload::default::DefaultExecutor);

impl Engine for LinuxContainerEngine {
fn name() -> &'static str {
libcontainer::workload::default::DefaultExecutor::default().name()
}

fn can_handle(&self, ctx: RuntimeContext, _spec: &Spec) -> bool {
is_linux_executable(&ctx.args)
}

fn run(&self, ctx: RuntimeContext, spec: &Spec) -> anyhow::Result<i32> {
ctx.stdio.redirect().unwrap();
self.0.exec(spec)?;
Ok(0)
}
}

fn is_wasm_binary(module: Option<String>) -> bool {
// check if the entrypoint of the spec is a wasm binary.
if let Some(module) = module {
Path::new(&module)
.extension()
.map(std::ffi::OsStr::to_ascii_lowercase)
.is_some_and(|ext| ext == "wasm" || ext == "wat")
} else {
log::info!("WasmEdge cannot handle this workload, no arguments provided");
false
}
}

fn is_linux_executable(args: &[String]) -> bool {
if args.is_empty() {
return false;
}

let executable = args[0].as_str();

// mostly follows youki's verify_binary implementation
// https://github.com/containers/youki/blob/2d6fd7650bb0f22a78fb5fa982b5628f61fe25af/crates/libcontainer/src/process/container_init_process.rs#L106
let path = if executable.contains('/') {
PathBuf::from(executable)
} else {
let path = std::env::var("PATH").unwrap_or_default();
// check each path in $PATH
let mut found = false;
let mut found_path = PathBuf::default();
for p in path.split(':') {
let path = PathBuf::from(p).join(executable);
if path.exists() {
found = true;
found_path = path;
break;
}
}
if !found {
return false;
}
found_path
};

// check execute permission
let metadata = path.metadata();
if metadata.is_err() {
log::info!("failed to get metadata of {:?}", path);
return false;
}
let metadata = metadata.unwrap();
let permissions = metadata.permissions();
if !metadata.is_file() || permissions.mode() & 0o001 == 0 {
log::info!("{} is not a file or has no execute permission", executable);
return false;
}

// check the shebang and ELF magic number
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
let mut buffer = [0; 4];

let file = OpenOptions::new().read(true).open(path);
if file.is_err() {
log::info!("failed to open {}", executable);
return false;
}
let mut file = file.unwrap();
match file.read_exact(&mut buffer) {
Ok(_) => {}
Err(err) => {
log::info!("failed to read shebang of {}: {}", executable, err);
return false;
}
}
match buffer {
// ELF magic number
[0x7f, 0x45, 0x4c, 0x46] => true,
// shebang
[0x23, 0x21, ..] => true,
_ => {
log::info!("{} is not a valid script or elf file", executable);
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::sync::Arc;

use libcontainer::workload::ExecutorError;

use oci_spec::runtime::Spec;

use crate::libcontainer_instance::easy::context::RuntimeContext;

use super::{engine::Engine, stdio::Stdio};

pub struct Executor<E: Engine> {
pub engine: Arc<E>,
pub stdio: Stdio,
}

impl<E: Engine> libcontainer::workload::Executor for Executor<E> {
fn can_handle(&self, spec: &Spec) -> bool {
self.engine.can_handle(spec.into(), spec)
}

fn name(&self) -> &'static str {
E::name()
}

fn exec(&self, spec: &oci_spec::runtime::Spec) -> Result<(), ExecutorError> {
log::info!("calling start function");
let ctx = RuntimeContext {
stdio: self.stdio.clone(),
..spec.into()
};
match self.engine.run(ctx, spec) {
Ok(code) => std::process::exit(code),
Err(err) => {
log::info!("error running start function: {err}");
std::process::exit(137)
}
};
}
}
123 changes: 123 additions & 0 deletions crates/containerd-shim-wasm/src/libcontainer_instance/easy/instance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::{
fs::File,
io::ErrorKind,
path::{Path, PathBuf},
sync::Arc,
};

use libcontainer::{container::builder::ContainerBuilder, syscall::syscall::create_syscall};

use serde::{Deserialize, Serialize};

use crate::sandbox::{self, instance::ExitCode, InstanceConfig};

use super::{
super::LibcontainerInstance,
engine::{Engine, LinuxContainerEngine},
executor::Executor,
stdio::Stdio,
};

static DEFAULT_CONTAINER_ROOT_DIR: &str = "/run/containerd";

pub struct Instance<E: Engine> {
engine: Arc<E>,
exit_code: ExitCode,
stdio: Stdio,
bundle: PathBuf,
rootdir: PathBuf,
id: String,
}

impl<E: Engine> LibcontainerInstance for Instance<E> {
type Engine = Arc<E>;

fn new_libcontainer(id: String, cfg: Option<&InstanceConfig<Self::Engine>>) -> Self {
let cfg = cfg.unwrap();
let engine = cfg.get_engine();
let bundle = cfg.get_bundle().unwrap_or_default().into();
let rootdir = determine_rootdir(E::name(), &bundle, cfg.get_namespace()).unwrap();
let stdio = Stdio {
stdin: cfg.get_stdin().try_into().expect("failed to open stdin"),
stdout: cfg.get_stdout().try_into().expect("failed to open stdout"),
stderr: cfg.get_stderr().try_into().expect("failed to open stderr"),
};
Self {
id,
exit_code: ExitCode::default(),
engine,
stdio,
bundle,
rootdir,
}
}

fn get_id(&self) -> String {
self.id.clone()
}

fn get_root_dir(&self) -> sandbox::Result<PathBuf> {
Ok(self.rootdir.clone())
}

fn get_exit_code(&self) -> ExitCode {
self.exit_code.clone()
}

fn build_container(&self) -> Result<libcontainer::container::Container, sandbox::Error> {
let syscall = create_syscall();

let err_others =
|err| sandbox::Error::Others(format!("failed to create container: {}", err));

let wasm_executor = Box::new(Executor {
engine: self.engine.clone(),
stdio: self.stdio.clone(),
});

let linux_executor: Box<Executor<LinuxContainerEngine>> = Box::new(Executor {
engine: Arc::new(LinuxContainerEngine::default()),
stdio: self.stdio.clone(),
});

let container = ContainerBuilder::new(self.id.clone(), syscall.as_ref())
.with_executor(vec![linux_executor, wasm_executor])
.map_err(err_others)?
.with_root_path(self.rootdir.clone())
.map_err(err_others)?
.as_init(&self.bundle)
.with_systemd(false)
.build()
.map_err(err_others)?;

Ok(container)
}
}

#[derive(Serialize, Deserialize)]
struct Options {
root: Option<PathBuf>,
}

fn determine_rootdir<P: AsRef<Path>>(
name: &str,
bundle: P,
namespace: String,
) -> std::io::Result<PathBuf> {
let default_root_dir = Path::new(DEFAULT_CONTAINER_ROOT_DIR).join(name);

let file = match File::open(bundle.as_ref().join("options.json")) {
Ok(f) => f,
Err(err) if err.kind() == ErrorKind::NotFound => {
return Ok(default_root_dir.join(namespace));
}
Err(err) => return Err(err),
};

let path = serde_json::from_reader::<_, Options>(file)?
.root
.unwrap_or(default_root_dir)
.join(namespace);

Ok(path)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod context;
pub mod engine;
pub mod executor;
pub mod instance;
pub mod stdio;
Loading

0 comments on commit 243c034

Please sign in to comment.