feat(rust): extract .tgz archives (Rust-only short suffix)
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Extraction backends: `sevenz-rust2` for .7z, `zip` for .zip, `unrar` for
|
//! Extraction backends: `sevenz-rust2` for .7z, `zip` for .zip, `unrar` for
|
||||||
//! .rar — pure Rust, no external binaries. Tarballs and bare compressed
|
//! .rar — pure Rust, no external binaries. Tarballs and bare compressed
|
||||||
//! files (.tar/.tar.gz/.tar.bz2/.tar.xz/.gz/.bz2/.xz) are handled by the
|
//! files (.tar/.tar.gz/.tgz/.tar.bz2/.tar.xz/.gz/.bz2/.xz) are handled by the
|
||||||
//! system `tar`/`gzip`/`bzip2`/`xz` binaries, exactly like the python
|
//! system `tar`/`gzip`/`bzip2`/`xz` binaries, exactly like the python
|
||||||
//! version shelled out to `7z`: merged output capture, exit-code parity
|
//! version shelled out to `7z`: merged output capture, exit-code parity
|
||||||
//! (0 ok, 1 warnings), and the 4h per-archive timeout. The tar/flate2/
|
//! (0 ok, 1 warnings), and the 4h per-archive timeout. The tar/flate2/
|
||||||
@@ -37,8 +37,11 @@ use std::time::{Duration, Instant};
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
/// Archive suffixes recognized by the extractors (python `ARCHIVE_SUFFIXES`).
|
/// Archive suffixes recognized by the extractors (python `ARCHIVE_SUFFIXES`,
|
||||||
pub const ARCHIVE_SUFFIXES: [&str; 7] = [".zip", ".7z", ".rar", ".tar", ".gz", ".bz2", ".xz"];
|
/// plus `.tgz` — a Rust-only extension; the python version never extracted
|
||||||
|
/// those). `.tgz` is a gzip-compressed tar, so it maps onto the tarball path.
|
||||||
|
pub const ARCHIVE_SUFFIXES: [&str; 8] =
|
||||||
|
[".zip", ".7z", ".rar", ".tar", ".gz", ".bz2", ".xz", ".tgz"];
|
||||||
|
|
||||||
/// Stop extracting below this free space (python `MIN_FREE_BYTES`).
|
/// Stop extracting below this free space (python `MIN_FREE_BYTES`).
|
||||||
pub const MIN_FREE_BYTES: u64 = 20 * 1024 * 1024 * 1024;
|
pub const MIN_FREE_BYTES: u64 = 20 * 1024 * 1024 * 1024;
|
||||||
@@ -388,6 +391,9 @@ impl Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(".tar") => extract_tar_cmd(archive, &actual, None),
|
Some(".tar") => extract_tar_cmd(archive, &actual, None),
|
||||||
|
// .tgz is a gzip-compressed tar: same as .tar.gz, but the tar flag
|
||||||
|
// cannot be inferred from the name (Rust-only extension).
|
||||||
|
Some(".tgz") => extract_tar_cmd(archive, &actual, Some(".gz")),
|
||||||
_ => Err(format!("no extractor for {name}")),
|
_ => Err(format!("no extractor for {name}")),
|
||||||
};
|
};
|
||||||
match res {
|
match res {
|
||||||
@@ -957,6 +963,10 @@ mod tests {
|
|||||||
let targz = gzip_stored(&make_ustar("delta.txt", b"tarred payload"));
|
let targz = gzip_stored(&make_ustar("delta.txt", b"tarred payload"));
|
||||||
fs::write(s.join("logs.tar.gz"), targz).unwrap();
|
fs::write(s.join("logs.tar.gz"), targz).unwrap();
|
||||||
|
|
||||||
|
// a .tgz (gzip-compressed tar under the short suffix — Rust-only)
|
||||||
|
let tgz = gzip_stored(&make_ustar("epsilon.txt", b"tgz payload"));
|
||||||
|
fs::write(s.join("bundle.tgz"), tgz).unwrap();
|
||||||
|
|
||||||
// garbage zip -> must land in .broken
|
// garbage zip -> must land in .broken
|
||||||
fs::write(s.join("bad.zip"), b"this is not a zip archive at all").unwrap();
|
fs::write(s.join("bad.zip"), b"this is not a zip archive at all").unwrap();
|
||||||
|
|
||||||
@@ -1081,6 +1091,10 @@ mod tests {
|
|||||||
fs::read_to_string(o.join("src1/logs/delta.txt")).unwrap(),
|
fs::read_to_string(o.join("src1/logs/delta.txt")).unwrap(),
|
||||||
"tarred payload"
|
"tarred payload"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(o.join("src1/bundle/epsilon.txt")).unwrap(),
|
||||||
|
"tgz payload"
|
||||||
|
);
|
||||||
|
|
||||||
// no archives left in the extracted tree (outside .broken)
|
// no archives left in the extracted tree (outside .broken)
|
||||||
for p in walk_files_sorted(o) {
|
for p in walk_files_sorted(o) {
|
||||||
@@ -1164,6 +1178,7 @@ mod tests {
|
|||||||
"The Twin Lance",
|
"The Twin Lance",
|
||||||
),
|
),
|
||||||
("src1/logs.tar.gz", "src1/logs", "logs"),
|
("src1/logs.tar.gz", "src1/logs", "logs"),
|
||||||
|
("src1/bundle.tgz", "src1/bundle", "bundle"),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(a, d, u)| (a.to_string(), d.to_string(), u.to_string()))
|
.map(|(a, d, u)| (a.to_string(), d.to_string(), u.to_string()))
|
||||||
@@ -1255,6 +1270,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(display_unit("Pack.zip"), "Pack");
|
assert_eq!(display_unit("Pack.zip"), "Pack");
|
||||||
assert!(is_archive("c.7z.7z"));
|
assert!(is_archive("c.7z.7z"));
|
||||||
|
// .tgz: Rust-only short suffix for a gzip-compressed tar
|
||||||
|
assert_eq!(archive_suffixes("x.tgz"), vec![".tgz"]);
|
||||||
|
assert_eq!(display_unit("x.tgz"), "x");
|
||||||
|
assert!(is_archive("X.TGZ"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user