Skip to content

Commit 3d62276

Browse files
committed
nydus-image: enhance unpack command to support extracting files to dir
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 in the following 3 aspects: 1. Allow --output to be passed as a directory. In this case, the tar file will be output to this subdirectory. 2. Added --untar flag to support decompressing this tar file into the directory specified by --output. 3. When --output is not specified, the default output is to /dev/stdout Signed-off-by: Qinqi Qu <[email protected]>
1 parent c9fbce8 commit 3d62276

File tree

2 files changed

+91
-10
lines changed

2 files changed

+91
-10
lines changed

src/bin/nydus-image/main.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -720,8 +720,16 @@ fn prepare_cmd_args(bti_string: &'static str) -> App {
720720
.arg(
721721
Arg::new("output")
722722
.long("output")
723-
.help("path for output tar file")
724-
.required(true),
723+
.default_value("/dev/stdout")
724+
.help("Path for output tar file, if not configured, it will default to STDOUT.")
725+
.required(false),
726+
)
727+
.arg(
728+
Arg::new("untar")
729+
.long("untar")
730+
.action(ArgAction::SetTrue)
731+
.help("Untar all files to the output dir, creating dir if it does not exist.")
732+
.required(false),
725733
),
726734
)
727735
}
@@ -1363,11 +1371,12 @@ impl Command {
13631371
}
13641372
}
13651373
};
1374+
let untar = matches.get_flag("untar");
13661375

1367-
OCIUnpacker::new(bootstrap, backend, output)
1368-
.with_context(|| "fail to create unpacker")?
1376+
OCIUnpacker::new(bootstrap, backend, output, untar)
1377+
.with_context(|| "failed to create unpacker")?
13691378
.unpack(config)
1370-
.with_context(|| "fail to unpack")
1379+
.with_context(|| format!("failed to unpack image to: {}", output))
13711380
}
13721381

13731382
fn check(matches: &ArgMatches, build_info: &BuildTimeInfo) -> Result<()> {

src/bin/nydus-image/unpack/mod.rs

+77-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// SPDX-License-Identifier: Apache-2.0
55
use std::collections::HashMap;
6-
use std::fs::{File, OpenOptions};
6+
use std::fs::{create_dir_all, remove_file, File, OpenOptions};
77
use std::io::Read;
88
use std::path::{Path, PathBuf};
99
use std::rc::Rc;
@@ -18,7 +18,7 @@ use nydus_rafs::{
1818
};
1919
use nydus_storage::backend::BlobBackend;
2020
use nydus_storage::device::BlobInfo;
21-
use tar::{Builder, Header};
21+
use tar::{Archive, Builder, Header};
2222

2323
use self::pax::{
2424
OCIBlockBuilder, OCICharBuilder, OCIDirBuilder, OCIFifoBuilder, OCILinkBuilder, OCIRegBuilder,
@@ -32,11 +32,12 @@ pub trait Unpacker {
3232
fn unpack(&self, config: Arc<ConfigV2>) -> Result<()>;
3333
}
3434

35-
/// A unpacker with the ability to convert bootstrap file and blob file to tar
35+
/// A unpacker with the ability to convert bootstrap file and blob file to tar or dir.
3636
pub struct OCIUnpacker {
3737
bootstrap: PathBuf,
3838
blob_backend: Option<Arc<dyn BlobBackend + Send + Sync>>,
3939
output: PathBuf,
40+
untar: bool,
4041

4142
builder_factory: OCITarBuilderFactory,
4243
}
@@ -46,6 +47,7 @@ impl OCIUnpacker {
4647
bootstrap: &Path,
4748
blob_backend: Option<Arc<dyn BlobBackend + Send + Sync>>,
4849
output: &str,
50+
untar: bool,
4951
) -> Result<Self> {
5052
let bootstrap = bootstrap.to_path_buf();
5153
let output = PathBuf::from(output);
@@ -57,31 +59,72 @@ impl OCIUnpacker {
5759
bootstrap,
5860
blob_backend,
5961
output,
62+
untar,
6063
})
6164
}
6265

6366
fn load_rafs(&self, config: Arc<ConfigV2>) -> Result<RafsSuper> {
6467
let (rs, _) = RafsSuper::load_from_file(self.bootstrap.as_path(), config, false)?;
6568
Ok(rs)
6669
}
70+
71+
fn get_unpack_path(&self) -> Result<PathBuf> {
72+
// If output ends with path separator, then it is a dir.
73+
let is_dir = self
74+
.output
75+
.to_string_lossy()
76+
.ends_with(std::path::MAIN_SEPARATOR);
77+
78+
// Unpack the tar file to a subdirectory
79+
if is_dir || self.untar {
80+
if !self.output.exists() {
81+
create_dir_all(&self.output)?;
82+
}
83+
let tar_path = self
84+
.output
85+
.join(self.bootstrap.file_stem().unwrap_or_default())
86+
.with_extension("tar");
87+
88+
return Ok(tar_path);
89+
}
90+
91+
// Unpack the tar file to the specified location
92+
Ok(self.output.clone())
93+
}
6794
}
6895

