diff --git a/oci_util/src/distribution/client.rs b/oci_util/src/distribution/client.rs index a39a7de..db4829f 100644 --- a/oci_util/src/distribution/client.rs +++ b/oci_util/src/distribution/client.rs @@ -548,7 +548,6 @@ impl Session { // if there is none, just register this manifest let manifest_variant = self.query_manifest(tag).await?; let manifest_list = match manifest_variant { - None => todo!(), Some(ManifestVariant::List(mut manifest_list)) => { let mut manifests = manifest_list .manifests diff --git a/ocitar/src/main.rs b/ocitar/src/main.rs index eb02130..e7cea66 100644 --- a/ocitar/src/main.rs +++ b/ocitar/src/main.rs @@ -26,6 +26,7 @@ mod util; use crate::util::*; use clap::{Parser, Subcommand}; +use sha2::{Digest, Sha256}; use std::fs::File; use std::io::{Read, Write}; use std::process::Command; @@ -216,18 +217,21 @@ fn zfs_dataset_get_mountpoint(dataset: &String) -> Result, std::i } pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> { - let mut output: Box = match args.file.as_str() { + let output: Box = match args.file.as_str() { "-" => Box::new(std::io::stdout()), path => Box::new(File::create(path)?), }; - output = match args.compression { - CompressionType::Zstd => Box::new(ZstdEncoder::new(output, 3)?.auto_finish()), + let sha256 = std::rc::Rc::new(std::cell::RefCell::new(Sha256::new())); + let handle = DigestSink::>::new(output, sha256.clone()); + + let mut output: Box = match args.compression { + CompressionType::Zstd => Box::new(ZstdEncoder::new(handle, 3)?.auto_finish()), CompressionType::Gzip => Box::new(flate2::write::GzEncoder::new( - output, + handle, flate2::Compression::default(), )), - _ => output, + _ => Box::new(handle), }; if args.zfs_diff { @@ -301,7 +305,7 @@ pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> { &removing, &mut output, args.write_to_stderr, - ) + )?; } else { create_tar( args.without_oci, @@ -311,8 +315,18 @@ pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> { &args.remove, &mut output, args.write_to_stderr, - ) + )?; } + + drop(output); + let digest: [u8; 32] = sha256.borrow().clone().finalize().into(); + + if !args.write_to_stderr { + println!("sha256:{}", hex(digest)); + } else { + eprintln!("sha256:{}", hex(digest)); + } + Ok(()) } pub fn do_extract(args: ExtractArgs) -> Result<(), std::io::Error> { @@ -377,6 +391,7 @@ fn create_tar( .arg("-T-") //.args(paths) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::null()) .spawn()?; let mut child_stdin = child.stdin.take().unwrap(); diff --git a/ocitar/src/tar.rs b/ocitar/src/tar.rs index c96e862..4b41b5e 100644 --- a/ocitar/src/tar.rs +++ b/ocitar/src/tar.rs @@ -298,9 +298,10 @@ pub struct Summary { } pub fn remove_path(p: std::path::PathBuf) -> std::io::Result<()> { + /* eprintln!("enter remove path"); log::warn!("removing: {p:?}"); - + */ let path = std::path::PathBuf::from(&p); if path.exists() { if path.is_dir() { @@ -308,10 +309,10 @@ pub fn remove_path(p: std::path::PathBuf) -> std::io::Result<()> { } else { _ = std::fs::remove_file(path); } - eprintln!("exit remove path"); + // eprintln!("exit remove path"); Ok(()) } else { - eprintln!("exit remove path"); + // eprintln!("exit remove path"); Ok(()) } diff --git a/ocitar/src/util.rs b/ocitar/src/util.rs index 72c319c..ce97d47 100644 --- a/ocitar/src/util.rs +++ b/ocitar/src/util.rs @@ -90,6 +90,33 @@ impl Read for DigestReaderHandle { } } + +pub struct DigestSink { + sink: W, + digest: Rc>, +} + +//pub struct DigestSinkHandle(pub Rc>>); + +impl DigestSink { + pub fn new(sink: W, digest: Rc>) -> DigestSink { + DigestSink { + sink, + digest, + } + } +} +impl Write for DigestSink { + fn write(&mut self, buf: &[u8]) -> Result { + let size = self.sink.write(buf)?; + self.digest.borrow_mut().update(&buf[..size]); + Ok(size) + } + fn flush(&mut self) -> Result<(), std::io::Error> { + self.sink.flush() + } +} + pub struct DigestWriter { sink: W, digest: Sha256, @@ -110,11 +137,12 @@ impl DigestWriter { impl Write for DigestWriter { fn write(&mut self, buf: &[u8]) -> Result { - self.digest.update(buf); - self.sink.write(buf) + let size = self.sink.write(buf)?; + self.digest.update(&buf[..size]); + Ok(size) } fn flush(&mut self) -> Result<(), std::io::Error> { - Ok(()) + self.sink.flush() } } diff --git a/varutil/src/string_interpolation.rs b/varutil/src/string_interpolation.rs index 45f742d..61aac1c 100644 --- a/varutil/src/string_interpolation.rs +++ b/varutil/src/string_interpolation.rs @@ -156,6 +156,16 @@ impl std::fmt::Display for Var { } } +impl FromStr for Var { + type Err = std::io::Error; + fn from_str(s: &str) -> Result { + Var::new(s).ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "must conform to IEEE Std 1003.1-2001", + )) + } +} + impl Var { pub fn as_str(&self) -> &str { self.0.as_str() diff --git a/xc-bin/src/jailfile/directives/mod.rs b/xc-bin/src/jailfile/directives/mod.rs index 8bec4b1..47dd09c 100644 --- a/xc-bin/src/jailfile/directives/mod.rs +++ b/xc-bin/src/jailfile/directives/mod.rs @@ -21,6 +21,7 @@ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. +pub mod add_env; pub mod copy; pub mod from; pub mod run; @@ -33,14 +34,14 @@ use anyhow::Context; use std::collections::HashMap; use varutil::string_interpolation::{InterpolatedString, Var}; use xc::models::jail_image::{JailConfig, SpecialMount}; -use xc::models::{EntryPoint, MountSpec, SystemVPropValue}; +use xc::models::{EntryPoint, EnvSpec, MountSpec, SystemVPropValue}; pub(crate) trait Directive: Sized { fn from_action(action: &Action) -> Result; fn run_in_context(&self, context: &mut JailContext) -> Result<(), anyhow::Error>; } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ConfigMod { Allow(Vec), ReplaceAllow(Vec), @@ -55,6 +56,7 @@ pub(crate) enum ConfigMod { Volume(String, MountSpec), Mount(String, String), SysV(Vec), + AddEnv(Var, EnvSpec), } impl ConfigMod { @@ -62,6 +64,9 @@ impl ConfigMod { match self { Self::NoInit => config.init = Vec::new(), Self::NoDeinit => config.deinit = Vec::new(), + Self::AddEnv(variable, spec) => { + config.envs.insert(variable.clone(), spec.clone()); + } Self::Allow(allows) => { for allow in allows.iter() { if let Some(param) = allow.strip_prefix('-') { @@ -232,10 +237,7 @@ impl Directive for ConfigMod { curr = args_iter.next(); } - let arg0 = curr - .as_ref() - .clone() - .context("entry point requires one variable")?; + let arg0 = curr.as_ref().context("entry point requires one variable")?; Ok(ConfigMod::EntryPoint(entry_point, arg0.to_string(), envs)) } "CMD" => { @@ -270,3 +272,52 @@ impl Directive for ConfigMod { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_directive_entrypoint_no_env() { + let action = Action { + directive_name: "ENTRYPOINT".to_string(), + directive_args: HashMap::new(), + args: vec!["arg0".to_string()], + heredoc: None, + }; + let config_mod = ConfigMod::from_action(&action).expect("cannot parse"); + assert_eq!( + config_mod, + ConfigMod::EntryPoint("main".to_string(), "arg0".to_string(), HashMap::new()) + ); + } + + #[test] + fn test_parse_directive_entrypoint_with_env() { + let action = Action { + directive_name: "ENTRYPOINT".to_string(), + directive_args: HashMap::new(), + args: vec![ + "A=BCD".to_string(), + "PATH=/usr/bin:/usr/sbin".to_string(), + "arg0".to_string(), + ], + heredoc: None, + }; + let config_mod = ConfigMod::from_action(&action).expect("cannot parse"); + let mut expect_hashmap = HashMap::new(); + + expect_hashmap.insert( + Var::new("A").unwrap(), + InterpolatedString::new("BCD").unwrap(), + ); + expect_hashmap.insert( + Var::new("PATH").unwrap(), + InterpolatedString::new("/usr/bin:/usr/sbin").unwrap(), + ); + assert_eq!( + config_mod, + ConfigMod::EntryPoint("main".to_string(), "arg0".to_string(), expect_hashmap) + ); + } +} diff --git a/xc-bin/src/main.rs b/xc-bin/src/main.rs index 89bdebb..d85c89b 100644 --- a/xc-bin/src/main.rs +++ b/xc-bin/src/main.rs @@ -255,6 +255,7 @@ fn main() -> Result<(), ActionError> { empty_dns, output_inplace, } => { + use crate::jailfile::directives::add_env::*; use crate::jailfile::directives::copy::*; use crate::jailfile::directives::from::*; use crate::jailfile::directives::run::*; @@ -311,19 +312,25 @@ fn main() -> Result<(), ActionError> { let mut context = JailContext::new(conn, dns, net_req, output_inplace); for action in actions.iter() { - if action.directive_name == "RUN" { - let directive = RunDirective::from_action(action)?; - directive.run_in_context(&mut context)?; - } else if action.directive_name == "FROM" { + macro_rules! do_directive { + ($name:expr, $tpe:ty) => { + if action.directive_name == $name { + let directive = <$tpe>::from_action(action)?; + directive.run_in_context(&mut context)?; + continue; + } + }; + } + + do_directive!("RUN", RunDirective); + do_directive!("COPY", CopyDirective); + do_directive!("VOLUME", VolumeDirective); + do_directive!("ADDENV", AddEnvDirective); + + if action.directive_name == "FROM" { let directive = FromDirective::from_action(action)?; directive.run_in_context(&mut context)?; std::thread::sleep_ms(500); - } else if action.directive_name == "COPY" { - let directive = CopyDirective::from_action(action)?; - directive.run_in_context(&mut context)?; - } else if action.directive_name == "VOLUME" { - let directive = VolumeDirective::from_action(action)?; - directive.run_in_context(&mut context)?; } else if ConfigMod::implemented_directives() .contains(&action.directive_name.as_str()) { diff --git a/xc/src/container/runner.rs b/xc/src/container/runner.rs index 52f55e4..f883ed6 100644 --- a/xc/src/container/runner.rs +++ b/xc/src/container/runner.rs @@ -369,25 +369,32 @@ impl ProcessRunner { cmd.jwork_dir(work_dir); } - let spawn_info = match &exec.output_mode { + let spawn_info_result = match &exec.output_mode { StdioMode::Terminal => { let socket_path = format!("/var/run/xc.{}.{}", self.container.id, id); let log_path = format!("/var/log/xc.{}.{}.log", self.container.id, id); - spawn_process_pty(cmd, &log_path, &socket_path)? + spawn_process_pty(cmd, &log_path, &socket_path) } - StdioMode::Files { stdout, stderr } => spawn_process_files(&mut cmd, stdout, stderr)?, + StdioMode::Files { stdout, stderr } => spawn_process_files(&mut cmd, stdout, stderr), StdioMode::Inherit => { let out_path = format!("/var/log/xc.{}.{}.out.log", self.container.id, id); let err_path = format!("/var/log/xc.{}.{}.err.log", self.container.id, id); - spawn_process_files(&mut cmd, &Some(out_path), &Some(err_path))? + spawn_process_files(&mut cmd, &Some(out_path), &Some(err_path)) } StdioMode::Forward { stdin, stdout, stderr, - } => spawn_process_forward(&mut cmd, *stdin, *stdout, *stderr)?, + } => spawn_process_forward(&mut cmd, *stdin, *stdout, *stderr), }; + let spawn_info = spawn_info_result.map_err(|error| { + if let Some(n) = notify.clone() { + n.notify_waiters(); + } + error + })?; + let pid = spawn_info.pid; tx.send_if_modified(|status| { diff --git a/xc/src/models/jail_image.rs b/xc/src/models/jail_image.rs index 15e2814..d77633a 100644 --- a/xc/src/models/jail_image.rs +++ b/xc/src/models/jail_image.rs @@ -198,6 +198,12 @@ impl Default for JailImage { } impl JailImage { + pub fn os(&self) -> &str { + &self.0.os + } + pub fn architecture(&self) -> &str { + &self.0.architecture + } pub fn chain_id(&self) -> Option { if self.0.rootfs.diff_ids.is_empty() { None diff --git a/xcd/src/context/mod.rs b/xcd/src/context/mod.rs index a4c5264..99cfbb7 100644 --- a/xcd/src/context/mod.rs +++ b/xcd/src/context/mod.rs @@ -294,10 +294,18 @@ impl ServerContext { let output = child.wait_with_output()?; + let (diff_id, _digest) = { + let mut results = std::str::from_utf8(&output.stdout).unwrap().trim().lines(); + let diff_id = results.next().expect("unexpected output"); + let digest = results.next().expect("unexpected output"); + eprintln!("diff_id: {diff_id}"); + ( + OciDigest::from_str(diff_id).unwrap(), + OciDigest::from_str(digest).unwrap() + ) + }; zfs.destroy(format!("{running_dataset}@{commit_id}"), true, true, true)?; - - let diff_id = std::str::from_utf8(&output.stderr).unwrap().trim(); - Ok(OciDigest::from_str(diff_id)?) + Ok(diff_id) } pub(crate) async fn do_commit( @@ -357,14 +365,19 @@ impl ServerContext { .output() .expect("fail to spawn ocitar"); - let diff_id = { - let diff_id = std::str::from_utf8(&output.stdout).unwrap().trim(); + let (diff_id, digest) = { + let mut results = std::str::from_utf8(&output.stdout).unwrap().trim().lines(); + let diff_id = results.next().expect("unexpected output"); + let digest = results.next().expect("unexpected output"); eprintln!("diff_id: {diff_id}"); - eprintln!("rename: {temp_file} -> {}/{diff_id}", config.layers_dir); - std::fs::rename(temp_file, format!("{}/{diff_id}", config.layers_dir))?; - OciDigest::from_str(diff_id).unwrap() + eprintln!("rename: {temp_file} -> {}/{digest}", config.layers_dir); + std::fs::rename(temp_file, format!("{}/{digest}", config.layers_dir))?; + ( + OciDigest::from_str(diff_id).unwrap(), + OciDigest::from_str(digest).unwrap() + ) }; - // + chain_id.consume_diff_id(oci_util::digest::DigestAlgorithm::Sha256, &diff_id); let new_name = format!("{}/{chain_id}", config.image_dataset); zfs.rename(&dst_dataset, new_name)?; @@ -377,7 +390,7 @@ impl ServerContext { .register_and_tag_manifest(name, tag, &manifest) .await; - context.map_diff_id(&diff_id, &diff_id, "plain").await?; + context.map_diff_id(&diff_id, &digest, "zstd").await?; Ok(commit_id) } diff --git a/xcd/src/image/push.rs b/xcd/src/image/push.rs index 00b3743..0baf0ea 100644 --- a/xcd/src/image/push.rs +++ b/xcd/src/image/push.rs @@ -30,6 +30,7 @@ use oci_util::digest::OciDigest; use oci_util::distribution::client::*; use oci_util::image_reference::ImageReference; use oci_util::models::Descriptor; +use oci_util::models::Platform; use serde::{Deserialize, Serialize}; use std::sync::Arc; use thiserror::Error; @@ -267,9 +268,23 @@ pub async fn push_image( state.push_manifest = true; Ok(()) }); - session.register_manifest(&tag, &manifest).await?; - debug!("Registering manifest: {manifest:#?}"); + + let arch = record.manifest.architecture(); + let arch_tag = format!("{tag}-{arch}"); + let platform = Platform { + os: record.manifest.os().to_string(), + architecture: arch.to_string(), + os_version: None, + os_features: Vec::new(), + variant: None, + features: Vec::new(), + }; + let descriptor = session.register_manifest(&arch_tag, &manifest).await?; + session + .merge_manifest_list(&descriptor, &platform, &tag) + .await?; + _ = emitter.use_try(|state| { state.done = true; Ok(())