feat(cli): restore patch-from argument routing
Parse attached and separated --patch-from references, preserve the upstream ultra ceiling and conflict checks, and pass the reference path through the existing file-I/O dictionary ABI with patch mode enabled. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets - cargo +nightly fmt --manifest-path rust/Cargo.toml - make -C tests -j2 check - small attached/separate patch-from round-trip and rejection smoke
This commit is contained in:
+61
-12
@@ -406,6 +406,7 @@ struct Cli {
|
||||
inputs: Vec<CString>,
|
||||
output: Option<CString>,
|
||||
dictionary: Option<CString>,
|
||||
patch_from: Option<CString>,
|
||||
level: i32,
|
||||
ultra: bool,
|
||||
display_level: i32,
|
||||
@@ -470,6 +471,7 @@ impl Cli {
|
||||
inputs: Vec::new(),
|
||||
output: None,
|
||||
dictionary: None,
|
||||
patch_from: None,
|
||||
level: default_level(),
|
||||
ultra: false,
|
||||
display_level: 2,
|
||||
@@ -1713,7 +1715,16 @@ fn parse_long_option(
|
||||
cli.trace_file_stat = true;
|
||||
Ok(None)
|
||||
}
|
||||
"--patch-from" | "--priority" | "--fake-stdin-is-console" | "--fake-stdout-is-console" => {
|
||||
"--patch-from" => {
|
||||
if cli.dictionary.is_some() {
|
||||
return Err("can't use -D and --patch-from=# at the same time".to_owned());
|
||||
}
|
||||
let value = next_field(attached, args, index, name)?;
|
||||
cli.patch_from = Some(cstring(&value)?);
|
||||
cli.ultra = true;
|
||||
Ok(None)
|
||||
}
|
||||
"--priority" | "--fake-stdin-is-console" | "--fake-stdout-is-console" => {
|
||||
unsupported(name)?;
|
||||
Ok(None)
|
||||
}
|
||||
@@ -1804,7 +1815,14 @@ fn parse_short_options(
|
||||
.unwrap_or_else(|| next_os_value(args, index, &format!("-{option}")))?;
|
||||
match option {
|
||||
'o' => cli.output = Some(os_cstring(&argument)?),
|
||||
'D' => cli.dictionary = Some(os_cstring(&argument)?),
|
||||
'D' => {
|
||||
if cli.patch_from.is_some() {
|
||||
return Err(
|
||||
"can't use -D and --patch-from=# at the same time".to_owned()
|
||||
);
|
||||
}
|
||||
cli.dictionary = Some(os_cstring(&argument)?);
|
||||
}
|
||||
'T' => cli.workers = Some(parse_worker_count(&argument.to_string_lossy())?),
|
||||
'M' => {
|
||||
cli.mem_limit =
|
||||
@@ -2004,10 +2022,7 @@ unsafe fn apply_preferences(cli: &Cli, prefs: *mut FIO_prefs_t, ctx: *mut FIO_ct
|
||||
if let Some(value) = cli.pass_through {
|
||||
FIO_setPassThroughFlag(prefs, value);
|
||||
}
|
||||
/* `FIO_createPreferences()` leaves this field unspecified. The Rust
|
||||
* frontend does not yet parse --patch-from, so explicitly keep the
|
||||
* C file-I/O backend in ordinary dictionary mode. */
|
||||
FIO_setPatchFromMode(prefs, 0);
|
||||
FIO_setPatchFromMode(prefs, i32::from(cli.patch_from.is_some()));
|
||||
FIO_setContentSize(prefs, cli.content_size);
|
||||
if let Some(value) = cli.dict_id {
|
||||
FIO_setDictIDFlag(prefs, value);
|
||||
@@ -2065,6 +2080,10 @@ fn is_stdin(value: &CString) -> bool {
|
||||
value.as_bytes() == STDIN_MARK.as_bytes()
|
||||
}
|
||||
|
||||
fn fileio_dictionary(cli: &Cli) -> Option<&CString> {
|
||||
cli.patch_from.as_ref().or(cli.dictionary.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_non_fifo_symlink(input: &CString) -> bool {
|
||||
if unsafe { UTIL_isLink(input.as_ptr()) } == 0 {
|
||||
@@ -2138,7 +2157,7 @@ fn check_terminal_safety(cli: &Cli) -> Result<(), String> {
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
unsafe fn print_default_cparams(cli: &Cli) {
|
||||
let dict_size = cli.dictionary.as_ref().map_or(0, |dictionary| unsafe {
|
||||
let dict_size = fileio_dictionary(cli).map_or(0, |dictionary| unsafe {
|
||||
UTIL_getFileSize(dictionary.as_ptr()) as usize
|
||||
});
|
||||
for input in &cli.inputs {
|
||||
@@ -2165,7 +2184,7 @@ unsafe fn print_default_cparams(cli: &Cli) {
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
unsafe fn print_actual_cparams(cli: &Cli) {
|
||||
let dict_size = cli.dictionary.as_ref().map_or(0, |dictionary| unsafe {
|
||||
let dict_size = fileio_dictionary(cli).map_or(0, |dictionary| unsafe {
|
||||
UTIL_getFileSize(dictionary.as_ptr()) as usize
|
||||
});
|
||||
for input in &cli.inputs {
|
||||
@@ -2746,6 +2765,10 @@ fn run_cli(mut cli: Cli) -> Result<i32, String> {
|
||||
return Err("Decompression not supported".to_owned());
|
||||
}
|
||||
|
||||
if cli.patch_from.is_some() && cli.inputs.len() > 1 {
|
||||
return Err("can't use --patch-from=# on multiple files".to_owned());
|
||||
}
|
||||
|
||||
let prefs = unsafe { FIO_createPreferences() };
|
||||
let ctx = unsafe { FIO_createContext() };
|
||||
if prefs.is_null() || ctx.is_null() {
|
||||
@@ -2778,10 +2801,7 @@ fn run_cli(mut cli: Cli) -> Result<i32, String> {
|
||||
.output
|
||||
.as_ref()
|
||||
.map_or(ptr::null(), |value| value.as_ptr());
|
||||
let dictionary = cli
|
||||
.dictionary
|
||||
.as_ref()
|
||||
.map_or(ptr::null(), |value| value.as_ptr());
|
||||
let dictionary = fileio_dictionary(&cli).map_or(ptr::null(), |value| value.as_ptr());
|
||||
let inputs: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
|
||||
match cli.operation {
|
||||
Operation::Compress => {
|
||||
@@ -3037,6 +3057,35 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch_from_accepts_attached_and_separate_reference_paths() {
|
||||
let attached = parse(&["zstd", "--patch-from=reference", "input"]);
|
||||
assert_eq!(
|
||||
attached.patch_from.as_deref().map(CStr::to_bytes),
|
||||
Some(&b"reference"[..])
|
||||
);
|
||||
assert!(attached.ultra);
|
||||
|
||||
let separate = parse(&["zstd", "--patch-from", "reference", "input"]);
|
||||
assert_eq!(
|
||||
separate.patch_from.as_deref().map(CStr::to_bytes),
|
||||
Some(&b"reference"[..])
|
||||
);
|
||||
assert!(separate.ultra);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch_from_rejects_conflicts_with_dictionary_mode_in_both_orders() {
|
||||
for values in [
|
||||
&["zstd", "-D", "dict", "--patch-from=reference", "input"][..],
|
||||
&["zstd", "--patch-from", "reference", "-D", "dict", "input"][..],
|
||||
] {
|
||||
let args = values.iter().map(OsString::from).collect();
|
||||
let error = parse_args(args).expect_err("-D and --patch-from must conflict");
|
||||
assert!(error.contains("can't use -D and --patch-from=# at the same time"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_selection_does_not_enable_force_or_pass_through() {
|
||||
let cli = parse(&["zstd", "-c", "input"]);
|
||||
|
||||
Reference in New Issue
Block a user