6996
impl Unpacker for OCIUnpacker {
7097
fn unpack(&self, config: Arc<ConfigV2>) -> Result<()> {
7198
debug!(
72-
"oci unpacker, bootstrap file: {:?}, output file: {:?}",
99+
"oci unpacker, bootstrap file: {:?}, output path: {:?}",
73100
self.bootstrap, self.output
74101
);
75102

76103
let rafs = self.load_rafs(config)?;
77104

105+
let tar_path = self.get_unpack_path()?;
78106
let mut builder = self
79107
.builder_factory
80-
.create(&rafs, &self.blob_backend, &self.output)?;
108+
.create(&rafs, &self.blob_backend, &tar_path)?;
81109

82110
for (node, path) in RafsIterator::new(&rafs) {
83111
builder.append(node, &path)?;
84112
}
113+
info!("successfully unpack image to: {}", tar_path.display());
114+
115+
// untar this tar file to self.output dir
116+
if self.untar {
117+
let file = File::open(&tar_path)?;
118+
let mut tar = Archive::new(file);
119+
tar.unpack(&self.output)?;
120+
remove_file(&tar_path)?;
121+
122+
info!(
123+
"successfully untar {} to: {}",
124+
tar_path.display(),
125+
self.output.display()
126+
);
127+
}
85128

86129
Ok(())
87130
}
@@ -230,3 +273,32 @@ impl TarBuilder for OCITarBuilder {
230273
bail!("node {:?} can not be unpacked", path)
231274
}
232275
}
276+
277+
#[cfg(test)]
278+
mod tests {
279+
use super::*;
280+
#[test]
281+
fn test_get_unpack_path() {
282+
// test data: (bootstrap, output, untar, expected_tar_path)
283+
let test_cases = [
284+
("./test", "target.tar", false, "target.tar"),
285+
("test/test", "target", false, "target"),
286+
("test/test", "target/", false, "target/test.tar"),
287+
("/run/test.meta", "target/", false, "target/test.tar"),
288+
("/run/test.meta", "/run/", false, "/run/test.tar"),
289+
("./test", "target.tar", true, "target.tar/test.tar"),
290+
("test/test", "target", true, "target/test.tar"),
291+
("test/test", "target/", true, "target/test.tar"),
292+
];
293+
294+
for (bootstrap, output, untar, expected_tar_path) in test_cases {
295+
let unpacker = OCIUnpacker::new(Path::new(bootstrap), None, output, untar).unwrap();
296+
let tar_path = unpacker.get_unpack_path().unwrap();
297+
assert_eq!(
298+
tar_path,
299+
PathBuf::from(expected_tar_path),
300+
"tar_path not equal to expected_tar_path"
301+
);
302+
}
303+
}
304+
}

0 commit comments

Comments
 (0)