mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-19 16:10:53 +00:00
Merge bb45b2f4a29c5580bb48ca42792af1d532062e10 into d0494504a1df4e13ecd4eeae0495066c81060cad
This commit is contained in:
commit
e8e6ba4b9b
281
Cargo.lock
generated
281
Cargo.lock
generated
@ -126,6 +126,24 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
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]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -181,6 +199,18 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
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]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@ -284,7 +314,7 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
"libbzip3-sys",
|
"libbzip3-sys",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -520,6 +550,66 @@ dependencies = [
|
|||||||
"typenum",
|
"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]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -570,6 +660,12 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.10"
|
||||||
@ -616,6 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
"libz-rs-sys",
|
||||||
"libz-sys",
|
"libz-sys",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
@ -648,6 +745,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "funty"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -728,9 +831,15 @@ dependencies = [
|
|||||||
"libz-sys",
|
"libz-sys",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"snap",
|
"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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -770,6 +879,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.23"
|
version = "0.4.23"
|
||||||
@ -786,6 +901,16 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "infer"
|
name = "infer"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
@ -937,6 +1062,15 @@ dependencies = [
|
|||||||
"redox_syscall",
|
"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]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.21"
|
version = "1.1.21"
|
||||||
@ -1036,6 +1170,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.15",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@ -1095,6 +1238,7 @@ version = "0.6.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"atty",
|
"atty",
|
||||||
|
"backhand",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bstr",
|
"bstr",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
@ -1213,6 +1357,12 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"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]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -1286,6 +1436,15 @@ dependencies = [
|
|||||||
"yansi",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.93"
|
||||||
@ -1330,6 +1489,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radium"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@ -1595,6 +1760,12 @@ version = "1.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
|
checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-nohash-hasher"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@ -1667,6 +1838,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar"
|
name = "tar"
|
||||||
version = "0.4.44"
|
version = "0.4.44"
|
||||||
@ -1716,7 +1893,16 @@ version = "1.0.69"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@ -1730,6 +1916,17 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"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]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.41"
|
version = "0.3.41"
|
||||||
@ -1760,6 +1957,54 @@ dependencies = [
|
|||||||
"time-core",
|
"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]]
|
[[package]]
|
||||||
name = "twox-hash"
|
name = "twox-hash"
|
||||||
version = "1.6.3"
|
version = "1.6.3"
|
||||||
@ -2058,6 +2303,15 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.33.0"
|
version = "0.33.0"
|
||||||
@ -2067,6 +2321,15 @@ dependencies = [
|
|||||||
"bitflags 2.8.0",
|
"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]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -2078,6 +2341,12 @@ dependencies = [
|
|||||||
"rustix",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xxhash-rust"
|
||||||
|
version = "0.8.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xz2"
|
name = "xz2"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -2152,6 +2421,12 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
|
@ -15,6 +15,8 @@ description = "A command-line utility for easily compressing and decompressing f
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atty = "0.2.14"
|
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"
|
brotli = "7.0.0"
|
||||||
bstr = { version = "1.10.0", default-features = false, features = ["std"] }
|
bstr = { version = "1.10.0", default-features = false, features = ["std"] }
|
||||||
bytesize = "1.3.0"
|
bytesize = "1.3.0"
|
||||||
|
@ -111,9 +111,9 @@ Output:
|
|||||||
|
|
||||||
# Supported formats
|
# Supported formats
|
||||||
|
|
||||||
| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` |
|
| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` | `.sqfs`, `.squashfs` |
|
||||||
|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ |
|
| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ | ✓¹ |
|
||||||
|
|
||||||
✓: Supports compression and decompression.
|
✓: Supports compression and decompression.
|
||||||
|
|
||||||
|
@ -7,5 +7,6 @@ pub mod rar;
|
|||||||
#[cfg(not(feature = "unrar"))]
|
#[cfg(not(feature = "unrar"))]
|
||||||
pub mod rar_stub;
|
pub mod rar_stub;
|
||||||
pub mod sevenz;
|
pub mod sevenz;
|
||||||
|
pub mod squashfs;
|
||||||
pub mod tar;
|
pub mod tar;
|
||||||
pub mod zip;
|
pub mod zip;
|
||||||
|
343
src/archive/squashfs.rs
Normal file
343
src/archive/squashfs.rs
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
io::{self, BufWriter, Seek, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use backhand::{
|
||||||
|
compression::Compressor, FilesystemCompressor, FilesystemReader, FilesystemWriter, InnerNode, NodeHeader,
|
||||||
|
SquashfsFileReader,
|
||||||
|
};
|
||||||
|
use filetime_creation::{set_file_handle_times, FileTime};
|
||||||
|
use same_file::Handle;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::FinalError,
|
||||||
|
list::FileInArchive,
|
||||||
|
utils::{
|
||||||
|
logger::{info, warning},
|
||||||
|
Bytes, FileVisibilityPolicy,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpack_archive(archive: FilesystemReader<'_>, output_folder: &Path, quiet: bool) -> crate::Result<usize> {
|
||||||
|
let mut unpacked_files = 0usize;
|
||||||
|
|
||||||
|
for f in archive.files() {
|
||||||
|
// `output_folder` should already be created.
|
||||||
|
if f.fullpath == Path::new("/") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relative_path = f.fullpath.strip_prefix("/").expect("paths must be absolute");
|
||||||
|
let file_path = output_folder.join(relative_path);
|
||||||
|
|
||||||
|
let mtime = FileTime::from_unix_time(f.header.mtime.into(), 0);
|
||||||
|
|
||||||
|
let warn_ignored = |inode_type: &str| {
|
||||||
|
warning(format!("ignored {inode_type} in archive {relative_path:?}"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match &f.inner {
|
||||||
|
InnerNode::Dir(_) => {
|
||||||
|
if !quiet {
|
||||||
|
info(format!("extracting directory {file_path:?}"));
|
||||||
|
}
|
||||||
|
fs::create_dir(&file_path)?;
|
||||||
|
// Directory mtime is not recovered. It will be overwritten by
|
||||||
|
// the creation of inner files. We would need a second pass to do so.
|
||||||
|
}
|
||||||
|
InnerNode::File(file) => {
|
||||||
|
if !quiet {
|
||||||
|
let file_size = Bytes::new(match file {
|
||||||
|
SquashfsFileReader::Basic(f) => f.file_size.into(),
|
||||||
|
SquashfsFileReader::Extended(f) => f.file_size,
|
||||||
|
});
|
||||||
|
info(format!("extracting file ({file_size}) {file_path:?}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = archive.file(file).reader();
|
||||||
|
let output_file = fs::File::create(&file_path)?;
|
||||||
|
let mut output_file = BufWriter::new(output_file);
|
||||||
|
io::copy(&mut reader, &mut output_file)?;
|
||||||
|
output_file.flush()?;
|
||||||
|
set_file_handle_times(output_file.get_ref(), None, Some(mtime), None)?;
|
||||||
|
}
|
||||||
|
InnerNode::Symlink(symlink) => {
|
||||||
|
if !quiet {
|
||||||
|
info(format!("extracting symlink {file_path:?}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = &symlink.link;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
std::os::unix::fs::symlink(target, &file_path)?;
|
||||||
|
filetime_creation::set_symlink_file_times(&file_path, mtime, mtime, mtime)?;
|
||||||
|
// Note: Symlink permissions are ignored on *NIX anyway. No need to set them.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&target, &file_path)?;
|
||||||
|
|
||||||
|
// Symlink mtime is specially handled above. Skip the normal handler.
|
||||||
|
unpacked_files += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Named pipes and sockets *CAN* be created by unprivileged users.
|
||||||
|
// Should we extract them by default?
|
||||||
|
InnerNode::NamedPipe => {
|
||||||
|
warn_ignored("named pipe");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InnerNode::Socket => {
|
||||||
|
warn_ignored("socket");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not possible without root permission.
|
||||||
|
InnerNode::CharacterDevice(_) => {
|
||||||
|
warn_ignored("character device");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InnerNode::BlockDevice(_) => {
|
||||||
|
warn_ignored("block device");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
fs::set_permissions(&file_path, fs::Permissions::from_mode(f.header.permissions.into()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
unpacked_files += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(unpacked_files)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-assignments work bettwe with `cfg` blocks.
|
||||||
|
#[allow(clippy::field_reassign_with_default)]
|
||||||
|
pub fn build_archive_from_paths<W>(
|
||||||
|
input_filenames: &[PathBuf],
|
||||||
|
output_path: &Path,
|
||||||
|
mut writer: W,
|
||||||
|
file_visibility_policy: FileVisibilityPolicy,
|
||||||
|
quiet: bool,
|
||||||
|
follow_symlinks: bool,
|
||||||
|
) -> crate::Result<W>
|
||||||
|
where
|
||||||
|
W: Write + Seek,
|
||||||
|
{
|
||||||
|
let root_dir = match input_filenames {
|
||||||
|
[path] if path.is_dir() => path,
|
||||||
|
_ => {
|
||||||
|
let error = FinalError::with_title("Cannot build squashfs")
|
||||||
|
.detail("Squashfs requires a single directory input for root directory")
|
||||||
|
.detail(if input_filenames.len() != 1 {
|
||||||
|
"Multiple paths are provided".into()
|
||||||
|
} else {
|
||||||
|
format!("Not a directory: {:?}", input_filenames[0])
|
||||||
|
});
|
||||||
|
return Err(error.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_handle = Handle::from_path(output_path);
|
||||||
|
|
||||||
|
let mut fs_writer = FilesystemWriter::default();
|
||||||
|
// Set the default compression to Gzip with default level, matching mksquashfs's default.
|
||||||
|
// The default choice of `backhand` is Xz which is not enabled by us.
|
||||||
|
// TODO: We do not support customization argument for archive formats.
|
||||||
|
fs_writer.set_compressor(FilesystemCompressor::new(Compressor::Gzip, None).expect("gzip is supported"));
|
||||||
|
|
||||||
|
// cd *into* the source directory, using it as the archive root.
|
||||||
|
let previous_cwd = env::current_dir()?;
|
||||||
|
env::set_current_dir(root_dir)?;
|
||||||
|
|
||||||
|
for entry in file_visibility_policy.build_walker(".") {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if let Ok(handle) = &output_handle {
|
||||||
|
if matches!(Handle::from_path(path), Ok(x) if &x == handle) {
|
||||||
|
warning(format!(
|
||||||
|
"Cannot compress `{}` into itself, skipping",
|
||||||
|
output_path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
// `fs_writer.push_*` only maintains metadata. We do not want to give
|
||||||
|
// users a false information that we are compressing during the
|
||||||
|
// traversal. So this is not "compressing".
|
||||||
|
// File reading, compression and writing are done in
|
||||||
|
// `fs_writer.write` below, after the hierarchy tree gets finalized.
|
||||||
|
info(format!("Found {path:?}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = entry.metadata()?;
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
|
||||||
|
let mut header = NodeHeader::default();
|
||||||
|
header.mtime = match metadata.modified() {
|
||||||
|
// Not available.
|
||||||
|
Err(_) => 0,
|
||||||
|
Ok(mtime) => {
|
||||||
|
let mtime = mtime
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.ok()
|
||||||
|
.and_then(|dur| u32::try_from(dur.as_secs()).ok());
|
||||||
|
if mtime.is_none() {
|
||||||
|
warning(format!(
|
||||||
|
"Modification time of {path:?} exceeds the representable range (1970-01-01 ~ 2106-02-07) \
|
||||||
|
of squashfs. Recorded as 1970-01-01."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
mtime.unwrap_or(0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
header.permissions = 0o777;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
|
|
||||||
|
// Only permission bits, not file type bits.
|
||||||
|
header.permissions = metadata.permissions().mode() as u16 & !(libc::S_IFMT as u16);
|
||||||
|
header.uid = metadata.uid();
|
||||||
|
header.gid = metadata.gid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root directory is special cased.
|
||||||
|
if path == Path::new(".") {
|
||||||
|
fs_writer.set_root_mode(header.permissions);
|
||||||
|
fs_writer.set_root_uid(header.uid);
|
||||||
|
fs_writer.set_root_gid(header.gid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if file_type.is_dir() {
|
||||||
|
fs_writer.push_dir(path, header)?;
|
||||||
|
} else if !follow_symlinks && file_type.is_symlink() {
|
||||||
|
let target = fs::read_link(path)?;
|
||||||
|
fs_writer.push_symlink(target, path, header)?;
|
||||||
|
} else if maybe_push_unix_special_inode(&mut fs_writer, path, &metadata, header)? {
|
||||||
|
// Already handled.
|
||||||
|
} else {
|
||||||
|
// Fallback case: read as a regular file.
|
||||||
|
// See comments of `LazyFile` for why not `File::open` here.
|
||||||
|
let reader = LazyFile::Path(path.to_path_buf());
|
||||||
|
fs_writer.push_file(reader, path, header)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
info("Compressing data".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize the superblock and write data. This should be done before
|
||||||
|
// resetting current directory, because `LazyFile`s store relative paths.
|
||||||
|
fs_writer.write(&mut writer)?;
|
||||||
|
|
||||||
|
env::set_current_dir(previous_cwd)?;
|
||||||
|
|
||||||
|
Ok(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn maybe_push_unix_special_inode(
|
||||||
|
_writer: &mut FilesystemWriter,
|
||||||
|
_path: &Path,
|
||||||
|
_metadata: &fs::Metadata,
|
||||||
|
_header: NodeHeader,
|
||||||
|
) -> io::Result<bool> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn maybe_push_unix_special_inode(
|
||||||
|
writer: &mut FilesystemWriter,
|
||||||
|
path: &Path,
|
||||||
|
metadata: &fs::Metadata,
|
||||||
|
header: NodeHeader,
|
||||||
|
) -> io::Result<bool> {
|
||||||
|
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||||
|
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
if file_type.is_fifo() {
|
||||||
|
writer.push_fifo(path, header)?;
|
||||||
|
} else if file_type.is_socket() {
|
||||||
|
writer.push_socket(path, header)?;
|
||||||
|
} else if file_type.is_block_device() {
|
||||||
|
let dev = metadata.rdev() as u32;
|
||||||
|
writer.push_block_device(dev, path, header)?;
|
||||||
|
} else if file_type.is_char_device() {
|
||||||
|
let dev = metadata.rdev() as u32;
|
||||||
|
writer.push_char_device(dev, path, header)?;
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delay file opening until the first read and close it as soon as the EOF is encountered.
|
||||||
|
///
|
||||||
|
/// Due to design of `backhand`, we need to store all `impl Read` into the
|
||||||
|
/// builder during traversal and write out the squashfs later. But we cannot
|
||||||
|
/// open and store all file handles during traversal or it will exhaust file
|
||||||
|
/// descriptors on *NIX if there are thousands of files (a pretty low limit!).
|
||||||
|
///
|
||||||
|
/// Upstream discussion: https://github.com/wcampbell0x2a/backhand/discussions/614
|
||||||
|
enum LazyFile {
|
||||||
|
Path(PathBuf),
|
||||||
|
Opened(fs::File),
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for LazyFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self {
|
||||||
|
LazyFile::Path(path) => {
|
||||||
|
let file = fs::File::open(path)?;
|
||||||
|
*self = Self::Opened(file);
|
||||||
|
self.read(buf)
|
||||||
|
}
|
||||||
|
LazyFile::Opened(file) => {
|
||||||
|
let cnt = file.read(buf)?;
|
||||||
|
if !buf.is_empty() && cnt == 0 {
|
||||||
|
*self = Self::Closed;
|
||||||
|
}
|
||||||
|
Ok(cnt)
|
||||||
|
}
|
||||||
|
LazyFile::Closed => Ok(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,9 @@ use std::{
|
|||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
|
||||||
use super::warn_user_about_loading_sevenz_in_memory;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archive,
|
archive,
|
||||||
commands::warn_user_about_loading_zip_in_memory,
|
commands::warn_user_about_loading_in_memory,
|
||||||
extension::{split_first_compression_format, CompressionFormat::*, Extension},
|
extension::{split_first_compression_format, CompressionFormat::*, Extension},
|
||||||
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue, FileVisibilityPolicy},
|
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue, FileVisibilityPolicy},
|
||||||
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
||||||
@ -96,7 +95,7 @@ pub fn compress_files(
|
|||||||
let win_size = 22; // default to 2^22 = 4 MiB window size
|
let win_size = 22; // default to 2^22 = 4 MiB window size
|
||||||
Box::new(brotli::CompressorWriter::new(encoder, BUFFER_CAPACITY, level, win_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)
|
Ok(encoder)
|
||||||
};
|
};
|
||||||
@ -125,28 +124,41 @@ pub fn compress_files(
|
|||||||
)?;
|
)?;
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
}
|
}
|
||||||
Zip => {
|
Zip | Squashfs => {
|
||||||
|
let is_zip = matches!(first_format, Zip);
|
||||||
|
|
||||||
if !formats.is_empty() {
|
if !formats.is_empty() {
|
||||||
// Locking necessary to guarantee that warning and question
|
// Locking necessary to guarantee that warning and question
|
||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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(output_path, question_policy, QuestionAction::Compression)? {
|
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vec_buffer = Cursor::new(vec![]);
|
let mut vec_buffer = Cursor::new(vec![]);
|
||||||
|
if is_zip {
|
||||||
|
archive::zip::build_archive_from_paths(
|
||||||
|
&files,
|
||||||
|
output_path,
|
||||||
|
&mut vec_buffer,
|
||||||
|
file_visibility_policy,
|
||||||
|
quiet,
|
||||||
|
follow_symlinks,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
archive::squashfs::build_archive_from_paths(
|
||||||
|
&files,
|
||||||
|
output_path,
|
||||||
|
&mut vec_buffer,
|
||||||
|
file_visibility_policy,
|
||||||
|
quiet,
|
||||||
|
follow_symlinks,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
archive::zip::build_archive_from_paths(
|
|
||||||
&files,
|
|
||||||
output_path,
|
|
||||||
&mut vec_buffer,
|
|
||||||
file_visibility_policy,
|
|
||||||
quiet,
|
|
||||||
follow_symlinks,
|
|
||||||
)?;
|
|
||||||
vec_buffer.rewind()?;
|
vec_buffer.rewind()?;
|
||||||
io::copy(&mut vec_buffer, &mut writer)?;
|
io::copy(&mut vec_buffer, &mut writer)?;
|
||||||
}
|
}
|
||||||
@ -163,7 +175,7 @@ pub fn compress_files(
|
|||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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)? {
|
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,13 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use backhand::BufReadSeek;
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
|
||||||
#[cfg(not(feature = "bzip3"))]
|
#[cfg(not(feature = "bzip3"))]
|
||||||
use crate::archive;
|
use crate::archive;
|
||||||
use crate::{
|
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::{
|
extension::{
|
||||||
split_first_compression_format,
|
split_first_compression_format,
|
||||||
CompressionFormat::{self, *},
|
CompressionFormat::{self, *},
|
||||||
@ -25,9 +26,6 @@ use crate::{
|
|||||||
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
trait ReadSeek: Read + io::Seek {}
|
|
||||||
impl<T: Read + io::Seek> ReadSeek for T {}
|
|
||||||
|
|
||||||
pub struct DecompressOptions<'a> {
|
pub struct DecompressOptions<'a> {
|
||||||
pub input_file_path: &'a Path,
|
pub input_file_path: &'a Path,
|
||||||
pub formats: Vec<Extension>,
|
pub formats: Vec<Extension>,
|
||||||
@ -51,29 +49,40 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
assert!(options.output_dir.exists());
|
assert!(options.output_dir.exists());
|
||||||
let input_is_stdin = is_path_stdin(options.input_file_path);
|
let input_is_stdin = is_path_stdin(options.input_file_path);
|
||||||
|
|
||||||
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
// Zip and squashfs archives are special, because they require io::Seek, so it requires it's logic separated
|
||||||
// from decoder chaining.
|
// from decoder chaining.
|
||||||
//
|
//
|
||||||
// This is the only case where we can read and unpack it directly, without having to do
|
// This is the only case where we can read and unpack it directly, without having to do
|
||||||
// in-memory decompression/copying first.
|
// in-memory decompression/copying first.
|
||||||
//
|
//
|
||||||
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
|
// Any other decompression done can take up the whole RAM and freeze ouch.
|
||||||
if let [Extension {
|
if let [Extension {
|
||||||
compression_formats: [Zip],
|
compression_formats: [archive_format @ (Zip | Squashfs)],
|
||||||
..
|
..
|
||||||
}] = options.formats.as_slice()
|
}] = options.formats.as_slice()
|
||||||
{
|
{
|
||||||
|
let is_zip = matches!(archive_format, Zip);
|
||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
let reader: Box<dyn ReadSeek> = if input_is_stdin {
|
let reader: Box<dyn BufReadSeek> = if input_is_stdin {
|
||||||
warn_user_about_loading_zip_in_memory();
|
warn_user_about_loading_in_memory(if is_zip { ".zip" } else { ".sqfs" });
|
||||||
io::copy(&mut io::stdin(), &mut vec)?;
|
io::copy(&mut io::stdin(), &mut vec)?;
|
||||||
Box::new(io::Cursor::new(vec))
|
Box::new(io::Cursor::new(vec))
|
||||||
} else {
|
} else {
|
||||||
Box::new(fs::File::open(options.input_file_path)?)
|
let file = fs::File::open(options.input_file_path)?;
|
||||||
|
let file = BufReader::new(file);
|
||||||
|
Box::new(file)
|
||||||
};
|
};
|
||||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
|
||||||
let files_unpacked = if let ControlFlow::Continue(files) = execute_decompression(
|
let files_unpacked = if let ControlFlow::Continue(files) = execute_decompression(
|
||||||
|output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet),
|
|output_dir| {
|
||||||
|
if is_zip {
|
||||||
|
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||||
|
crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet)
|
||||||
|
} else {
|
||||||
|
let archive = backhand::FilesystemReader::from_reader(reader)?;
|
||||||
|
crate::archive::squashfs::unpack_archive(archive, output_dir, options.quiet)
|
||||||
|
}
|
||||||
|
},
|
||||||
options.output_dir,
|
options.output_dir,
|
||||||
&options.output_file_path,
|
&options.output_file_path,
|
||||||
options.question_policy,
|
options.question_policy,
|
||||||
@ -132,7 +141,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
||||||
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
||||||
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
||||||
Tar | Zip | Rar | SevenZip => decoder,
|
Tar | Zip | Rar | SevenZip | Squashfs => decoder,
|
||||||
};
|
};
|
||||||
Ok(decoder)
|
Ok(decoder)
|
||||||
};
|
};
|
||||||
@ -174,13 +183,15 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Zip => {
|
Zip | Squashfs => {
|
||||||
|
let is_zip = matches!(first_extension, Zip);
|
||||||
|
|
||||||
if options.formats.len() > 1 {
|
if options.formats.len() > 1 {
|
||||||
// Locking necessary to guarantee that warning and question
|
// Locking necessary to guarantee that warning and question
|
||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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(
|
if !user_wants_to_continue(
|
||||||
options.input_file_path,
|
options.input_file_path,
|
||||||
options.question_policy,
|
options.question_policy,
|
||||||
@ -192,11 +203,17 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
io::copy(&mut reader, &mut vec)?;
|
io::copy(&mut reader, &mut vec)?;
|
||||||
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
let reader = io::Cursor::new(vec);
|
||||||
|
|
||||||
if let ControlFlow::Continue(files) = execute_decompression(
|
if let ControlFlow::Continue(files) = execute_decompression(
|
||||||
|output_dir| {
|
move |output_dir| {
|
||||||
crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet)
|
if is_zip {
|
||||||
|
let archive = zip::ZipArchive::new(reader)?;
|
||||||
|
crate::archive::zip::unpack_archive(archive, output_dir, options.password, options.quiet)
|
||||||
|
} else {
|
||||||
|
let archive = backhand::FilesystemReader::from_reader(reader)?;
|
||||||
|
crate::archive::squashfs::unpack_archive(archive, output_dir, options.quiet)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
options.output_dir,
|
options.output_dir,
|
||||||
&options.output_file_path,
|
&options.output_file_path,
|
||||||
@ -252,7 +269,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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(
|
if !user_wants_to_continue(
|
||||||
options.input_file_path,
|
options.input_file_path,
|
||||||
options.question_policy,
|
options.question_policy,
|
||||||
|
@ -7,7 +7,7 @@ use fs_err as fs;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archive,
|
archive,
|
||||||
commands::warn_user_about_loading_zip_in_memory,
|
commands::warn_user_about_loading_in_memory,
|
||||||
extension::CompressionFormat::{self, *},
|
extension::CompressionFormat::{self, *},
|
||||||
list::{self, FileInArchive, ListOptions},
|
list::{self, FileInArchive, ListOptions},
|
||||||
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue},
|
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue},
|
||||||
@ -25,7 +25,7 @@ pub fn list_archive_contents(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let reader = fs::File::open(archive_path)?;
|
let reader = fs::File::open(archive_path)?;
|
||||||
|
|
||||||
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
// Zip and squashfs archives are special, because they require io::Seek, so it requires it's logic separated
|
||||||
// from decoder chaining.
|
// from decoder chaining.
|
||||||
//
|
//
|
||||||
// This is the only case where we can read and unpack it directly, without having to do
|
// This is the only case where we can read and unpack it directly, without having to do
|
||||||
@ -38,6 +38,13 @@ pub fn list_archive_contents(
|
|||||||
list::list_files(archive_path, files, list_options)?;
|
list::list_files(archive_path, files, list_options)?;
|
||||||
return Ok(());
|
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
|
// Will be used in decoder chaining
|
||||||
let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader);
|
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)),
|
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
||||||
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
||||||
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
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)
|
Ok(decoder)
|
||||||
};
|
};
|
||||||
@ -78,13 +85,15 @@ pub fn list_archive_contents(
|
|||||||
let archive_format = misplaced_archive_format.unwrap_or(formats[0]);
|
let archive_format = misplaced_archive_format.unwrap_or(formats[0]);
|
||||||
let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match archive_format {
|
let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match archive_format {
|
||||||
Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))),
|
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 {
|
if formats.len() > 1 {
|
||||||
// Locking necessary to guarantee that warning and question
|
// Locking necessary to guarantee that warning and question
|
||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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)? {
|
if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -92,9 +101,14 @@ pub fn list_archive_contents(
|
|||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
io::copy(&mut reader, &mut vec)?;
|
io::copy(&mut reader, &mut vec)?;
|
||||||
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
let reader = io::Cursor::new(vec);
|
||||||
|
if is_zip {
|
||||||
Box::new(crate::archive::zip::list_archive(zip_archive, password))
|
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")]
|
#[cfg(feature = "unrar")]
|
||||||
Rar => {
|
Rar => {
|
||||||
@ -116,7 +130,7 @@ pub fn list_archive_contents(
|
|||||||
// messages stay adjacent
|
// messages stay adjacent
|
||||||
let _locks = lock_and_flush_output_stdio();
|
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)? {
|
if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -25,24 +25,15 @@ use crate::{
|
|||||||
CliArgs, QuestionPolicy,
|
CliArgs, QuestionPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Warn the user that (de)compressing this .zip archive might freeze their system.
|
/// Warn the user that (de)compressing this format might freeze their system.
|
||||||
fn warn_user_about_loading_zip_in_memory() {
|
fn warn_user_about_loading_in_memory(ext: &str) {
|
||||||
const ZIP_IN_MEMORY_LIMITATION_WARNING: &str = "\n \
|
eprintln!(
|
||||||
The format '.zip' is limited by design and cannot be (de)compressed with encoding streams.\n \
|
"{}[WARNING]{}:\n \
|
||||||
When chaining '.zip' with other formats, all (de)compression needs to be done in-memory\n \
|
The format '{ext}' is limited by design and cannot be (de)compressed with encoding streams.\n \
|
||||||
Careful, you might run out of RAM if the archive is too large!";
|
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!",
|
||||||
eprintln!("{}[WARNING]{}: {ZIP_IN_MEMORY_LIMITATION_WARNING}", *ORANGE, *RESET);
|
*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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function checks what command needs to be run and performs A LOT of ahead-of-time checks
|
/// This function checks what command needs to be run and performs A LOT of ahead-of-time checks
|
||||||
|
23
src/error.rs
23
src/error.rs
@ -47,6 +47,10 @@ pub enum Error {
|
|||||||
UnsupportedFormat { reason: String },
|
UnsupportedFormat { reason: String },
|
||||||
/// Invalid password provided
|
/// Invalid password provided
|
||||||
InvalidPassword { reason: String },
|
InvalidPassword { reason: String },
|
||||||
|
/// From backhand::BackhandError
|
||||||
|
InvalidSquashfs { reason: String },
|
||||||
|
/// From backhand::BackhandError
|
||||||
|
UnsupportedSquashfs { reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias to std's Result with ouch's Error
|
/// 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::Lz4Error { reason } => FinalError::with_title(reason),
|
||||||
Error::AlreadyExists { error_title } => FinalError::with_title(error_title).detail("File already exists"),
|
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::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::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
|
||||||
Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(reason),
|
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 } => {
|
Error::InvalidFormatFlag { reason, text } => {
|
||||||
FinalError::with_title(format!("Failed to parse `--format {}`", os_str_to_str(&text)))
|
FinalError::with_title(format!("Failed to parse `--format {}`", os_str_to_str(&text)))
|
||||||
.detail(reason)
|
.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")]
|
#[cfg(feature = "unrar")]
|
||||||
impl From<unrar::error::UnrarError> for Error {
|
impl From<unrar::error::UnrarError> for Error {
|
||||||
fn from(err: unrar::error::UnrarError) -> Self {
|
fn from(err: unrar::error::UnrarError) -> Self {
|
||||||
|
@ -25,16 +25,18 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[
|
|||||||
"rar",
|
"rar",
|
||||||
"7z",
|
"7z",
|
||||||
"br",
|
"br",
|
||||||
|
// TODO(review): Which to use as "official" extension? squashfs or sqfs?
|
||||||
|
"sqfs",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"];
|
pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst", "squashfs"];
|
||||||
|
|
||||||
#[cfg(not(feature = "unrar"))]
|
#[cfg(not(feature = "unrar"))]
|
||||||
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z";
|
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs";
|
||||||
#[cfg(feature = "unrar")]
|
#[cfg(feature = "unrar")]
|
||||||
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z";
|
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs";
|
||||||
|
|
||||||
pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst";
|
pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs";
|
||||||
|
|
||||||
/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
|
/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -102,6 +104,14 @@ pub enum CompressionFormat {
|
|||||||
SevenZip,
|
SevenZip,
|
||||||
/// .br
|
/// .br
|
||||||
Brotli,
|
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 {
|
impl CompressionFormat {
|
||||||
@ -109,7 +119,7 @@ impl CompressionFormat {
|
|||||||
pub fn archive_format(&self) -> bool {
|
pub fn archive_format(&self) -> bool {
|
||||||
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
||||||
match self {
|
match self {
|
||||||
Tar | Zip | Rar | SevenZip => true,
|
Tar | Zip | Rar | SevenZip | Squashfs => true,
|
||||||
Gzip => false,
|
Gzip => false,
|
||||||
Bzip => false,
|
Bzip => false,
|
||||||
Bzip3 => false,
|
Bzip3 => false,
|
||||||
@ -144,6 +154,7 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
|
|||||||
b"rar" => &[Rar],
|
b"rar" => &[Rar],
|
||||||
b"7z" => &[SevenZip],
|
b"7z" => &[SevenZip],
|
||||||
b"br" => &[Brotli],
|
b"br" => &[Brotli],
|
||||||
|
b"sqfs" | b"squashfs" => &[Squashfs],
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
ext.to_str_lossy(),
|
ext.to_str_lossy(),
|
||||||
|
@ -158,6 +158,10 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
|
|||||||
fn is_sevenz(buf: &[u8]) -> bool {
|
fn is_sevenz(buf: &[u8]) -> bool {
|
||||||
buf.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])
|
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 buf = {
|
||||||
let mut buf = [0; 270];
|
let mut buf = [0; 270];
|
||||||
@ -195,6 +199,8 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
|
|||||||
Some(Extension::new(&[Rar], "rar"))
|
Some(Extension::new(&[Rar], "rar"))
|
||||||
} else if is_sevenz(&buf) {
|
} else if is_sevenz(&buf) {
|
||||||
Some(Extension::new(&[SevenZip], "7z"))
|
Some(Extension::new(&[SevenZip], "7z"))
|
||||||
|
} else if is_squashfs(&buf) {
|
||||||
|
Some(Extension::new(&[Squashfs], "sqfs"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
|
|||||||
- Files with missing extensions: <TMP_DIR>/a
|
- Files with missing extensions: <TMP_DIR>/a
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Alternatively, you can pass an extension to the '--format' flag:
|
hint: Alternatively, you can pass an extension to the '--format' flag:
|
||||||
hint: ouch decompress <TMP_DIR>/a --format tar.gz
|
hint: ouch decompress <TMP_DIR>/a --format tar.gz
|
||||||
|
@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
|
|||||||
- Files with missing extensions: <TMP_DIR>/a
|
- Files with missing extensions: <TMP_DIR>/a
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
|
@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
|
|||||||
- Files with unsupported extensions: <TMP_DIR>/b.unknown
|
- Files with unsupported extensions: <TMP_DIR>/b.unknown
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Alternatively, you can pass an extension to the '--format' flag:
|
hint: Alternatively, you can pass an extension to the '--format' flag:
|
||||||
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz
|
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz
|
||||||
|
@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
|
|||||||
- Files with missing extensions: <TMP_DIR>/a
|
- Files with missing extensions: <TMP_DIR>/a
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Alternatively, you can pass an extension to the '--format' flag:
|
hint: Alternatively, you can pass an extension to the '--format' flag:
|
||||||
hint: ouch decompress <TMP_DIR>/a --format tar.gz
|
hint: ouch decompress <TMP_DIR>/a --format tar.gz
|
||||||
|
@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
|
|||||||
- Files with missing extensions: <TMP_DIR>/a
|
- Files with missing extensions: <TMP_DIR>/a
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
|
@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
|
|||||||
- Files with unsupported extensions: <TMP_DIR>/b.unknown
|
- Files with unsupported extensions: <TMP_DIR>/b.unknown
|
||||||
- Decompression formats are detected automatically from file extension
|
- Decompression formats are detected automatically from file extension
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Alternatively, you can pass an extension to the '--format' flag:
|
hint: Alternatively, you can pass an extension to the '--format' flag:
|
||||||
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz
|
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di
|
|||||||
[ERROR] Failed to parse `--format tar.gz.unknown`
|
[ERROR] Failed to parse `--format tar.gz.unknown`
|
||||||
- Unsupported extension 'unknown'
|
- Unsupported extension 'unknown'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)"
|
|||||||
[ERROR] Failed to parse `--format targz`
|
[ERROR] Failed to parse `--format targz`
|
||||||
- Unsupported extension 'targz'
|
- Unsupported extension 'targz'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di
|
|||||||
[ERROR] Failed to parse `--format .tar.$#!@.rest`
|
[ERROR] Failed to parse `--format .tar.$#!@.rest`
|
||||||
- Unsupported extension '$#!@'
|
- Unsupported extension '$#!@'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di
|
|||||||
[ERROR] Failed to parse `--format tar.gz.unknown`
|
[ERROR] Failed to parse `--format tar.gz.unknown`
|
||||||
- Unsupported extension 'unknown'
|
- Unsupported extension 'unknown'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)"
|
|||||||
[ERROR] Failed to parse `--format targz`
|
[ERROR] Failed to parse `--format targz`
|
||||||
- Unsupported extension 'targz'
|
- Unsupported extension 'targz'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di
|
|||||||
[ERROR] Failed to parse `--format .tar.$#!@.rest`
|
[ERROR] Failed to parse `--format .tar.$#!@.rest`
|
||||||
- Unsupported extension '$#!@'
|
- Unsupported extension '$#!@'
|
||||||
|
|
||||||
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z
|
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z, sqfs
|
||||||
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
|
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst, squashfs
|
||||||
hint:
|
hint:
|
||||||
hint: Examples:
|
hint: Examples:
|
||||||
hint: --format tar
|
hint: --format tar
|
||||||
|
Loading…
x
Reference in New Issue
Block a user