diff --git a/rust/src/zstd_cli.rs b/rust/src/zstd_cli.rs index db33cb801..41c653a5d 100644 --- a/rust/src/zstd_cli.rs +++ b/rust/src/zstd_cli.rs @@ -105,6 +105,7 @@ unsafe extern "C" { fn UTIL_countPhysicalCores() -> c_int; #[cfg(feature = "compression")] fn UTIL_countLogicalCores() -> c_int; + fn UTIL_getFileSize(input_file_name: *const c_char) -> u64; fn UTIL_createFileNamesTable_fromFileName( input_file_name: *const c_char, ) -> *mut FileNamesTable; @@ -226,8 +227,27 @@ unsafe extern "C" { block_size: usize, nb_workers: c_int, ) -> c_int; + #[cfg(feature = "compression")] + fn ZSTD_getCParams( + compression_level: c_int, + estimated_src_size: u64, + dict_size: usize, + ) -> ZSTD_compressionParameters; } +const ZSTD_STRATEGY_NAMES: [&str; 10] = [ + "", + "ZSTD_fast", + "ZSTD_dfast", + "ZSTD_greedy", + "ZSTD_lazy", + "ZSTD_lazy2", + "ZSTD_btlazy2", + "ZSTD_btopt", + "ZSTD_btultra", + "ZSTD_btultra2", +]; + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Operation { Compress, @@ -285,6 +305,7 @@ struct Cli { row_match_finder: i32, exclude_compressed: bool, compression_params: ZSTD_compressionParameters, + show_default_cparams: bool, bench_end_level: Option, bench_nb_seconds: Option, recursive: bool, @@ -336,6 +357,7 @@ impl Cli { row_match_finder: ZSTD_PS_AUTO, exclude_compressed: false, compression_params: ZSTD_compressionParameters::default(), + show_default_cparams: false, bench_end_level: None, bench_nb_seconds: None, recursive: false, @@ -784,6 +806,7 @@ fn parse_long_option( | "--exclude-compressed" | "--no-name" | "--list" + | "--show-default-cparams" ) { return Err(format!("{name} does not take an argument")); @@ -993,6 +1016,10 @@ fn parse_long_option( cli.operation = Operation::List; Ok(None) } + "--show-default-cparams" => { + cli.show_default_cparams = true; + Ok(None) + } "--filelist" => { let value = next_field(attached, args, index, name)?; cli.file_lists.push(cstring(&value)?); @@ -1038,8 +1065,7 @@ fn parse_long_option( | "--fake-stdin-is-console" | "--fake-stdout-is-console" | "--fake-stderr-is-console" - | "--trace-file-stat" - | "--show-default-cparams" => { + | "--trace-file-stat" => { unsupported(name)?; Ok(None) } @@ -1353,6 +1379,76 @@ fn check_terminal_safety(cli: &Cli) -> Result<(), String> { Ok(()) } +#[cfg(feature = "compression")] +unsafe fn print_default_cparams(cli: &Cli) { + let dict_size = cli.dictionary.as_ref().map_or(0, |dictionary| unsafe { + UTIL_getFileSize(dictionary.as_ptr()) as usize + }); + for input in &cli.inputs { + let file_size = unsafe { UTIL_getFileSize(input.as_ptr()) }; + let cparams = unsafe { ZSTD_getCParams(cli.level, file_size, dict_size) }; + let strategy = ZSTD_STRATEGY_NAMES + .get(cparams.strategy as usize) + .copied() + .unwrap_or(""); + if file_size != u64::MAX { + eprintln!("{} ({} bytes)", input.to_string_lossy(), file_size); + } else { + eprintln!("{} (src size unknown)", input.to_string_lossy()); + } + eprintln!(" - windowLog : {}", cparams.windowLog); + eprintln!(" - chainLog : {}", cparams.chainLog); + eprintln!(" - hashLog : {}", cparams.hashLog); + eprintln!(" - searchLog : {}", cparams.searchLog); + eprintln!(" - minMatch : {}", cparams.minMatch); + eprintln!(" - targetLength : {}", cparams.targetLength); + eprintln!(" - strategy : {strategy} ({})", cparams.strategy); + } +} + +#[cfg(feature = "compression")] +unsafe fn print_actual_cparams(cli: &Cli) { + let dict_size = cli.dictionary.as_ref().map_or(0, |dictionary| unsafe { + UTIL_getFileSize(dictionary.as_ptr()) as usize + }); + for input in &cli.inputs { + let file_size = unsafe { UTIL_getFileSize(input.as_ptr()) }; + let mut actual = unsafe { ZSTD_getCParams(cli.level, file_size, dict_size) }; + let requested = cli.compression_params; + if requested.windowLog != 0 { + actual.windowLog = requested.windowLog; + } + if requested.chainLog != 0 { + actual.chainLog = requested.chainLog; + } + if requested.hashLog != 0 { + actual.hashLog = requested.hashLog; + } + if requested.searchLog != 0 { + actual.searchLog = requested.searchLog; + } + if requested.minMatch != 0 { + actual.minMatch = requested.minMatch; + } + if requested.targetLength != 0 { + actual.targetLength = requested.targetLength; + } + if requested.strategy != 0 { + actual.strategy = requested.strategy; + } + eprintln!( + "--zstd=wlog={},clog={},hlog={},slog={},mml={},tlen={},strat={}", + actual.windowLog, + actual.chainLog, + actual.hashLog, + actual.searchLog, + actual.minMatch, + actual.targetLength, + actual.strategy + ); + } +} + #[cfg(feature = "compression")] unsafe fn run_compress( cli: &Cli, @@ -1593,6 +1689,10 @@ fn run_cli(mut cli: Cli) -> Result { cli.output = Some(cstring(STDOUT_MARK)?); } + if cli.show_default_cparams && cli.operation == Operation::Decompress { + return Err("error : can't use --show-default-cparams in decompression mode".to_owned()); + } + check_terminal_safety(&cli)?; if cli.operation == Operation::Compress { @@ -1648,6 +1748,15 @@ fn run_cli(mut cli: Cli) -> Result { unsafe { FIO_addAbortHandler(); apply_preferences(&cli, prefs, ctx); + #[cfg(feature = "compression")] + if cli.operation == Operation::Compress { + if cli.show_default_cparams { + print_default_cparams(&cli); + } + if cli.display_level >= 4 { + print_actual_cparams(&cli); + } + } } let output = cli .output @@ -1949,6 +2058,25 @@ mod tests { assert!(error.contains("does not take an argument")); } + #[test] + fn show_default_cparams_is_a_compression_information_flag() { + let cli = parse(&["zstd", "--show-default-cparams", "input"]); + + assert!(cli.show_default_cparams); + assert_eq!(cli.operation, Operation::Compress); + } + + #[test] + fn show_default_cparams_rejects_attached_values() { + let error = parse_args(vec![ + OsString::from("zstd"), + OsString::from("--show-default-cparams=x"), + ]) + .expect_err("show-default-cparams must not consume an attached value"); + + assert!(error.contains("does not take an argument")); + } + #[test] fn filelist_accumulates_both_syntaxes() { let cli = parse(&["zstd", "--filelist=one.txt", "--filelist", "two.txt", "in"]);