feat: add listing support for squashfs

This commit is contained in:
oxalica 2025-07-01 01:38:42 -04:00
parent 945ffde551
commit bcdff0f46b
11 changed files with 385 additions and 40 deletions

281
Cargo.lock generated
View File

@ -126,6 +126,24 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backhand"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c45726a83c67f85d931ef28d331cbc4108b179e06359ed84944313dfc2bb9ce1"
dependencies = [
"deku",
"flate2",
"lz4_flex",
"rayon",
"solana-nohash-hasher",
"thiserror 2.0.12",
"tracing",
"xxhash-rust",
"zstd",
"zstd-safe",
]
[[package]]
name = "base64ct"
version = "1.6.0"
@ -181,6 +199,18 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -284,7 +314,7 @@ dependencies = [
"byteorder",
"bytesize",
"libbzip3-sys",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -520,6 +550,66 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.98",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.98",
]
[[package]]
name = "deku"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476a022dcfbb013d1365734a42e05b6aca967ebe0d3bb38170086abd9ea3324"
dependencies = [
"bitvec",
"deku_derive",
"no_std_io2",
"rustversion",
]
[[package]]
name = "deku_derive"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb216d425bdf810c165a8ae1649523033e88b5f795480ccec63926295541b084"
dependencies = [
"darling",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "deranged"
version = "0.4.0"
@ -570,6 +660,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
@ -616,6 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"libz-rs-sys",
"libz-sys",
"miniz_oxide",
]
@ -648,6 +745,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures-core"
version = "0.3.31"
@ -728,9 +831,15 @@ dependencies = [
"libz-sys",
"num_cpus",
"snap",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "heck"
version = "0.5.0"
@ -770,6 +879,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "ignore"
version = "0.4.23"
@ -786,6 +901,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "indexmap"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "infer"
version = "0.16.0"
@ -937,6 +1062,15 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "libz-rs-sys"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
dependencies = [
"zlib-rs",
]
[[package]]
name = "libz-sys"
version = "1.1.21"
@ -1036,6 +1170,15 @@ dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "no_std_io2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c2b9acd47481ab557a89a5665891be79e43cce8a29ad77aa9419d7be5a7c06a"
dependencies = [
"memchr",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1095,6 +1238,7 @@ version = "0.6.1"
dependencies = [
"assert_cmd",
"atty",
"backhand",
"brotli",
"bstr",
"bytesize",
@ -1213,6 +1357,12 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkg-config"
version = "0.3.31"
@ -1286,6 +1436,15 @@ dependencies = [
"yansi",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
@ -1330,6 +1489,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@ -1595,6 +1760,12 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
[[package]]
name = "solana-nohash-hasher"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e"
[[package]]
name = "spin"
version = "0.9.8"
@ -1667,6 +1838,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.44"
@ -1716,7 +1893,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -1730,6 +1916,17 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "time"
version = "0.3.41"
@ -1760,6 +1957,54 @@ dependencies = [
"time-core",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "tracing-core"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
]
[[package]]
name = "twox-hash"
version = "1.6.3"
@ -2058,6 +2303,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
@ -2067,6 +2321,15 @@ dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xattr"
version = "1.4.0"
@ -2078,6 +2341,12 @@ dependencies = [
"rustix",
]
[[package]]
name = "xxhash-rust"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]]
name = "xz2"
version = "0.1.7"
@ -2152,6 +2421,12 @@ dependencies = [
"time",
]
[[package]]
name = "zlib-rs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
[[package]]
name = "zstd"
version = "0.13.3"

View File

@ -15,6 +15,8 @@ description = "A command-line utility for easily compressing and decompressing f
[dependencies]
atty = "0.2.14"
# FIXME: `xz` features cannot be enabled because it uses `lzma-sys` which cannot co-exist with our dependency `xz2`.
backhand = { version = "0.23.0", default-features = false, features = ["parallel", "gzip", "lz4", "zstd"] }
brotli = "7.0.0"
bstr = { version = "1.10.0", default-features = false, features = ["std"] }
bytesize = "1.3.0"

View File

@ -7,5 +7,6 @@ pub mod rar;
#[cfg(not(feature = "unrar"))]
pub mod rar_stub;
pub mod sevenz;
pub mod squashfs;
pub mod tar;
pub mod zip;

23
src/archive/squashfs.rs Normal file
View File

@ -0,0 +1,23 @@
use std::path::Path;
use backhand::{FilesystemReader, InnerNode};
use crate::list::FileInArchive;
pub fn list_archive<'a>(archive: FilesystemReader<'a>) -> impl Iterator<Item = crate::Result<FileInArchive>> + 'a {
archive.root.nodes.into_iter().filter_map(move |f| {
// The reported paths are absolute, and include the root directory `/`.
// To be consistent with outputs of other formats, we strip the prefix `/` and ignore the root directory.
if f.fullpath == Path::new("/") {
return None;
}
Some(Ok(FileInArchive {
is_dir: matches!(f.inner, InnerNode::Dir(_)),
path: f
.fullpath
.strip_prefix("/")
.expect("paths must be absolute")
.to_path_buf(),
}))
})
}

