feat(cli): port default compression parameter reporting

Implement the Rust frontend's --show-default-cparams path, including source
and dictionary size discovery, strategy labels, verbose --zstd parameter
reporting, and the decompression-mode diagnostic.  Keep the C parameter API
as the source of the selected values so the CLI remains aligned with the
active compression build.

Test Plan:
- cargo test --manifest-path rust/cli/Cargo.toml
- cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings
- rustfmt +nightly --edition 2021 rust/src/zstd_cli.rs
- git diff --check

Depends-on: Rust CLI parser, file-selection, and C fileio bridge ports
This commit is contained in:
2026-07-12 10:08:52 +02:00
parent 468ccfa388
commit e75574690b
+130 -2
View File
@@ -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<i32>,
bench_nb_seconds: Option<u32>,
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<i32, String> {
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<i32, String> {
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"]);