scanner: Add post-scan progress analysis

This commit is contained in:
David Wendt 2021-09-11 11:28:45 -04:00 committed by Mike Welsh
parent d08d2ec783
commit f7a4593ab4
5 changed files with 128 additions and 19 deletions

101
scanner/src/analyze.rs Normal file
View File

@ -0,0 +1,101 @@
//! Post-scan analysis
use crate::cli_options::AnalyzeOpt;
use crate::file_results::{FileResults, Step};
use std::cmp::max;
use std::fs::File;
/// Generate and print statistics related to a scan's results
pub fn analyze(results: impl Iterator<Item = FileResults>) {
let mut total = 0;
let mut start = 0;
let mut read = 0;
let mut decompress = 0;
let mut parse = 0;
let mut execute = 0;
let mut complete = 0;
for result in results {
total += 1;
match result.progress {
Step::Start => start += 1,
Step::Read => read += 1,
Step::Decompress => decompress += 1,
Step::Parse => parse += 1,
Step::Execute => execute += 1,
Step::Complete => complete += 1,
}
}
println!("Scanned {} swf files.", total);
let digits = max(
(start as f64).log10().ceil() as usize,
max(
(read as f64).log10().ceil() as usize,
max(
(decompress as f64).log10().ceil() as usize,
max(
(parse as f64).log10().ceil() as usize,
max(
(execute as f64).log10().ceil() as usize,
(complete as f64).log10().ceil() as usize,
),
),
),
),
) + 4;
println!();
if start > 0 {
println!(
"{:>digits$} movies panicked or crashed the scanner",
start,
digits = digits
);
}
println!(
"{:>digits$} movies failed when reading",
read,
digits = digits
);
println!(
"{:>digits$} movies failed to decompress",
decompress,
digits = digits
);
println!("{:>digits$} movies failed to parse", parse, digits = digits);
println!(
"{:>digits$} movies failed to execute",
execute,
digits = digits
);
println!(
"{:>digits$} movies completed without errors",
complete,
digits = digits
);
}
pub fn analyze_main(opt: AnalyzeOpt) -> Result<(), std::io::Error> {
let file = File::open(opt.input_path)?;
let reader = csv::Reader::from_reader(file);
analyze(reader.into_deserialize::<FileResults>().map(|r| {
match r {
Ok(fr) => fr,
Err(e) => {
// Treat unparseable CSV rows as a scanner panic
FileResults {
error: Some(format!("{}", e)),
..FileResults::default()
}
}
}
}));
Ok(())
}

View File

@ -15,6 +15,9 @@ pub enum Mode {
/// Scan an entire directory for SWF files /// Scan an entire directory for SWF files
Scan(ScanOpt), Scan(ScanOpt),
/// Analyze a previously executed scan and compile statistics on it
Analyze(AnalyzeOpt),
/// Execute a single SWF file and generate a machine-readable report /// Execute a single SWF file and generate a machine-readable report
ExecuteReport(ExecuteReportOpt), ExecuteReport(ExecuteReportOpt),
} }
@ -34,6 +37,13 @@ pub struct ScanOpt {
pub ignore: Vec<String>, pub ignore: Vec<String>,
} }
#[derive(Clap, Debug)]
pub struct AnalyzeOpt {
/// The CSV file to reanalyze
#[clap(name = "input", parse(from_os_str))]
pub input_path: PathBuf,
}
#[derive(Clap, Debug)] #[derive(Clap, Debug)]
pub struct ExecuteReportOpt { pub struct ExecuteReportOpt {
/// The single SWF file to parse and run /// The single SWF file to parse and run

View File

@ -8,13 +8,13 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt; use std::fmt;
use std::fmt::Write; use std::fmt::Write;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AvmType { pub enum AvmType {
Avm1, Avm1,
Avm2, Avm2,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Compression { pub enum Compression {
None, None,
Zlib, Zlib,
@ -32,7 +32,7 @@ impl From<swf::Compression> for Compression {
} }
/// A particular step in the scanner process. /// A particular step in the scanner process.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Step { pub enum Step {
/// Nothing has been done yet. /// Nothing has been done yet.
/// ///
@ -57,7 +57,7 @@ pub enum Step {
} }
/// The result of a single scan. /// The result of a single scan.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FileResults { pub struct FileResults {
/// The file name scanned (including path). /// The file name scanned (including path).
pub name: String, pub name: String,

View File

@ -1,8 +1,10 @@
use crate::analyze::analyze_main;
use crate::cli_options::{Mode, Opt}; use crate::cli_options::{Mode, Opt};
use crate::execute::execute_report_main; use crate::execute::execute_report_main;
use crate::scan::scan_main; use crate::scan::scan_main;
use clap::Clap; use clap::Clap;
mod analyze;
mod cli_options; mod cli_options;
mod execute; mod execute;
mod file_results; mod file_results;
@ -15,6 +17,7 @@ fn main() -> Result<(), std::io::Error> {
match opt.mode { match opt.mode {
Mode::Scan(scan_opt) => scan_main(scan_opt), Mode::Scan(scan_opt) => scan_main(scan_opt),
Mode::Analyze(analyze_opt) => analyze_main(analyze_opt),
Mode::ExecuteReport(exeute_report_opt) => { Mode::ExecuteReport(exeute_report_opt) => {
if execute_report_main(exeute_report_opt).is_err() { if execute_report_main(exeute_report_opt).is_err() {
// Do nothing. // Do nothing.

View File

@ -1,5 +1,6 @@
//! Main/scanner process impls //! Main/scanner process impls
use crate::analyze::analyze;
use crate::cli_options::ScanOpt; use crate::cli_options::ScanOpt;
use crate::file_results::FileResults; use crate::file_results::FileResults;
use crate::ser_bridge::SerBridge; use crate::ser_bridge::SerBridge;
@ -119,8 +120,6 @@ pub fn scan_main(opt: ScanOpt) -> Result<(), std::io::Error> {
let binary_path = env::current_exe()?; let binary_path = env::current_exe()?;
let to_scan = find_files(&opt.input_path, &opt.ignore); let to_scan = find_files(&opt.input_path, &opt.ignore);
let total = to_scan.len() as u64; let total = to_scan.len() as u64;
let mut good = 0;
let mut bad = 0;
let progress = ProgressBar::new(total); let progress = ProgressBar::new(total);
let mut writer = csv::Writer::from_path(opt.output_path.clone())?; let mut writer = csv::Writer::from_path(opt.output_path.clone())?;
@ -169,22 +168,18 @@ pub fn scan_main(opt: ScanOpt) -> Result<(), std::io::Error> {
result result
}) })
.ser_bridge(); .ser_bridge()
.map(|result| {
if let Err(e) = writer.serialize(result.clone()) {
eprintln!("{}", e);
};
for result in result_iter { result
if result.error.is_none() { });
good += 1;
} else {
bad += 1;
}
writer.serialize(result)?; progress.finish();
}
progress.finish_with_message(format!( analyze(result_iter);
"Scanned {} swf files. {} successfully parsed, {} encountered errors",
total, good, bad
));
Ok(()) Ok(())
} }