View File

@ -5,10 +5,9 @@ use std::{
use fs_err as fs;
use super::warn_user_about_loading_sevenz_in_memory;
use crate::{
archive,
commands::warn_user_about_loading_zip_in_memory,
commands::warn_user_about_loading_in_memory,
extension::{split_first_compression_format, CompressionFormat::*, Extension},
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue, FileVisibilityPolicy},
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
@ -96,7 +95,7 @@ pub fn compress_files(
let win_size = 22; // default to 2^22 = 4 MiB window size
Box::new(brotli::CompressorWriter::new(encoder, BUFFER_CAPACITY, level, win_size))
}
Tar | Zip | Rar | SevenZip => unreachable!(),
Tar | Zip | Rar | SevenZip | Squashfs => unreachable!(),
};
Ok(encoder)
};
@ -125,13 +124,14 @@ pub fn compress_files(
)?;
writer.flush()?;
}
Squashfs => todo!(),
Zip => {
if !formats.is_empty() {
// Locking necessary to guarantee that warning and question
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_zip_in_memory();
warn_user_about_loading_in_memory(".zip");
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
return Ok(false);
}
@ -163,7 +163,7 @@ pub fn compress_files(
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_sevenz_in_memory();
warn_user_about_loading_in_memory(".7z");
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
return Ok(false);
}

View File

@ -9,7 +9,7 @@ use fs_err as fs;
#[cfg(not(feature = "bzip3"))]
use crate::archive;
use crate::{
commands::{warn_user_about_loading_sevenz_in_memory, warn_user_about_loading_zip_in_memory},
commands::warn_user_about_loading_in_memory,
extension::{
split_first_compression_format,
CompressionFormat::{self, *},
@ -65,7 +65,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
{
let mut vec = vec![];
let reader: Box<dyn ReadSeek> = if input_is_stdin {
warn_user_about_loading_zip_in_memory();
warn_user_about_loading_in_memory(".zip");
io::copy(&mut io::stdin(), &mut vec)?;
Box::new(io::Cursor::new(vec))
} else {
@ -132,7 +132,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
Tar | Zip | Rar | SevenZip => decoder,
Tar | Zip | Rar | SevenZip | Squashfs => decoder,
};
Ok(decoder)
};
@ -174,13 +174,14 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
return Ok(());
}
}
Squashfs => todo!(),
Zip => {
if options.formats.len() > 1 {
// Locking necessary to guarantee that warning and question
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_zip_in_memory();
warn_user_about_loading_in_memory(".zip");
if !user_wants_to_continue(
options.input_file_path,
options.question_policy,
@ -252,7 +253,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_sevenz_in_memory();
warn_user_about_loading_in_memory(".7z");
if !user_wants_to_continue(
options.input_file_path,
options.question_policy,

View File

@ -7,7 +7,7 @@ use fs_err as fs;
use crate::{
archive,
commands::warn_user_about_loading_zip_in_memory,
commands::warn_user_about_loading_in_memory,
extension::CompressionFormat::{self, *},
list::{self, FileInArchive, ListOptions},
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue},
@ -38,6 +38,13 @@ pub fn list_archive_contents(
list::list_files(archive_path, files, list_options)?;
return Ok(());
}
if let &[Squashfs] = formats.as_slice() {
let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader);
let archive = backhand::FilesystemReader::from_reader(reader)?;
let files = crate::archive::squashfs::list_archive(archive);
list::list_files(archive_path, files, list_options)?;
return Ok(());
}
// Will be used in decoder chaining
let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader);
@ -61,7 +68,7 @@ pub fn list_archive_contents(
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
Tar | Zip | Rar | SevenZip => unreachable!("should be treated by caller"),
Tar | Zip | Rar | SevenZip | Squashfs => unreachable!("should be treated by caller"),
};
Ok(decoder)
};
@ -78,13 +85,15 @@ pub fn list_archive_contents(
let archive_format = misplaced_archive_format.unwrap_or(formats[0]);
let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match archive_format {
Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))),
Zip => {
Zip | Squashfs => {
let is_zip = matches!(archive_format, Zip);
if formats.len() > 1 {
// Locking necessary to guarantee that warning and question
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_zip_in_memory();
warn_user_about_loading_in_memory(if is_zip { ".zip" } else { ".sqfs" });
if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
return Ok(());
}
@ -92,9 +101,14 @@ pub fn list_archive_contents(
let mut vec = vec![];
io::copy(&mut reader, &mut vec)?;
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
Box::new(crate::archive::zip::list_archive(zip_archive, password))
let reader = io::Cursor::new(vec);
if is_zip {
let zip_archive = zip::ZipArchive::new(reader)?;
Box::new(crate::archive::zip::list_archive(zip_archive, password))
} else {
let archive = backhand::FilesystemReader::from_reader(reader)?;
Box::new(crate::archive::squashfs::list_archive(archive))
}
}
#[cfg(feature = "unrar")]
Rar => {
@ -116,7 +130,7 @@ pub fn list_archive_contents(
// messages stay adjacent
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_zip_in_memory();
warn_user_about_loading_in_memory(".7z");
if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
return Ok(());
}

View File

@ -25,24 +25,15 @@ use crate::{
CliArgs, QuestionPolicy,
};
/// Warn the user that (de)compressing this .zip archive might freeze their system.
fn warn_user_about_loading_zip_in_memory() {
const ZIP_IN_MEMORY_LIMITATION_WARNING: &str = "\n \
The format '.zip' is limited by design and cannot be (de)compressed with encoding streams.\n \
When chaining '.zip' with other formats, all (de)compression needs to be done in-memory\n \
Careful, you might run out of RAM if the archive is too large!";
eprintln!("{}[WARNING]{}: {ZIP_IN_MEMORY_LIMITATION_WARNING}", *ORANGE, *RESET);
}
/// Warn the user that (de)compressing this .7z archive might freeze their system.
fn warn_user_about_loading_sevenz_in_memory() {
const SEVENZ_IN_MEMORY_LIMITATION_WARNING: &str = "\n \
The format '.7z' is limited by design and cannot be (de)compressed with encoding streams.\n \
When chaining '.7z' with other formats, all (de)compression needs to be done in-memory\n \
Careful, you might run out of RAM if the archive is too large!";
eprintln!("{}[WARNING]{}: {SEVENZ_IN_MEMORY_LIMITATION_WARNING}", *ORANGE, *RESET);
/// Warn the user that (de)compressing this format might freeze their system.
fn warn_user_about_loading_in_memory(ext: &str) {
eprintln!(
"{}[WARNING]{}:\n \
The format '{ext}' is limited by design and cannot be (de)compressed with encoding streams.\n \
When chaining '{ext}' with other formats, all (de)compression needs to be done in-memory\n \
Careful, you might run out of RAM if the archive is too large!",
*ORANGE, *RESET
);
}
/// This function checks what command needs to be run and performs A LOT of ahead-of-time checks

View File

@ -47,6 +47,10 @@ pub enum Error {
UnsupportedFormat { reason: String },
/// Invalid password provided
InvalidPassword { reason: String },
/// From backhand::BackhandError
InvalidSquashfs { reason: String },
/// From backhand::BackhandError
UnsupportedSquashfs { reason: String },
}
/// Alias to std's Result with ouch's Error
@ -158,8 +162,10 @@ impl From<Error> for FinalError {
Error::Lz4Error { reason } => FinalError::with_title(reason),
Error::AlreadyExists { error_title } => FinalError::with_title(error_title).detail("File already exists"),
Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(reason),
Error::InvalidSquashfs { reason } => FinalError::with_title("Invalid squashfs").detail(reason),
Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(reason),
Error::UnsupportedSquashfs { reason } => FinalError::with_title("Unsupported squashfs").detail(reason),
Error::InvalidFormatFlag { reason, text } => {
FinalError::with_title(format!("Failed to parse `--format {}`", os_str_to_str(&text)))
.detail(reason)
@ -227,6 +233,23 @@ impl From<zip::result::ZipError> for Error {
}
}
impl From<backhand::BackhandError> for Error {
fn from(err: backhand::BackhandError) -> Self {
use backhand::BackhandError;
match err {
BackhandError::StdIo(io_err) => Self::from(io_err),
err @ (BackhandError::UnsupportedCompression(_) | BackhandError::UnsupportedInode(_)) => {
Self::UnsupportedSquashfs {
reason: err.to_string(),
}
}
err => Self::InvalidSquashfs {
reason: err.to_string(),
},
}
}
}
#[cfg(feature = "unrar")]
impl From<unrar::error::UnrarError> for Error {
fn from(err: unrar::error::UnrarError) -> Self {

View File

@ -102,6 +102,14 @@ pub enum CompressionFormat {
SevenZip,
/// .br
Brotli,
/// .squashfs, .sqfs
//
// Note: There is not canonical extension for squashfs, we pick the two semi-official ones:
// - `.squashfs`: The most popular one from a quick search on GitHub. Also used by some distros.
// https://github.com/NixOS/nixpkgs/blob/6576d979e9a64d870b1f298a5c598116891a6c25/nixos/modules/installer/cd-dvd/iso-image.nix#L797
// - `.sqfs`: Mentioned in `man mksquashfs` by squashfs-tools, the reference implementation.
// https://github.com/plougher/squashfs-tools/blob/9f9bbd79016ff04967800f4301c7a9d0024c0c91/Documentation/manpages/mksquashfs.1#L494
Squashfs,
}
impl CompressionFormat {
@ -109,7 +117,7 @@ impl CompressionFormat {
pub fn archive_format(&self) -> bool {
// Keep this match like that without a wildcard `_` so we don't forget to update it
match self {
Tar | Zip | Rar | SevenZip => true,
Tar | Zip | Rar | SevenZip | Squashfs => true,
Gzip => false,
Bzip => false,
Bzip3 => false,
@ -144,6 +152,7 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
b"rar" => &[Rar],
b"7z" => &[SevenZip],
b"br" => &[Brotli],
b"sqfs" | b"squashfs" => &[Squashfs],
_ => return None,
},
ext.to_str_lossy(),

View File

@ -158,6 +158,10 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
fn is_sevenz(buf: &[u8]) -> bool {
buf.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])
}
fn is_squashfs(buf: &[u8]) -> bool {
// Ref: https://dr-emann.github.io/squashfs/squashfs.html#_the_superblock
buf.starts_with(b"hsqs")
}
let buf = {
let mut buf = [0; 270];
@ -195,6 +199,8 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
Some(Extension::new(&[Rar], "rar"))
} else if is_sevenz(&buf) {
Some(Extension::new(&[SevenZip], "7z"))
} else if is_squashfs(&buf) {
Some(Extension::new(&[Squashfs], "sqfs"))
} else {
None
}