From f712f6814eafbb0869beeb094b6d4c5807d9db74 Mon Sep 17 00:00:00 2001 From: Caden Kline Date: Thu, 9 Apr 2026 23:05:37 -0400 Subject: [PATCH 1/3] fix scratch arg --- Cargo.toml | 2 +- src/analysis/mod.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9013746..7962354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" sha1 = "0.10.6" tar = { git = "https://github.com/jamcleod/tar-rs" } -tempfile = "3.19.0" +tempfile = "3.27.0" thiserror = "2.0.12" wait-timeout = "0.2.1" walkdir = "2.5.0" diff --git a/src/analysis/mod.rs b/src/analysis/mod.rs index 31f7b22..8020a73 100644 --- a/src/analysis/mod.rs +++ b/src/analysis/mod.rs @@ -54,14 +54,14 @@ pub fn extract_and_process( ) -> Result<(), ExtractProcessError> { let extractor_name = extractor.name(); - let scratch_dir = scratch_dir + let origin_dir = scratch_dir .map(Path::to_path_buf) .unwrap_or_else(env::temp_dir); - + log::debug!("looking at {:?}", scratch_dir); let temp_dir_prefix = format!("fw2tar_{extractor_name}"); - let temp_dir = TempDir::with_prefix_in(temp_dir_prefix, &scratch_dir) + let mut temp_dir = TempDir::with_prefix_in(temp_dir_prefix, &origin_dir) .map_err(ExtractProcessError::TempDirFail)?; - + temp_dir.disable_cleanup(scratch_dir.is_some()); let extract_dir = temp_dir.path(); let log_file = { @@ -123,7 +123,7 @@ pub fn extract_and_process( } drop(temp_dir); - + Ok(()) } From 0ec327712672ba68bd7e5cc6eaee33e4808d8fab Mon Sep 17 00:00:00 2001 From: Caden Kline Date: Thu, 21 May 2026 14:48:22 -0400 Subject: [PATCH 2/3] working external and internal mount points override symlinks at mount_points removing commented function handle mount point dirs removed excess logging --- Cargo.lock | 18 +-- src/analysis/find_linux_filesystems.rs | 38 ++++-- src/analysis/mod.rs | 15 ++- src/archive.rs | 160 +++++++++++++++---------- src/args.rs | 7 ++ src/error.rs | 3 + src/extractors/mod.rs | 13 +- src/lib.rs | 110 ++++++++++++----- 8 files changed, 245 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 405533d..46c0cc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -468,9 +468,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libredox" @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" @@ -659,9 +659,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "1.0.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -763,9 +763,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", diff --git a/src/analysis/find_linux_filesystems.rs b/src/analysis/find_linux_filesystems.rs index 7ed1523..64ea9ba 100644 --- a/src/analysis/find_linux_filesystems.rs +++ b/src/analysis/find_linux_filesystems.rs @@ -1,4 +1,5 @@ use std::cmp::Reverse; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -14,7 +15,7 @@ const MIN_REQUIRED: usize = (KEY_DIRS.len() + CRITICAL_FILES.len()) / 2; #[derive(Debug, Clone)] pub struct PrimaryFilesystem { - pub path: PathBuf, + pub paths: BTreeMap, pub size: u64, pub num_files: usize, pub key_file_count: usize, @@ -25,12 +26,13 @@ pub fn find_linux_filesystems( start_dir: &Path, min_executables: Option, extractor_name: &str, + internal_paths: Option<&BTreeMap>, ) -> Vec { let mut filesystems = Vec::new(); let min_executables = min_executables.unwrap_or(DEFAULT_MIN_EXECUTABLES); log::info!("Searching {start_dir:?}"); - + let mut paths: BTreeMap = BTreeMap::new(); for entry in WalkDir::new(start_dir) .max_depth(MAX_EXPLORE_DEPTH) .into_iter() @@ -38,21 +40,27 @@ pub fn find_linux_filesystems( { let Ok(entry) = entry else { continue }; - //log::trace!("looking at {:?}", entry.path()); - let mut total_matches = 0; let root = entry.path(); for dir in KEY_DIRS { if root.join(dir).exists() { - //log::trace!("{dir} found in {root:?}"); + //log::debug!("{dir} found in {root:?}"); total_matches += 1; } } - + if let Some(internals) = internal_paths{ + if let Some(result ) = internals.iter().find(|(_,v)| {entry.path().to_str().unwrap_or_default().contains(*v)}){ + if ! paths.contains_key(result.0) { + paths.insert(result.0.clone(), entry.path().to_path_buf()); + } else { + continue; //don't double add + } + } + } for file in CRITICAL_FILES { if root.join(file).exists() { - //log::trace!("{file} found in {root:?}"); + //log::debug!("{file} found in {root:?}"); total_matches += 1; } } @@ -66,9 +74,10 @@ pub fn find_linux_filesystems( if total_executables >= min_executables { log::info!("{root:?}: {total_executables}, {total_size}, {total_files}"); - + let mut paths: BTreeMap = BTreeMap::new(); + paths.insert(PathBuf::from("/"),root.to_owned()); filesystems.push(PrimaryFilesystem { - path: root.to_owned(), + paths: paths, size: total_size, num_files: total_files, key_file_count: total_matches, @@ -81,9 +90,16 @@ pub fn find_linux_filesystems( log::info!("Directory {} had {total_matches}", root.display()); } } - + filesystems = filesystems.iter().map(|file_system| -> PrimaryFilesystem{ + let root = file_system.paths.get(&PathBuf::from("/")).unwrap(); + let mut new_filesystem = file_system.clone(); + let mut new_paths = paths.clone(); + new_paths.insert(PathBuf::from("/"), root.to_owned()); + new_filesystem.paths = new_paths; + new_filesystem + }).collect(); filesystems.sort_by_key(|fs| Reverse((fs.executables, fs.size, fs.key_file_count))); - + //log::debug!("filesystems: {:?}", filesystems); filesystems } diff --git a/src/analysis/mod.rs b/src/analysis/mod.rs index 8020a73..562cca8 100644 --- a/src/analysis/mod.rs +++ b/src/analysis/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::Instant; @@ -51,13 +51,14 @@ pub fn extract_and_process( results: &Mutex>, metadata: &Metadata, removed_devices: Option<&Mutex>>, + external_paths: Option<&BTreeMap>, + internal_paths: Option<&BTreeMap>, ) -> Result<(), ExtractProcessError> { let extractor_name = extractor.name(); let origin_dir = scratch_dir .map(Path::to_path_buf) .unwrap_or_else(env::temp_dir); - log::debug!("looking at {:?}", scratch_dir); let temp_dir_prefix = format!("fw2tar_{extractor_name}"); let mut temp_dir = TempDir::with_prefix_in(temp_dir_prefix, &origin_dir) .map_err(ExtractProcessError::TempDirFail)?; @@ -84,7 +85,7 @@ pub fn extract_and_process( log::info!("{extractor_name} took {elapsed:.2} seconds"); } - let rootfs_choices = find_linux_filesystems(extract_dir, None, extractor_name); + let rootfs_choices = find_linux_filesystems(extract_dir, None, extractor_name, internal_paths); if rootfs_choices.is_empty() { log::error!("No Linux filesystems found extracting {in_file:?} with {extractor_name}"); @@ -105,9 +106,13 @@ pub fn extract_and_process( let file_name = out_file_base.file_name().unwrap().to_string_lossy(); out_file_base.with_file_name(format!("{}.{extractor_name}.{i}.tar.gz", file_name)) }; - + let combined_paths = if let Some(external) = external_paths { + fs.paths.iter().chain(external.iter()).map(|(k,v)| (k.clone(),v.clone())).collect() + }else { + fs.paths.clone() + }; // XXX: improve error handling here - let file_node_count = tar_fs(&fs.path, &tar_path, metadata, removed_devices).unwrap(); + let file_node_count = tar_fs(&combined_paths, &tar_path, metadata, removed_devices, ).unwrap(); let archive_hash = sha1_file(&tar_path).unwrap(); results.lock().unwrap().push(ExtractionResult { diff --git a/src/archive.rs b/src/archive.rs index 12342fd..6fd7086 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use std::collections::{BTreeMap, BTreeSet, HashSet, btree_set}; use std::fs::{self, File}; use std::io::{self, Cursor, Write}; use std::iter; use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; -use std::path::{Component, Path, PathBuf}; +use std::path::{Component,Path, PathBuf}; use std::sync::Mutex; use flate2::write::GzEncoder; @@ -23,23 +23,23 @@ fn is_blk_or_chr(meta: fs::Metadata) -> bool { } pub fn tar_fs( - rootfs_dir: &Path, + paths: &BTreeMap, tar_path: &Path, fw2tar_metadata: &Metadata, removed_devices: Option<&Mutex>>, ) -> io::Result { let mut tar_entry_count = 0; - let prefix_to_skip = rootfs_dir.components().count(); + //log::debug!("paths: {:?}", paths); - let should_add_to_tar = |entry: &DirEntry| { - if entry.path() == rootfs_dir { + let should_add_to_tar = |entry: &DirEntry, start_dir: &Path, mount_point: &Path, count: usize| { + if entry.path() == start_dir { return true; } if entry.metadata().map(is_blk_or_chr).unwrap_or(false) { if let Some(removed_devices) = removed_devices { - let path = iter::once(Component::RootDir) - .chain(entry.path().components().skip(prefix_to_skip)) + let path = mount_point.components() + .chain(entry.path().components().skip(count)) .collect(); removed_devices.lock().unwrap().insert(path); @@ -74,68 +74,104 @@ pub fn tar_fs( let file = File::create(tar_path)?; let encoder = GzEncoder::new(file, Compression::default()); - + let mut paths_proven: BTreeSet = BTreeSet::new(); let mut tar = tar::Builder::new(encoder); + for (mount_point, start_dir )in paths{ + let skip = start_dir.components().count(); + //make sure mount point exists + mount_point.components().fold(PathBuf::from("./"),|mut path, component| { + if component != Component::RootDir { + path = path.join(component); + if &path != mount_point && ! paths_proven.insert(path.clone()) { + let mut header = tar::Header::new_gnu(); + header.set_mode(0o755); + tar.append_data(&mut header, format!("{}/",path.display()), Cursor::new(Vec::new())).unwrap(); + } + path + } else { + path + } + + }); + for entry in WalkDir::new(start_dir) + .into_iter() + .filter_entry(|a|should_add_to_tar(a, start_dir, mount_point, skip)) + { + let Ok(entry) = entry else { continue }; + let Ok(metadata) = entry.metadata() else { + continue; + }; + + let rel_path: PathBuf = mount_point.components().skip(1).chain(entry.path().components().skip(skip)).collect(); + let entry_path = format!("./{}", rel_path.display()); + + let entry_path = if !entry_path.ends_with('/') && metadata.is_dir() { + format!("{entry_path}/") + } else { + entry_path + }; + //log::debug!("header: {:?},{:?},{:?},{:?}", entry_path,entry.path(),mount_point,start_dir); + + let data = if metadata.is_file() { + let result = fs::read(entry.path()); + match result { + Err(a) => {log::debug!("{:?}",a); continue;}, + Ok(a) => a, + } + } else { + Vec::new() + }; + + let mut header = tar::Header::new_gnu(); + header.set_metadata_in_mode(&metadata, tar::HeaderMode::Deterministic); + header.set_mode(metadata.permissions().mode()); + + if entry_path == "./" { + header.set_mode(0o755); + } - for entry in WalkDir::new(rootfs_dir) - .into_iter() - .filter_entry(should_add_to_tar) - { - let Ok(entry) = entry else { continue }; - let Ok(metadata) = entry.metadata() else { - continue; - }; - - let rel_path: PathBuf = entry.path().components().skip(prefix_to_skip).collect(); - let entry_path = format!("./{}", rel_path.display()); - - let entry_path = if !entry_path.ends_with('/') && metadata.is_dir() { - format!("{entry_path}/") - } else { - entry_path - }; - - let data = if metadata.is_file() { - fs::read(entry.path())? - } else { - Vec::new() - }; - - let mut header = tar::Header::new_gnu(); - header.set_metadata_in_mode(&metadata, tar::HeaderMode::Deterministic); - header.set_mode(metadata.permissions().mode()); - - if entry_path == "./" { - header.set_mode(0o755); - } - - header.set_mtime(FIXED_TIMESTAMP); + header.set_mtime(FIXED_TIMESTAMP); - if metadata.is_file() { - header.set_size(data.len() as u64); // buffering to prevent ToKToU - } + if metadata.is_file() { + header.set_size(data.len() as u64); // buffering to prevent ToKToU + } - if let Ok(Some(user)) = User::from_uid(Uid::from_raw(metadata.uid())) { - header.set_username(&user.name).unwrap(); - } + if let Ok(Some(user)) = User::from_uid(Uid::from_raw(metadata.uid())) { + header.set_username(&user.name).unwrap(); + } - if let Ok(Some(user)) = Group::from_gid(Gid::from_raw(metadata.gid())) { - header.set_groupname(&user.name).unwrap(); - } + if let Ok(Some(user)) = Group::from_gid(Gid::from_raw(metadata.gid())) { + header.set_groupname(&user.name).unwrap(); + } - header.set_cksum(); + header.set_cksum(); + if metadata.is_symlink() { + //check if symlink would interfere with a mount point + if let Some(_mount_point) = paths.keys().find(|mount_point: &&PathBuf| { + let root: PathBuf = iter::once(Component::RootDir).chain(rel_path.components()).collect(); + mount_point.ancestors().any(|a| a.eq(&root)) + }){ + header.set_entry_type(tar::EntryType::Directory); + log::debug!("root: {header:?}"); + paths_proven.insert(PathBuf::from(entry_path.clone())); + tar.append_data(&mut header, format!("{entry_path}/"), Cursor::new(Vec::new()))?; + } else { + tar.append_link( + &mut header, + entry_path, + fs::read_link(entry.path()).unwrap(), + )?; + } + + } else { + if metadata.is_dir() { + paths_proven.insert(PathBuf::from(entry_path.clone())); + } + tar.append_data(&mut header, entry_path, Cursor::new(data))?; + } - if metadata.is_symlink() { - tar.append_link( - &mut header, - entry_path, - fs::read_link(entry.path()).unwrap(), - )?; - } else { - tar.append_data(&mut header, entry_path, Cursor::new(data))?; + tar_entry_count += 1; } - - tar_entry_count += 1; } tar.finish()?; diff --git a/src/args.rs b/src/args.rs index 3ddf6e3..86aa1eb 100644 --- a/src/args.rs +++ b/src/args.rs @@ -50,4 +50,11 @@ pub struct Args { /// Timeout for extractors, measured in seconds #[arg(long, default_value_t = 20)] pub timeout: u64, + + + // external + #[arg(long,short('e'),requires("scratch_dir"))] + pub external: Option>, + #[arg(long,short('i'),requires("firmware"))] + pub internal: Option>, } diff --git a/src/error.rs b/src/error.rs index 3e6b2e1..917c044 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,4 +15,7 @@ pub enum Fw2tarError { #[error("Output file ({0:?}) already exists. Use --force to overwrite.")] OutputExists(PathBuf), + + #[error("External mapping failed: {0:?}.")] + ExternalFailure(String), } diff --git a/src/extractors/mod.rs b/src/extractors/mod.rs index 912f18b..b8e6d5d 100644 --- a/src/extractors/mod.rs +++ b/src/extractors/mod.rs @@ -63,12 +63,21 @@ pub trait Extractor: Sync { verbose: bool, ) -> Result<(), ExtractError>; - fn cmd_output_to_result(&self, output: Output, timed_out: bool, verbose: bool) -> Result<(), ExtractError> { + fn cmd_output_to_result( + &self, + output: Output, + timed_out: bool, + verbose: bool, + ) -> Result<(), ExtractError> { if output.status.success() { Ok(()) } else { if let Some(code) = output.status.code() { - log::error!("{} exited with error code {}. Run with --loud to see more output", self.name(), code); + log::error!( + "{} exited with error code {}. Run with --loud to see more output", + self.name(), + code + ); if verbose { if !output.stdout.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index adc4ce4..0c5a69b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,11 +10,13 @@ pub use error::Fw2tarError; use metadata::Metadata; use std::cmp::Reverse; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::path::PathBuf; use std::sync::Mutex; use std::{env, fs, thread}; +use crate::archive::tar_fs; + pub enum BestExtractor { Best(&'static str), Only(&'static str), @@ -22,26 +24,68 @@ pub enum BestExtractor { None, } + +fn extract_external_args(external: Option>, scratch_dir: Option<&PathBuf>) -> Result>, Fw2tarError> { + + if let Some(external) = external { + + if let Some(scratch_dir) = scratch_dir { + let external_mapping: Result, Fw2tarError> = external.iter().map(|a| -> Result<(PathBuf,PathBuf), Fw2tarError> { + if let Some((path_str,mount_point)) = a.split_once(":"){ + let external = scratch_dir.join(path_str); + //log::debug!("external: {:?}, {:?}",external,env::current_dir()); + if external.exists(){ + return Ok((PathBuf::from(mount_point),external)); + } + } + Err(Fw2tarError::ExternalFailure(a.to_string())) + + }).collect(); + Ok(Some(external_mapping?)) + + }else { + Err(Fw2tarError::ExternalFailure("No scratch_dir".to_string())) + } + }else { + Ok(None) + } +} pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { - if !args.firmware.is_file() { + let external_paths = if args.external.is_some() { + extract_external_args(args.external.clone(),args.scratch_dir.as_ref())? + } else{ + None + }; + let internals: Option> = if let Some(internal) = args.internal{ + let internal_mapping: Result, Fw2tarError> = internal.iter().map(|a| -> Result<(PathBuf,String),Fw2tarError> { + if let Some((search_string,mount_point)) = a.split_once(":"){ + Ok((PathBuf::from(mount_point),search_string.to_string())) + }else{Err(Fw2tarError::ExternalFailure(a.to_string()))} + }).collect(); + Some(internal_mapping?) + } else {None}; + let external_only = external_paths.is_some() && internals.is_none(); + let results: Mutex> = Mutex::new(Vec::new()); + + let removed_devices: Option>> = + args.log_devices.then(|| Mutex::new(HashSet::new())); + + if !args.firmware.is_file() && !external_only { if args.firmware.exists() { return Err(Fw2tarError::FirmwareNotAFile(args.firmware)); } else { return Err(Fw2tarError::FirmwareDoesNotExist(args.firmware)); } } - - let output = args - .output - .unwrap_or_else(|| { - // Use file_stem() which should behave like Python's Path.stem - if let Some(stem) = args.firmware.file_stem() { - args.firmware.with_file_name(stem) - } else { - // No stem available, use as-is - args.firmware.clone() - } - }); + let output = args.output.unwrap_or_else(|| { + // Use file_stem() which should behave like Python's Path.stem + if let Some(stem) = args.firmware.file_stem() { + args.firmware.with_file_name(stem) + } else { + // No stem available, use as-is + args.firmware.clone() + } + }); let selected_output_path = { // Simple string append to avoid with_extension() being greedy @@ -60,7 +104,7 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { }; extractors::set_timeout(args.timeout); - + //remove clone let extractors: Vec<_> = args .extractors .map(|extractors| extractors.split(",").map(String::from).collect()) @@ -70,12 +114,8 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { .collect() }); - let results: Mutex> = Mutex::new(Vec::new()); - - let removed_devices: Option>> = - args.log_devices.then(|| Mutex::new(HashSet::new())); - - thread::scope(|threads| -> Result<(), Fw2tarError> { + if !external_only { + thread::scope(|threads| -> Result<(), Fw2tarError> { for extractor_name in extractors { let extractor = extractors::get_extractor(&extractor_name) .ok_or_else(|| Fw2tarError::InvalidExtractor(extractor_name.clone()))?; @@ -92,6 +132,8 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { &results, &metadata, removed_devices.as_ref(), + external_paths.as_ref(), + internals.as_ref(), ) { log::info!("{} error: {e}", extractor.name()); } @@ -100,6 +142,12 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { Ok(()) })?; + }else { + let result = tar_fs(&external_paths.unwrap(), &selected_output_path, &metadata, removed_devices.as_ref()); + //log::debug!("{:?}",result); + } + + if let Some(removed_devices) = removed_devices { let mut removed_devices = removed_devices @@ -119,25 +167,26 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { let file_name = output.file_name().unwrap().to_string_lossy(); output.with_file_name(format!("{}.devices.log", file_name)) }; - fs::write( - devices_log_path, - removed_devices.join("\n"), - ) - .unwrap(); + fs::write(devices_log_path, removed_devices.join("\n")).unwrap(); } } - + let results = results.lock().unwrap(); let mut best_results: Vec<_> = results.iter().filter(|&res| res.index == 0).collect(); - let result = if best_results.is_empty() { return Ok((BestExtractor::None, selected_output_path)); } else if best_results.len() == 1 { - Ok((BestExtractor::Only(best_results[0].extractor), selected_output_path.clone())) + Ok(( + BestExtractor::Only(best_results[0].extractor), + selected_output_path.clone(), + )) } else { best_results.sort_by_key(|res| Reverse((res.file_node_count, res.extractor == "unblob"))); - Ok((BestExtractor::Best(best_results[0].extractor), selected_output_path.clone())) + Ok(( + BestExtractor::Best(best_results[0].extractor), + selected_output_path.clone(), + )) }; let best_result = best_results[0]; @@ -145,4 +194,5 @@ pub fn main(args: args::Args) -> Result<(BestExtractor, PathBuf), Fw2tarError> { fs::rename(&best_result.path, &selected_output_path).unwrap(); result + } From dfcf4cae36aff130aa1c9b04f57a0da460c43f21 Mon Sep 17 00:00:00 2001 From: Caden Kline Date: Tue, 26 May 2026 11:14:55 -0400 Subject: [PATCH 3/3] document args --- src/args.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 86aa1eb..154f460 100644 --- a/src/args.rs +++ b/src/args.rs @@ -52,9 +52,10 @@ pub struct Args { pub timeout: u64, - // external + /// external mount point relative path to scrath directory path:mount_point #[arg(long,short('e'),requires("scratch_dir"))] pub external: Option>, + /// internal mount point search_string:mount_point #[arg(long,short('i'),requires("firmware"))] pub internal: Option>, }