Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4e034a987
|
+329
-26
@@ -16,9 +16,13 @@
|
|||||||
//! (`benchzstd.c`) remain C behind the preprocessor-gated bridge, so builds
|
//! (`benchzstd.c`) remain C behind the preprocessor-gated bridge, so builds
|
||||||
//! with `ZSTD_NOBENCH` never reference benchmark symbols.
|
//! with `ZSTD_NOBENCH` never reference benchmark symbols.
|
||||||
//!
|
//!
|
||||||
|
//! Recursive expansion (`-r`), `--filelist`, the output-directory modes, and
|
||||||
|
//! `--list` reuse the C `FileNamesTable` helpers from `programs/util.c` and
|
||||||
|
//! `FIO_listMultipleFiles`, so directory traversal and frame inspection stay
|
||||||
|
//! byte-identical with the C CLI.
|
||||||
|
//!
|
||||||
//! Remaining C-only CLI boundaries are called out in `unsupported()` below:
|
//! Remaining C-only CLI boundaries are called out in `unsupported()` below:
|
||||||
//! dictionary training, recursive/file-list expansion, tracing,
|
//! dictionary training, tracing, and alternate-format selection.
|
||||||
//! alternate-format selection, and the advanced directory modes.
|
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CStr, CString, OsStr, OsString};
|
use std::ffi::{CStr, CString, OsStr, OsString};
|
||||||
@@ -79,6 +83,16 @@ struct ZSTD_compressionParameters {
|
|||||||
strategy: c_int,
|
strategy: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mirror of the `FileNamesTable` in `programs/util.h`; the tables returned
|
||||||
|
/// by the `UTIL_*FNT` helpers are only read and released here, never resized.
|
||||||
|
#[repr(C)]
|
||||||
|
struct FileNamesTable {
|
||||||
|
fileNames: *mut *const c_char,
|
||||||
|
buf: *mut c_char,
|
||||||
|
tableSize: usize,
|
||||||
|
tableCapacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn ZSTD_versionString() -> *const c_char;
|
fn ZSTD_versionString() -> *const c_char;
|
||||||
fn ZSTD_rust_cli_expected_version() -> *const c_char;
|
fn ZSTD_rust_cli_expected_version() -> *const c_char;
|
||||||
@@ -91,6 +105,15 @@ unsafe extern "C" {
|
|||||||
fn UTIL_countPhysicalCores() -> c_int;
|
fn UTIL_countPhysicalCores() -> c_int;
|
||||||
#[cfg(feature = "compression")]
|
#[cfg(feature = "compression")]
|
||||||
fn UTIL_countLogicalCores() -> c_int;
|
fn UTIL_countLogicalCores() -> c_int;
|
||||||
|
fn UTIL_createFileNamesTable_fromFileName(
|
||||||
|
input_file_name: *const c_char,
|
||||||
|
) -> *mut FileNamesTable;
|
||||||
|
fn UTIL_createExpandedFNT(
|
||||||
|
file_names: *const *const c_char,
|
||||||
|
nb_file_names: usize,
|
||||||
|
follow_links: c_int,
|
||||||
|
) -> *mut FileNamesTable;
|
||||||
|
fn UTIL_freeFileNamesTable(table: *mut FileNamesTable);
|
||||||
|
|
||||||
fn FIO_createPreferences() -> *mut FIO_prefs_t;
|
fn FIO_createPreferences() -> *mut FIO_prefs_t;
|
||||||
fn FIO_freePreferences(prefs: *mut FIO_prefs_t);
|
fn FIO_freePreferences(prefs: *mut FIO_prefs_t);
|
||||||
@@ -180,6 +203,12 @@ unsafe extern "C" {
|
|||||||
output: *const c_char,
|
output: *const c_char,
|
||||||
dict: *const c_char,
|
dict: *const c_char,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
#[cfg(feature = "decompression")]
|
||||||
|
fn FIO_listMultipleFiles(
|
||||||
|
nb_files: c_uint,
|
||||||
|
file_names: *const *const c_char,
|
||||||
|
display_level: c_int,
|
||||||
|
) -> c_int;
|
||||||
|
|
||||||
/// Benchmark bridge implemented by the `programs/zstdcli.c` shim, which
|
/// Benchmark bridge implemented by the `programs/zstdcli.c` shim, which
|
||||||
/// owns the `ZSTD_NOBENCH` preprocessor decision. Returns the benchmark
|
/// owns the `ZSTD_NOBENCH` preprocessor decision. Returns the benchmark
|
||||||
@@ -204,6 +233,7 @@ enum Operation {
|
|||||||
Decompress,
|
Decompress,
|
||||||
Test,
|
Test,
|
||||||
Bench,
|
Bench,
|
||||||
|
List,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -256,6 +286,10 @@ struct Cli {
|
|||||||
compression_params: ZSTD_compressionParameters,
|
compression_params: ZSTD_compressionParameters,
|
||||||
bench_end_level: Option<i32>,
|
bench_end_level: Option<i32>,
|
||||||
bench_nb_seconds: Option<u32>,
|
bench_nb_seconds: Option<u32>,
|
||||||
|
recursive: bool,
|
||||||
|
file_lists: Vec<CString>,
|
||||||
|
output_dir_flat: Option<CString>,
|
||||||
|
output_dir_mirror: Option<CString>,
|
||||||
unsupported_program: Option<String>,
|
unsupported_program: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +337,10 @@ impl Cli {
|
|||||||
compression_params: ZSTD_compressionParameters::default(),
|
compression_params: ZSTD_compressionParameters::default(),
|
||||||
bench_end_level: None,
|
bench_end_level: None,
|
||||||
bench_nb_seconds: None,
|
bench_nb_seconds: None,
|
||||||
|
recursive: false,
|
||||||
|
file_lists: Vec::new(),
|
||||||
|
output_dir_flat: None,
|
||||||
|
output_dir_mirror: None,
|
||||||
unsupported_program: None,
|
unsupported_program: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,6 +470,28 @@ fn usage(advanced: bool) {
|
|||||||
out,
|
out,
|
||||||
" --adapt[=min=#,max=#], --rsyncable, --[no-]row-match-finder"
|
" --adapt[=min=#,max=#], --rsyncable, --[no-]row-match-finder"
|
||||||
);
|
);
|
||||||
|
let _ = writeln!(out, "\nFile selection and placement:");
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
" -r Operate recursively on directories"
|
||||||
|
);
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
" --filelist LIST Read a list of input files from LIST"
|
||||||
|
);
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
" --output-dir-flat DIR Store processed files in DIR"
|
||||||
|
);
|
||||||
|
#[cfg(unix)]
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
" --output-dir-mirror DIR Store processed files in DIR, mirroring the input tree"
|
||||||
|
);
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
" -l, --list Print information about .zst file(s)"
|
||||||
|
);
|
||||||
let _ = writeln!(out, "\nBenchmark options:");
|
let _ = writeln!(out, "\nBenchmark options:");
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
@@ -447,9 +507,8 @@ fn usage(advanced: bool) {
|
|||||||
);
|
);
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
"\nNot yet migrated: dictionary training, recursive/file-list expansion,"
|
"\nNot yet migrated: dictionary training, trace, and alternate formats."
|
||||||
);
|
);
|
||||||
let _ = writeln!(out, "trace, alternate formats, and output-directory modes.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,6 +620,32 @@ fn next_os_value(args: &[OsString], index: &mut usize, option: &str) -> Result<O
|
|||||||
Ok(value.clone())
|
Ok(value.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mirrors the C CLI `NEXT_FIELD` macro: an attached `=value` is used as-is
|
||||||
|
/// (even when empty), otherwise the next argument is consumed. Unlike
|
||||||
|
/// `next_value`, an empty attached value never falls through to the next
|
||||||
|
/// argument, so `--output-dir-flat=` stays an empty (and rejected) name.
|
||||||
|
fn next_field(
|
||||||
|
attached: Option<&str>,
|
||||||
|
args: &[OsString],
|
||||||
|
index: &mut usize,
|
||||||
|
option: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if let Some(value) = attached {
|
||||||
|
return Ok(value.to_owned());
|
||||||
|
}
|
||||||
|
*index += 1;
|
||||||
|
let Some(value) = args.get(*index) else {
|
||||||
|
return Err(format!("missing argument for {option}"));
|
||||||
|
};
|
||||||
|
let rendered = value.to_string_lossy().into_owned();
|
||||||
|
if rendered.starts_with('-') {
|
||||||
|
return Err(format!(
|
||||||
|
"{option} cannot be separated from its argument by another option"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(rendered)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn short_attached_value(value: &OsStr, start: usize) -> Option<OsString> {
|
fn short_attached_value(value: &OsStr, start: usize) -> Option<OsString> {
|
||||||
let bytes = &value.as_bytes()[start..];
|
let bytes = &value.as_bytes()[start..];
|
||||||
@@ -689,6 +774,7 @@ fn parse_long_option(
|
|||||||
| "--no-compress-literals"
|
| "--no-compress-literals"
|
||||||
| "--exclude-compressed"
|
| "--exclude-compressed"
|
||||||
| "--no-name"
|
| "--no-name"
|
||||||
|
| "--list"
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return Err(format!("{name} does not take an argument"));
|
return Err(format!("{name} does not take an argument"));
|
||||||
@@ -894,17 +980,47 @@ fn parse_long_option(
|
|||||||
parse_compression_parameters(&value, cli)?;
|
parse_compression_parameters(&value, cli)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
"--list"
|
"--list" => {
|
||||||
| "--train"
|
cli.operation = Operation::List;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
"--filelist" => {
|
||||||
|
let value = next_field(attached, args, index, name)?;
|
||||||
|
cli.file_lists.push(cstring(&value)?);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
"--output-dir-flat" => {
|
||||||
|
let value = next_field(attached, args, index, name)?;
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(
|
||||||
|
"output dir cannot be empty string (did you mean to pass '.' instead?)"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cli.output_dir_flat = Some(cstring(&value)?);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
"--output-dir-mirror" => {
|
||||||
|
/* Parsed only where the C CLI defines UTIL_HAS_MIRRORFILELIST
|
||||||
|
* (POSIX); elsewhere the option falls through as unknown. */
|
||||||
|
let value = next_field(attached, args, index, name)?;
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(
|
||||||
|
"output dir cannot be empty string (did you mean to pass '.' instead?)"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cli.output_dir_mirror = Some(cstring(&value)?);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
"--train"
|
||||||
| "--train-cover"
|
| "--train-cover"
|
||||||
| "--train-fastcover"
|
| "--train-fastcover"
|
||||||
| "--train-legacy"
|
| "--train-legacy"
|
||||||
| "--max"
|
| "--max"
|
||||||
| "--maxdict"
|
| "--maxdict"
|
||||||
| "--dictID"
|
| "--dictID"
|
||||||
| "--filelist"
|
|
||||||
| "--output-dir-flat"
|
|
||||||
| "--output-dir-mirror"
|
|
||||||
| "--patch-from"
|
| "--patch-from"
|
||||||
| "--trace"
|
| "--trace"
|
||||||
| "--format"
|
| "--format"
|
||||||
@@ -947,6 +1063,9 @@ fn parse_short_options(
|
|||||||
'z' => cli.operation = Operation::Compress,
|
'z' => cli.operation = Operation::Compress,
|
||||||
't' => cli.operation = Operation::Test,
|
't' => cli.operation = Operation::Test,
|
||||||
'b' => cli.operation = Operation::Bench,
|
'b' => cli.operation = Operation::Bench,
|
||||||
|
'l' => cli.operation = Operation::List,
|
||||||
|
#[cfg(any(unix, windows))]
|
||||||
|
'r' => cli.recursive = true,
|
||||||
'e' | 'i' => {
|
'e' | 'i' => {
|
||||||
// Benchmark range end (-e#) and duration (-i#): like the C
|
// Benchmark range end (-e#) and duration (-i#): like the C
|
||||||
// parser, digits attach directly and default to 0.
|
// parser, digits attach directly and default to 0.
|
||||||
@@ -1008,7 +1127,7 @@ fn parse_short_options(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
'l' | 'p' | 'P' | 'r' | 's' | 'S' => {
|
'p' | 'P' | 's' | 'S' => {
|
||||||
unsupported(&format!("-{option}"))?;
|
unsupported(&format!("-{option}"))?;
|
||||||
}
|
}
|
||||||
_ => return Err(format!("unknown option -{option}")),
|
_ => return Err(format!("unknown option -{option}")),
|
||||||
@@ -1248,8 +1367,8 @@ unsafe fn run_compress(
|
|||||||
ctx,
|
ctx,
|
||||||
prefs,
|
prefs,
|
||||||
inputs.as_ptr(),
|
inputs.as_ptr(),
|
||||||
ptr::null(),
|
output_dir_mirror(cli),
|
||||||
ptr::null(),
|
output_dir_flat(cli),
|
||||||
output,
|
output,
|
||||||
ZSTD_SUFFIX.as_ptr().cast(),
|
ZSTD_SUFFIX.as_ptr().cast(),
|
||||||
dictionary,
|
dictionary,
|
||||||
@@ -1260,16 +1379,28 @@ unsafe fn run_compress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output_dir_flat(cli: &Cli) -> *const c_char {
|
||||||
|
cli.output_dir_flat
|
||||||
|
.as_ref()
|
||||||
|
.map_or(ptr::null(), |value| value.as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_dir_mirror(cli: &Cli) -> *const c_char {
|
||||||
|
cli.output_dir_mirror
|
||||||
|
.as_ref()
|
||||||
|
.map_or(ptr::null(), |value| value.as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "decompression")]
|
#[cfg(feature = "decompression")]
|
||||||
unsafe fn run_decompress(
|
unsafe fn run_decompress(
|
||||||
operation: Operation,
|
cli: &Cli,
|
||||||
ctx: *mut FIO_ctx_t,
|
ctx: *mut FIO_ctx_t,
|
||||||
prefs: *mut FIO_prefs_t,
|
prefs: *mut FIO_prefs_t,
|
||||||
inputs: &[*const c_char],
|
inputs: &[*const c_char],
|
||||||
output: *const c_char,
|
output: *const c_char,
|
||||||
dictionary: *const c_char,
|
dictionary: *const c_char,
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
match operation {
|
match cli.operation {
|
||||||
Operation::Test => {
|
Operation::Test => {
|
||||||
let null_output = cstring(NULL_MARK).expect("static null marker");
|
let null_output = cstring(NULL_MARK).expect("static null marker");
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -1278,8 +1409,8 @@ unsafe fn run_decompress(
|
|||||||
ctx,
|
ctx,
|
||||||
prefs,
|
prefs,
|
||||||
inputs.as_ptr(),
|
inputs.as_ptr(),
|
||||||
ptr::null(),
|
output_dir_mirror(cli),
|
||||||
ptr::null(),
|
output_dir_flat(cli),
|
||||||
null_output.as_ptr(),
|
null_output.as_ptr(),
|
||||||
dictionary,
|
dictionary,
|
||||||
)
|
)
|
||||||
@@ -1293,18 +1424,74 @@ unsafe fn run_decompress(
|
|||||||
ctx,
|
ctx,
|
||||||
prefs,
|
prefs,
|
||||||
inputs.as_ptr(),
|
inputs.as_ptr(),
|
||||||
ptr::null(),
|
output_dir_mirror(cli),
|
||||||
ptr::null(),
|
output_dir_flat(cli),
|
||||||
output,
|
output,
|
||||||
dictionary,
|
dictionary,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Operation::Compress | Operation::Bench => {
|
Operation::Compress | Operation::Bench | Operation::List => {
|
||||||
unreachable!("compression and benchmark are dispatched separately")
|
unreachable!("compression, benchmark, and list are dispatched separately")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copies every name out of a C `FileNamesTable` and releases the table.
|
||||||
|
unsafe fn drain_file_names_table(table: *mut FileNamesTable, into: &mut Vec<CString>) {
|
||||||
|
unsafe {
|
||||||
|
for entry in 0..(*table).tableSize {
|
||||||
|
let name = *(*table).fileNames.add(entry);
|
||||||
|
if !name.is_null() {
|
||||||
|
into.push(CStr::from_ptr(name).to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UTIL_freeFileNamesTable(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends the names read from every `--filelist` argument to the inputs.
|
||||||
|
/// As in the C CLI, a list that cannot be read (missing, irregular, empty,
|
||||||
|
/// or with over-long lines) aborts the whole run.
|
||||||
|
fn expand_file_lists(cli: &mut Cli) -> Result<(), String> {
|
||||||
|
let file_lists = std::mem::take(&mut cli.file_lists);
|
||||||
|
for list in &file_lists {
|
||||||
|
let table = unsafe { UTIL_createFileNamesTable_fromFileName(list.as_ptr()) };
|
||||||
|
if table.is_null() {
|
||||||
|
return Err(format!("error reading {}", list.to_string_lossy()));
|
||||||
|
}
|
||||||
|
unsafe { drain_file_names_table(table, &mut cli.inputs) };
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces the inputs with their recursive directory expansion. Symbolic
|
||||||
|
/// links are followed only under --force, matching the C `followLinks` flag.
|
||||||
|
fn expand_recursive_inputs(cli: &mut Cli) -> Result<(), String> {
|
||||||
|
let pointers: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
|
||||||
|
let names = if pointers.is_empty() {
|
||||||
|
ptr::null()
|
||||||
|
} else {
|
||||||
|
pointers.as_ptr()
|
||||||
|
};
|
||||||
|
let table = unsafe { UTIL_createExpandedFNT(names, pointers.len(), c_int::from(cli.force)) };
|
||||||
|
if table.is_null() {
|
||||||
|
/* The C CLI treats this allocation failure as fatal (CONTROL). */
|
||||||
|
return Err("recursive directory expansion failed".to_owned());
|
||||||
|
}
|
||||||
|
let mut expanded = Vec::new();
|
||||||
|
unsafe { drain_file_names_table(table, &mut expanded) };
|
||||||
|
cli.inputs = expanded;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints frame information for every input through `FIO_listMultipleFiles`,
|
||||||
|
/// which owns the display-level gating, the stdin refusal, and the totals.
|
||||||
|
#[cfg(feature = "decompression")]
|
||||||
|
fn run_list(cli: &Cli) -> i32 {
|
||||||
|
let inputs: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
|
||||||
|
unsafe { FIO_listMultipleFiles(inputs.len() as c_uint, inputs.as_ptr(), cli.display_level) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs benchmark mode through the C bridge. Level clamping against
|
/// Runs benchmark mode through the C bridge. Level clamping against
|
||||||
/// `ZSTD_maxCLevel()` happens on the C side, where the symbol is always
|
/// `ZSTD_maxCLevel()` happens on the C side, where the symbol is always
|
||||||
/// available when benchmarking is compiled in. No input file means a
|
/// available when benchmarking is compiled in. No input file means a
|
||||||
@@ -1342,19 +1529,45 @@ fn run_cli(mut cli: Cli) -> Result<i32, String> {
|
|||||||
"{program_name} compatibility mode is not yet implemented by the Rust CLI frontend"
|
"{program_name} compatibility mode is not yet implemented by the Rust CLI frontend"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if cli.operation == Operation::Bench {
|
unsafe {
|
||||||
return run_bench(&cli);
|
/* The C CLI publishes the display level to util.c before any table
|
||||||
|
* expansion, so traversal warnings obey -q/-v. */
|
||||||
|
g_utilDisplayLevel = cli.display_level;
|
||||||
}
|
}
|
||||||
let explicit_input_count = cli.inputs.len();
|
let explicit_input_count = cli.inputs.len();
|
||||||
filter_symlink_inputs(&mut cli);
|
filter_symlink_inputs(&mut cli);
|
||||||
if explicit_input_count > 0 && cli.inputs.is_empty() {
|
if explicit_input_count > 0 && cli.inputs.is_empty() {
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
expand_file_lists(&mut cli)?;
|
||||||
|
/* Names known before recursive expansion: an input set that named only
|
||||||
|
* (possibly empty) directories must not fall back to stdin below. */
|
||||||
|
let input_name_count = cli.inputs.len();
|
||||||
|
if cli.recursive {
|
||||||
|
expand_recursive_inputs(&mut cli)?;
|
||||||
|
}
|
||||||
|
if cli.operation == Operation::List {
|
||||||
|
#[cfg(feature = "decompression")]
|
||||||
|
return Ok(run_list(&cli));
|
||||||
|
#[cfg(not(feature = "decompression"))]
|
||||||
|
return Err("file information is not supported".to_owned());
|
||||||
|
}
|
||||||
|
if cli.operation == Operation::Bench {
|
||||||
|
return run_bench(&cli);
|
||||||
|
}
|
||||||
if cli.operation == Operation::Test {
|
if cli.operation == Operation::Test {
|
||||||
cli.output = Some(cstring(NULL_MARK)?);
|
cli.output = Some(cstring(NULL_MARK)?);
|
||||||
cli.remove_source = false;
|
cli.remove_source = false;
|
||||||
}
|
}
|
||||||
if cli.inputs.is_empty() {
|
if cli.inputs.is_empty() {
|
||||||
|
if input_name_count > 0 {
|
||||||
|
if cli.display_level >= 1 {
|
||||||
|
eprintln!(
|
||||||
|
"please provide correct input file(s) or non-empty directories -- ignored"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
cli.inputs.push(cstring(STDIN_MARK)?);
|
cli.inputs.push(cstring(STDIN_MARK)?);
|
||||||
if cli.output.is_none() {
|
if cli.output.is_none() {
|
||||||
cli.output = Some(cstring(STDOUT_MARK)?);
|
cli.output = Some(cstring(STDOUT_MARK)?);
|
||||||
@@ -1444,14 +1657,14 @@ fn run_cli(mut cli: Cli) -> Result<i32, String> {
|
|||||||
Operation::Decompress | Operation::Test => {
|
Operation::Decompress | Operation::Test => {
|
||||||
#[cfg(feature = "decompression")]
|
#[cfg(feature = "decompression")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe { run_decompress(&cli, ctx, prefs, &inputs, output, dictionary) }
|
||||||
run_decompress(cli.operation, ctx, prefs, &inputs, output, dictionary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "decompression"))]
|
#[cfg(not(feature = "decompression"))]
|
||||||
unreachable!("unsupported decompression was rejected above")
|
unreachable!("unsupported decompression was rejected above")
|
||||||
}
|
}
|
||||||
Operation::Bench => unreachable!("benchmark mode was dispatched earlier"),
|
Operation::Bench | Operation::List => {
|
||||||
|
unreachable!("benchmark and list modes were dispatched earlier")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1687,6 +1900,96 @@ mod tests {
|
|||||||
assert!(error.contains("not yet implemented"));
|
assert!(error.contains("not yet implemented"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recursive_flag_can_be_aggregated_with_other_shorts() {
|
||||||
|
let cli = parse(&["zstd", "-rq", "dir"]);
|
||||||
|
|
||||||
|
assert!(cli.recursive);
|
||||||
|
assert_eq!(cli.display_level, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_mode_is_selected_by_short_and_long_flags() {
|
||||||
|
let short = parse(&["zstd", "-l", "file.zst"]);
|
||||||
|
let long = parse(&["zstd", "--list", "file.zst"]);
|
||||||
|
|
||||||
|
assert_eq!(short.operation, Operation::List);
|
||||||
|
assert_eq!(long.operation, Operation::List);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_rejects_attached_values() {
|
||||||
|
let error = parse_args(vec![OsString::from("zstd"), OsString::from("--list=x")])
|
||||||
|
.expect_err("an attached value must not activate --list");
|
||||||
|
|
||||||
|
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"]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cli.file_lists
|
||||||
|
.iter()
|
||||||
|
.map(|list| list.as_bytes())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![&b"one.txt"[..], &b"two.txt"[..]]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filelist_argument_must_not_be_another_option() {
|
||||||
|
let error = parse_args(vec![
|
||||||
|
OsString::from("zstd"),
|
||||||
|
OsString::from("--filelist"),
|
||||||
|
OsString::from("-q"),
|
||||||
|
])
|
||||||
|
.expect_err("an option must not be consumed as the list name");
|
||||||
|
|
||||||
|
assert!(error.contains("cannot be separated from its argument"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_dir_flat_accepts_attached_and_separate_values() {
|
||||||
|
let attached = parse(&["zstd", "--output-dir-flat=out", "in"]);
|
||||||
|
let separate = parse(&["zstd", "--output-dir-flat", "out", "in"]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
attached.output_dir_flat.as_deref().map(CStr::to_bytes),
|
||||||
|
Some(&b"out"[..])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
separate.output_dir_flat.as_deref().map(CStr::to_bytes),
|
||||||
|
Some(&b"out"[..])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_dir_flat_rejects_an_empty_name() {
|
||||||
|
/* Mirrors the C NEXT_FIELD semantics: `--output-dir-flat=` keeps the
|
||||||
|
* empty attached value instead of consuming the next argument. */
|
||||||
|
let error = parse_args(vec![
|
||||||
|
OsString::from("zstd"),
|
||||||
|
OsString::from("--output-dir-flat="),
|
||||||
|
OsString::from("out"),
|
||||||
|
])
|
||||||
|
.expect_err("an empty output dir must be rejected");
|
||||||
|
|
||||||
|
assert!(error.contains("output dir cannot be empty string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn output_dir_mirror_is_parsed_on_posix_targets() {
|
||||||
|
let cli = parse(&["zstd", "--output-dir-mirror", "out", "in"]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cli.output_dir_mirror.as_deref().map(CStr::to_bytes),
|
||||||
|
Some(&b"out"[..])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bench_mode_parses_level_duration_and_defaults() {
|
fn bench_mode_parses_level_duration_and_defaults() {
|
||||||
let cli = parse(&["zstd", "-b1", "-i0", "input"]);
|
let cli = parse(&["zstd", "-b1", "-i0", "input"]);
|
||||||
|
|||||||
Reference in New Issue
Block a user