use std::path::{Path, PathBuf};
use crate::Result;
use crate::extractor::{Entries, Entry, ToteExtractor};
pub(super) struct Extractor {}
impl ToteExtractor for Extractor {
fn list(&self, target: PathBuf) -> Result<Entries> {
log::info!("listing CPIO archive: {target:?}");
let mut file = std::fs::File::open(&target)
.map(cpio::Archive::new)
.map_err(crate::Error::IO)?;
let mut entries: Vec<Entry> = vec![];
loop {
let entry = file.read_entry()
.map_err(crate::Error::IO)?;
match entry {
Some(entry) => {
if entry.metadata.is_file() {
entries.push(create_new_entry(&entry.path, &entry.metadata));
}
},
None => break,
}
}
Ok(Entries::new(target, entries))
}
fn perform(&self, target: PathBuf, base: PathBuf) -> Result<()> {
log::info!("extracting CPIO archive: {target:?}");
let mut file = std::fs::File::open(&target)
.map(cpio::Archive::new)
.map_err(crate::Error::IO)?;
let mut errs = vec![];
loop {
let r = file.read_entry();
match r {
Ok(Some(entry)) => {
if !entry.metadata.is_file() {
continue;
}
match prepare_write(&entry, &base) {
Ok(dest_path) => write_to(entry, &dest_path, &mut errs),
Err(e) => errs.push(e),
}
},
Ok(None) => break,
Err(e) => errs.push(crate::Error::IO(e)),
}
};
crate::Error::error_or((), errs)
}
}
fn prepare_write(entry: &cpio::Entry<std::fs::File>, base: &Path) -> Result<PathBuf>{
let dest_path = base.join(&entry.path);
log::info!("extracting {:?} ({} bytes) to {dest_path:?}", &entry.path, entry.metadata.size());
if let Some(parent) = dest_path.parent() {
std::fs::create_dir_all(parent)
.map_err(crate::Error::IO)?;
};
Ok(dest_path)
}
fn write_to(mut entry: cpio::Entry<std::fs::File>, dest_path: &Path, errs: &mut Vec<crate::Error>) {
match std::fs::File::create(dest_path) {
Ok(mut dest_file) => if let Err(e) = std::io::copy(&mut entry.reader, &mut dest_file) {
errs.push(crate::Error::IO(e));
},
Err(e) => {
log::error!("failed to create file {dest_path:?}: {e}");
errs.push(crate::Error::IO(e));
}
}
}
fn create_new_entry(path: &Path, entry: &cpio::Metadata) -> Entry {
let timestamp = entry.mtime();
let ndt = chrono::DateTime::from_timestamp(timestamp as i64, 0)
.map(|d| d.naive_utc());
Entry::builder()
.name(path.to_string_lossy().to_string())
.original_size(entry.size())
.unix_mode(entry.mode())
.date(ndt)
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_archives() {
let file = PathBuf::from("../testdata/test.cpio");
let extractor = Extractor {};
match extractor.list(file) {
Ok(r) => {
let r = r.iter().map(|e| e.name.clone()).collect::<Vec<_>>();
assert_eq!(r.len(), 16);
assert_eq!(r.get(0), Some("./Cargo.toml".to_string()).as_ref());
assert_eq!(r.get(1), Some("./LICENSE".to_string()).as_ref());
assert_eq!(r.get(2), Some("./build.rs".to_string()).as_ref());
assert_eq!(r.get(3), Some("./README.md".to_string()).as_ref());
}
Err(_) => assert!(false),
}
}
#[test]
fn test_extract_archive() {
let archive_file = PathBuf::from("../testdata/test.cpio");
let opts = crate::ExtractConfig::builder()
.dest("results/cpio")
.use_archive_name_dir(true)
.overwrite(true)
.build();
match crate::extract(archive_file, &opts) {
Ok(_) => {
assert!(true);
assert!(PathBuf::from("results/cpio/test/Cargo.toml").exists());
std::fs::remove_dir_all(PathBuf::from("results/cpio")).unwrap();
}
Err(e) => {
eprintln!("{:?}", e);
assert!(false);
}
};
}
}