diff --git a/src/bin/nydus-image/main.rs b/src/bin/nydus-image/main.rs index 29dd8a0e072..63cef4c30ce 100644 --- a/src/bin/nydus-image/main.rs +++ b/src/bin/nydus-image/main.rs @@ -720,8 +720,16 @@ fn prepare_cmd_args(bti_string: &'static str) -> App { .arg( Arg::new("output") .long("output") - .help("path for output tar file") - .required(true), + .default_value("/dev/stdout") + .help("Path for output tar file, if not configured, it will default to STDOUT.") + .required(false), + ) + .arg( + Arg::new("untar") + .long("untar") + .action(ArgAction::SetTrue) + .help("Untar all files to the output dir, creating dir if it does not exist.") + .required(false), ), ) } @@ -1363,11 +1371,12 @@ impl Command { } } }; + let untar = matches.get_flag("untar"); - OCIUnpacker::new(bootstrap, backend, output) - .with_context(|| "fail to create unpacker")? + OCIUnpacker::new(bootstrap, backend, output, untar) + .with_context(|| "failed to create unpacker")? .unpack(config) - .with_context(|| "fail to unpack") + .with_context(|| format!("failed to unpack image to: {}", output)) } fn check(matches: &ArgMatches, build_info: &BuildTimeInfo) -> Result<()> { diff --git a/src/bin/nydus-image/unpack/mod.rs b/src/bin/nydus-image/unpack/mod.rs index 81587172c7c..fbcac0a4f39 100644 --- a/src/bin/nydus-image/unpack/mod.rs +++ b/src/bin/nydus-image/unpack/mod.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: Apache-2.0 use std::collections::HashMap; -use std::fs::{File, OpenOptions}; +use std::fs::{create_dir_all, remove_file, File, OpenOptions}; use std::io::Read; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -18,7 +18,7 @@ use nydus_rafs::{ }; use nydus_storage::backend::BlobBackend; use nydus_storage::device::BlobInfo; -use tar::{Builder, Header}; +use tar::{Archive, Builder, Header}; use self::pax::{ OCIBlockBuilder, OCICharBuilder, OCIDirBuilder, OCIFifoBuilder, OCILinkBuilder, OCIRegBuilder, @@ -32,11 +32,12 @@ pub trait Unpacker { fn unpack(&self, config: Arc) -> Result<()>; } -/// A unpacker with the ability to convert bootstrap file and blob file to tar +/// A unpacker with the ability to convert bootstrap file and blob file to tar or dir. pub struct OCIUnpacker { bootstrap: PathBuf, blob_backend: Option>, output: PathBuf, + untar: bool, builder_factory: OCITarBuilderFactory, } @@ -46,6 +47,7 @@ impl OCIUnpacker { bootstrap: &Path, blob_backend: Option>, output: &str, + untar: bool, ) -> Result { let bootstrap = bootstrap.to_path_buf(); let output = PathBuf::from(output); @@ -57,6 +59,7 @@ impl OCIUnpacker { bootstrap, blob_backend, output, + untar, }) } @@ -64,24 +67,64 @@ impl OCIUnpacker { let (rs, _) = RafsSuper::load_from_file(self.bootstrap.as_path(), config, false)?; Ok(rs) } + + fn get_unpack_path(&self) -> Result { + // If output ends with path separator, then it is a dir. + let is_dir = self + .output + .to_string_lossy() + .ends_with(std::path::MAIN_SEPARATOR); + + // Unpack the tar file to a subdirectory + if is_dir || self.untar { + if !self.output.exists() { + create_dir_all(&self.output)?; + } + let tar_path = self + .output + .join(self.bootstrap.file_stem().unwrap_or_default()) + .with_extension("tar"); + + return Ok(tar_path); + } + + // Unpack the tar file to the specified location + Ok(self.output.clone()) + } } impl Unpacker for OCIUnpacker { fn unpack(&self, config: Arc) -> Result<()> { debug!( - "oci unpacker, bootstrap file: {:?}, output file: {:?}", + "oci unpacker, bootstrap file: {:?}, output path: {:?}", self.bootstrap, self.output ); let rafs = self.load_rafs(config)?; + let tar_path = self.get_unpack_path()?; let mut builder = self .builder_factory - .create(&rafs, &self.blob_backend, &self.output)?; + .create(&rafs, &self.blob_backend, &tar_path)?; for (node, path) in RafsIterator::new(&rafs) { builder.append(node, &path)?; } + info!("successfully unpack image to: {}", tar_path.display()); + + // untar this tar file to self.output dir + if self.untar { + let file = File::open(&tar_path)?; + let mut tar = Archive::new(file); + tar.unpack(&self.output)?; + remove_file(&tar_path)?; + + info!( + "successfully untar {} to: {}", + tar_path.display(), + self.output.display() + ); + } Ok(()) } @@ -230,3 +273,32 @@ impl TarBuilder for OCITarBuilder { bail!("node {:?} can not be unpacked", path) } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_get_unpack_path() { + // test data: (bootstrap, output, untar, expected_tar_path) + let test_cases = [ + ("./test", "target.tar", false, "target.tar"), + ("test/test", "target", false, "target"), + ("test/test", "target/", false, "target/test.tar"), + ("/run/test.meta", "target/", false, "target/test.tar"), + ("/run/test.meta", "/run/", false, "/run/test.tar"), + ("./test", "target.tar", true, "target.tar/test.tar"), + ("test/test", "target", true, "target/test.tar"), + ("test/test", "target/", true, "target/test.tar"), + ]; + + for (bootstrap, output, untar, expected_tar_path) in test_cases { + let unpacker = OCIUnpacker::new(Path::new(bootstrap), None, output, untar).unwrap(); + let tar_path = unpacker.get_unpack_path().unwrap(); + assert_eq!( + tar_path, + PathBuf::from(expected_tar_path), + "tar_path not equal to expected_tar_path" + ); + } + } +}