hypha: implement the ability to execute container images

This commit is contained in:
Alex Zenla 2024-01-22 05:08:14 -08:00
parent f3103c6c51
commit e15ac71405
No known key found for this signature in database
GPG Key ID: 067B238899B51269
3 changed files with 155 additions and 15 deletions

View File

@ -48,6 +48,10 @@ features = ["v4"]
default-features = false
version = "2.1.1"
[dependencies.nix]
features = ["process"]
version = "0.27.1"
[lib]
path = "src/lib.rs"

View File

@ -1,8 +1,18 @@
use crate::error::Result;
use oci_spec::image::ImageConfiguration;
use crate::hypha_err;
use log::trace;
use nix::libc::dup2;
use nix::unistd::execve;
use oci_spec::image::{Config, ImageConfiguration};
use std::ffi::CString;
use std::fs;
use std::fs::{File, OpenOptions};
use std::os::fd::AsRawFd;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::chroot;
use std::path::Path;
use sys_mount::{FilesystemType, Mount, MountFlags};
use walkdir::WalkDir;
const IMAGE_BLOCK_DEVICE_PATH: &str = "/dev/xvda";
const CONFIG_BLOCK_DEVICE_PATH: &str = "/dev/xvdb";
@ -15,8 +25,7 @@ const OVERLAY_IMAGE_BIND_PATH: &str = "/overlay/image";
const OVERLAY_WORK_PATH: &str = "/overlay/work";
const OVERLAY_UPPER_PATH: &str = "/overlay/upper";
const PIVOT_PATH: &str = "/pivot";
const NEW_ROOT_PATH: &str = "/newroot";
const IMAGE_CONFIG_JSON_PATH: &str = "/config/image/config.json";
pub struct ContainerInit {}
@ -33,19 +42,27 @@ impl ContainerInit {
}
pub fn init(&mut self) -> Result<()> {
self.mount_early()?;
let config = self.parse_image_config()?;
self.mount_late()?;
let console = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/console")?;
self.mount_squashfs_images()?;
let config = self.parse_image_config()?;
self.mount_new_root()?;
self.nuke_initrd()?;
self.bind_new_root()?;
self.map_console(console)?;
if let Some(cfg) = config.config() {
if let Some(cmd) = cfg.cmd() {
println!("image command: {:?}", cmd);
}
self.run(cfg)?;
} else {
return hypha_err!("unable to determine what to execute, image config doesn't tell us");
}
Ok(())
}
fn mount_early(&mut self) -> Result<()> {
fn mount_squashfs_images(&mut self) -> Result<()> {
trace!("mounting squashfs images");
let image_mount_path = Path::new(IMAGE_MOUNT_PATH);
let config_mount_path = Path::new(CONFIG_MOUNT_PATH);
self.mount_squashfs(Path::new(IMAGE_BLOCK_DEVICE_PATH), image_mount_path)?;
@ -54,6 +71,7 @@ impl ContainerInit {
}
fn mount_squashfs(&mut self, from: &Path, to: &Path) -> Result<()> {
trace!("mounting squashfs image {:?} to {:?}", from, to);
if !to.is_dir() {
fs::create_dir(to)?;
}
@ -64,10 +82,13 @@ impl ContainerInit {
Ok(())
}
fn mount_late(&mut self) -> Result<()> {
fn mount_new_root(&mut self) -> Result<()> {
trace!("mounting new root");
self.mount_overlay_tmpfs()?;
self.bind_image_to_overlay_tmpfs()?;
self.mount_overlay_to_pivot()?;
self.mount_overlay_to_new_root()?;
std::env::set_current_dir(NEW_ROOT_PATH)?;
trace!("mounted new root");
Ok(())
}
@ -90,8 +111,8 @@ impl ContainerInit {
Ok(())
}
fn mount_overlay_to_pivot(&mut self) -> Result<()> {
fs::create_dir(PIVOT_PATH)?;
fn mount_overlay_to_new_root(&mut self) -> Result<()> {
fs::create_dir(NEW_ROOT_PATH)?;
Mount::builder()
.fstype(FilesystemType::Manual("overlay"))
.flags(MountFlags::NOATIME)
@ -99,13 +120,115 @@ impl ContainerInit {
"lowerdir={},upperdir={},workdir={}",
OVERLAY_IMAGE_BIND_PATH, OVERLAY_UPPER_PATH, OVERLAY_WORK_PATH
))
.mount(format!("overlayfs:{}", OVERLAY_MOUNT_PATH), PIVOT_PATH)?;
.mount(format!("overlayfs:{}", OVERLAY_MOUNT_PATH), NEW_ROOT_PATH)?;
Ok(())
}
fn parse_image_config(&mut self) -> Result<ImageConfiguration> {
trace!("parsing image config");
let image_config_path = Path::new(IMAGE_CONFIG_JSON_PATH);
let config = ImageConfiguration::from_file(image_config_path)?;
Ok(config)
}
fn nuke_initrd(&mut self) -> Result<()> {
trace!("nuking initrd");
let initrd_dev = fs::metadata("/")?.st_dev();
for item in WalkDir::new("/")
.same_file_system(true)
.follow_links(false)
.contents_first(true)
{
if item.is_err() {
continue;
}
let item = item?;
let metadata = match item.metadata() {
Ok(value) => value,
Err(_) => continue,
};
if metadata.st_dev() != initrd_dev {
continue;
}
if metadata.is_symlink() || metadata.is_file() {
let _ = fs::remove_file(item.path());
trace!("deleting file {:?}", item.path());
} else if metadata.is_dir() {
let _ = fs::remove_dir(item.path());
trace!("deleting directory {:?}", item.path());
}
}
trace!("nuked initrd");
Ok(())
}
fn bind_new_root(&mut self) -> Result<()> {
trace!("binding new root");
Mount::builder()
.fstype(FilesystemType::Manual("none"))
.flags(MountFlags::BIND)
.mount(".", "/")?;
trace!("chrooting into new root");
chroot(".")?;
trace!("setting root as current directory");
std::env::set_current_dir("/")?;
Ok(())
}
fn map_console(&mut self, console: File) -> Result<()> {
trace!("map console");
unsafe {
dup2(console.as_raw_fd(), 0);
dup2(console.as_raw_fd(), 1);
dup2(console.as_raw_fd(), 2);
}
drop(console);
Ok(())
}
fn run(&mut self, config: &Config) -> Result<()> {
let mut cmd = match config.cmd() {
None => vec![],
Some(value) => value.clone(),
};
if cmd.is_empty() {
cmd.push("/bin/sh".to_string());
}
trace!("running container command: {}", cmd.join(" "));
let path = cmd.remove(0);
let mut env = match config.env() {
None => vec![],
Some(value) => value.clone(),
};
env.push("HYPHA_CONTAINER=1".to_string());
let path_cstr = CString::new(path)?;
let mut cmd_cstr = ContainerInit::strings_as_cstrings(cmd)?;
cmd_cstr.push(CString::new("")?);
let mut env_cstr = ContainerInit::strings_as_cstrings(env)?;
env_cstr.push(CString::new("")?);
let mut working_dir = config
.working_dir()
.as_ref()
.map(|x| x.to_string())
.unwrap_or("/".to_string());
if working_dir.is_empty() {
working_dir = "/".to_string();
}
std::env::set_current_dir(&working_dir)?;
execve(&path_cstr, &cmd_cstr, &env_cstr)?;
Ok(())
}
fn strings_as_cstrings(values: Vec<String>) -> Result<Vec<CString>> {
let mut results: Vec<CString> = vec![];
for value in values {
results.push(CString::new(value.as_bytes().to_vec())?);
}
Ok(results)
}
}

View File

@ -2,6 +2,7 @@ use backhand::BackhandError;
use cli_tables::TableError;
use oci_spec::OciSpecError;
use std::error::Error;
use std::ffi::NulError;
use std::fmt::{Display, Formatter};
use std::num::ParseIntError;
use std::path::StripPrefixError;
@ -127,3 +128,15 @@ impl From<TableError> for HyphaError {
HyphaError::new(value.to_string().as_str())
}
}
impl From<NulError> for HyphaError {
fn from(value: NulError) -> Self {
HyphaError::new(value.to_string().as_str())
}
}
impl From<nix::Error> for HyphaError {
fn from(value: nix::Error) -> Self {
HyphaError::new(value.to_string().as_str())
}
}