Skip to content

Commit

Permalink
nydus-image: enhance unpack command to support extracting files to dir
Browse files Browse the repository at this point in the history
When --output is specified as a directory, the nydus-image unpack
command will return an error, which is not convenient for users.

This patch enhances this command to allow --output to be passed into a
dir. In this case, nydus-image will extract all files into this dir.

Signed-off-by: Qinqi Qu <[email protected]>
  • Loading branch information
adamqqqplay committed Nov 10, 2023
1 parent c9fbce8 commit 73b6718
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 8 deletions.
6 changes: 3 additions & 3 deletions src/bin/nydus-image/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ fn prepare_cmd_args(bti_string: &'static str) -> App {
.arg(
Arg::new("output")
.long("output")
.help("path for output tar file")
.help("Path for output tar file, If it is a directory, extract all files to there.")
.required(true),
),
)
Expand Down Expand Up @@ -1365,9 +1365,9 @@ impl Command {
};

OCIUnpacker::new(bootstrap, backend, output)
.with_context(|| "fail to create unpacker")?
.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<()> {
Expand Down
72 changes: 67 additions & 5 deletions src/bin/nydus-image/unpack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -32,7 +32,7 @@ pub trait Unpacker {
fn unpack(&self, config: Arc<ConfigV2>) -> 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<Arc<dyn BlobBackend + Send + Sync>>,
Expand Down Expand Up @@ -66,23 +66,54 @@ impl OCIUnpacker {
}
}

// If output ends with path separator, then it is a dir.
// Output a tar file first, and untar it later.
fn get_unpack_path(bootstrap: PathBuf, output: PathBuf) -> Result<(bool, PathBuf)> {
let is_dir = output
.to_string_lossy()
.ends_with(std::path::MAIN_SEPARATOR);
let mut tar_path = output.clone();
if is_dir {
if !output.exists() {
create_dir_all(&output)?;
}
let mut base_name = output.as_os_str().to_os_string();
base_name.push(bootstrap.file_name().unwrap_or_default());
base_name.push(".tar");
tar_path = PathBuf::from(base_name);
}

Ok((is_dir, tar_path))
}

impl Unpacker for OCIUnpacker {
fn unpack(&self, config: Arc<ConfigV2>) -> 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 (is_dir, tar_path) = get_unpack_path(self.bootstrap.clone(), self.output.clone())?;

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)?;
}

// untar this tar file to self.output dir
if is_dir {
let file = File::open(&tar_path)?;
let mut tar = Archive::new(file);
tar.unpack(&self.output)?;
remove_file(&tar_path)?;
}

info!("successfully unpack image to: {}", self.output.display());
Ok(())
}
}
Expand Down Expand Up @@ -230,3 +261,34 @@ impl TarBuilder for OCITarBuilder {
bail!("node {:?} can not be unpacked", path)
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_unpack_path() {
let bootstrap = PathBuf::from("bootstrap");
let output = PathBuf::from("target.tar");
let (is_dir, tar_path) = get_unpack_path(bootstrap, output).unwrap();
assert!(!is_dir);
assert_eq!(tar_path, PathBuf::from("target.tar"));

let bootstrap = PathBuf::from("test/bootstrap");
let output = PathBuf::from("target/");
let (is_dir, tar_path) = get_unpack_path(bootstrap, output).unwrap();
assert!(is_dir);
assert_eq!(tar_path, PathBuf::from("target/bootstrap.tar"));

let bootstrap = PathBuf::from("/run/test/bootstrap.meta");
let output = PathBuf::from("target/");
let (is_dir, tar_path) = get_unpack_path(bootstrap, output).unwrap();
assert!(is_dir);
assert_eq!(tar_path, PathBuf::from("target/bootstrap.meta.tar"));

let bootstrap = PathBuf::from("/run/test/bootstrap.meta");
let output = PathBuf::from("/run/");
let (is_dir, tar_path) = get_unpack_path(bootstrap, output).unwrap();
assert!(is_dir);
assert_eq!(tar_path, PathBuf::from("/run/bootstrap.meta.tar"));
}
}

0 comments on commit 73b6718

Please sign in to comment.