use anyhow::{bail, Result};
use clap::Parser;
use execheck::{analyze_files, collect_executable_files, print_report, ScanOptions, OutputFormat, FileFilter};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
#[arg(required = true)]
paths: Vec<PathBuf>,
#[arg(short, long)]
recursive: bool,
#[arg(short, long, value_enum, default_value = "human")]
output: OutputFormat,
#[arg(short = 'f', long)]
output_file: Option<PathBuf>,
#[arg(long)]
strict: bool,
#[arg(long)]
issues_only: bool,
#[arg(long, value_enum, default_value = "all")]
filter: FileFilterArg,
#[arg(long, value_delimiter = ',')]
extensions: Option<Vec<String>>,
#[arg(short = 'x', long)]
one_filesystem: bool,
}
#[derive(clap::ValueEnum, Clone, Debug)]
enum FileFilterArg {
All,
Exe,
Dll,
ExeDll,
Custom,
}
fn main() -> Result<()> {
let args = Args::parse();
let file_filter = match args.filter {
FileFilterArg::All => FileFilter::All,
FileFilterArg::Exe => FileFilter::WindowsExecutables,
FileFilterArg::Dll => FileFilter::WindowsDlls,
FileFilterArg::ExeDll => FileFilter::WindowsExecutablesAndDlls,
FileFilterArg::Custom => {
if let Some(extensions) = args.extensions {
FileFilter::Extensions(extensions)
} else {
eprintln!("Warning: --filter=custom specified but no --extensions provided, using all files");
FileFilter::All
}
}
};
let options = ScanOptions {
recursive: args.recursive,
issues_only: args.issues_only,
strict: args.strict,
file_filter,
one_filesystem: args.one_filesystem,
};
let mut all_files = Vec::new();
for path in &args.paths {
if path.is_dir() {
let dir_files = collect_executable_files(path, &options)?;
all_files.extend(dir_files);
} else {
all_files.push(path.clone());
}
}
let report = analyze_files(all_files, &options)?;
print_report(&report, &args.output, args.output_file.as_ref())?;
if args.strict && report.summary.insecure_files > 0 {
bail!("{} files have security issues", report.summary.insecure_files);
}
Ok(())
}