refactor(fileio): move concatenation policy ordering to Rust

Move the multi-input single-output decision sequence out of
FIO_multiFilesConcatWarning while preserving the C-owned CLI boundary. The
previous wrapper classified fatal remove cases, emitted the concatenation
warning, disabled --rm, reclassified the action, and then selected quiet
abort or confirmation in C. Add a repr(C) callback projection so Rust owns
only that scalar ordering while C continues to own exact diagnostics, the
confirmation prompt, FIO_prefs_t mutation, and all private state.

The Rust bridge re-runs the action after the C disable-remove callback with
the same has-output/remove arguments as the original wrapper. C and Rust
assert the callback layout, and focused tests cover callback order,
fatal/quiet paths, and ABI offsets. Existing scalar action tests remain in
place.

Test Plan:
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -B -C programs -j1 fileio.o`
  — passed; only pre-existing suffixList C++-compat warnings appeared.
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path
  rust/cli/Cargo.toml --tests --no-deps` — passed.
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 RUSTFLAGS='-C
  link-arg=/tmp/zstd_rust_test_bridges.o' cargo test --manifest-path
  rust/cli/Cargo.toml --lib 'fileio_prefs::tests::multi_files_concat_'`
  — 8 passed.
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo build --manifest-path
  rust/cli/Cargo.toml --lib` — passed; the archive exports
  `FIO_rust_multiFilesConcatWarning`.
- `cargo +nightly fmt --manifest-path rust/cli/Cargo.toml -- --check` — not
  clean due pre-existing formatting drift in unchanged Rust code; no bulk
  formatting was applied.
- Full native/upstream/fuzzer suites were not run by request.
This commit is contained in:
2026-07-21 10:17:54 +02:00
parent af91fb23ab
commit 5948934bd9
2 changed files with 436 additions and 60 deletions
+129 -60
View File
@@ -292,22 +292,62 @@ typedef char FIO_rust_compression_params_size[
int FIO_shouldDisplayFileSummary(const FIO_ctx_t* fCtx);
int FIO_shouldDisplayMultipleFileSummary(const FIO_ctx_t* fCtx);
enum {
FIO_RUST_MULTI_FILES_ACTION_PROCEED = 0,
FIO_RUST_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE = 1,
FIO_RUST_MULTI_FILES_ACTION_FATAL_TEST_REMOVE = 2,
FIO_RUST_MULTI_FILES_ACTION_DISABLE_REMOVE = 3,
FIO_RUST_MULTI_FILES_ACTION_QUIET_ABORT = 4,
FIO_RUST_MULTI_FILES_ACTION_CONFIRM = 5
};
int FIO_rust_multiFilesConcatAction(int nbFilesTotal,
int hasStdoutOutput,
int testMode,
int hasOutputFile,
int removeSrcFile,
int overwrite,
int displayLevel,
int displayLevelCutoff);
typedef void (*FIO_rust_multi_files_concat_fatal_fn)(void* opaque);
typedef void (*FIO_rust_multi_files_concat_warning_fn)(void* opaque,
int hasStdoutOutput,
const char* outFileName);
typedef void (*FIO_rust_multi_files_concat_disable_remove_fn)(void* opaque);
typedef void (*FIO_rust_multi_files_concat_quiet_abort_fn)(void* opaque);
typedef int (*FIO_rust_multi_files_concat_confirmation_fn)(void* opaque,
int hasStdinInput);
typedef struct {
void* opaque;
FIO_rust_multi_files_concat_fatal_fn fatalStdoutRemove;
FIO_rust_multi_files_concat_fatal_fn fatalTestRemove;
FIO_rust_multi_files_concat_warning_fn displayWarning;
FIO_rust_multi_files_concat_disable_remove_fn disableRemove;
FIO_rust_multi_files_concat_quiet_abort_fn displayQuietAbort;
FIO_rust_multi_files_concat_confirmation_fn requireConfirmation;
} FIO_rust_multi_files_concat_callbacks_t;
typedef char FIO_rust_multi_files_concat_callback_sizes[
(sizeof(FIO_rust_multi_files_concat_fatal_fn) == sizeof(void*)
&& sizeof(FIO_rust_multi_files_concat_warning_fn) == sizeof(void*)
&& sizeof(FIO_rust_multi_files_concat_disable_remove_fn) == sizeof(void*)
&& sizeof(FIO_rust_multi_files_concat_quiet_abort_fn) == sizeof(void*)
&& sizeof(FIO_rust_multi_files_concat_confirmation_fn) == sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_opaque_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, opaque) == 0) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_fatal_stdout_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, fatalStdoutRemove)
== sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_fatal_test_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, fatalTestRemove)
== 2 * sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_warning_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, displayWarning)
== 3 * sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_disable_remove_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, disableRemove)
== 4 * sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_quiet_abort_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, displayQuietAbort)
== 5 * sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_confirmation_offset[
(offsetof(FIO_rust_multi_files_concat_callbacks_t, requireConfirmation)
== 6 * sizeof(void*)) ? 1 : -1];
typedef char FIO_rust_multi_files_concat_callbacks_size[
(sizeof(FIO_rust_multi_files_concat_callbacks_t) == 7 * sizeof(void*)) ? 1 : -1];
int FIO_rust_multiFilesConcatWarning(int nbFilesTotal,
int hasStdoutOutput,
int testMode,
int hasOutputFile,
int removeSrcFile,
int overwrite,
int displayLevel,
int displayLevelCutoff,
int hasStdinInput,
const char* outFileName,
const FIO_rust_multi_files_concat_callbacks_t* callbacks);
/*-*************************************
* Parameters: Initialization
@@ -1087,7 +1127,8 @@ static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs,
}
/* FIO_multiFilesConcatWarning() :
* This function handles logic when processing multiple files with -o or -c, displaying the appropriate warnings/prompts.
* This function supplies the C-owned diagnostics and prompt callbacks for the
* Rust policy/order wrapper used when processing multiple files with -o or -c.
* Returns 1 if the console should abort, 0 if console should proceed.
*
* If output is stdout or test mode is active, check that `--rm` disabled.
@@ -1101,60 +1142,88 @@ static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs,
* If -f is specified or if output is stdout, just proceed.
* If output is set with -o, prompt for confirmation.
*/
static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs, const char* outFileName, int displayLevelCutoff)
typedef struct {
const FIO_ctx_t* fCtx;
FIO_prefs_t* prefs;
} FIO_multiFilesConcatCallbackContext_t;
static void
FIO_multiFilesConcatFatalStdoutRemove(void* opaque)
{
int action = FIO_rust_multiFilesConcatAction(
fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode,
outFileName != NULL, prefs->removeSrcFile, prefs->overwrite,
g_display_prefs.displayLevel, displayLevelCutoff);
(void)opaque;
/* this should not happen ; hard fail, to protect user's data
* note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
EXM_THROW(43, "It's not allowed to remove input files when processed output is piped to stdout. "
"This scenario is not supposed to be possible. "
"This is a programming error. File an issue for it to be fixed.");
}
if (action == FIO_RUST_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE)
/* this should not happen ; hard fail, to protect user's data
* note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
EXM_THROW(43, "It's not allowed to remove input files when processed output is piped to stdout. "
"This scenario is not supposed to be possible. "
"This is a programming error. File an issue for it to be fixed.");
if (action == FIO_RUST_MULTI_FILES_ACTION_FATAL_TEST_REMOVE)
/* this should not happen ; hard fail, to protect user's data
* note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
EXM_THROW(43, "Test mode shall not remove input files! "
"This scenario is not supposed to be possible. "
"This is a programming error. File an issue for it to be fixed.");
static void
FIO_multiFilesConcatFatalTestRemove(void* opaque)
{
(void)opaque;
/* this should not happen ; hard fail, to protect user's data
* note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
EXM_THROW(43, "Test mode shall not remove input files! "
"This scenario is not supposed to be possible. "
"This is a programming error. File an issue for it to be fixed.");
}
if (fCtx->nbFilesTotal == 1) return 0;
assert(fCtx->nbFilesTotal > 1);
if (!outFileName || prefs->testMode) return 0;
if (fCtx->hasStdoutOutput) {
static void
FIO_multiFilesConcatDisplayWarning(void* opaque, int hasStdoutOutput,
const char* outFileName)
{
(void)opaque;
if (hasStdoutOutput) {
DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. \n");
} else {
DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into a single output file: %s \n", outFileName);
}
DISPLAYLEVEL(2, "The concatenated output CANNOT regenerate original file names nor directory structure. \n")
}
/* multi-input into single output : --rm is not allowed */
if (action == FIO_RUST_MULTI_FILES_ACTION_DISABLE_REMOVE) {
DISPLAYLEVEL(2, "Since it's a destructive operation, input files will not be removed. \n");
prefs->removeSrcFile = 0;
action = FIO_rust_multiFilesConcatAction(
fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode,
1, 0, prefs->overwrite, g_display_prefs.displayLevel,
displayLevelCutoff);
}
if (action == FIO_RUST_MULTI_FILES_ACTION_PROCEED) return 0;
static void
FIO_multiFilesConcatDisableRemove(void* opaque)
{
FIO_multiFilesConcatCallbackContext_t* const context =
(FIO_multiFilesConcatCallbackContext_t*)opaque;
DISPLAYLEVEL(2, "Since it's a destructive operation, input files will not be removed. \n");
context->prefs->removeSrcFile = 0;
}
static void
FIO_multiFilesConcatDisplayQuietAbort(void* opaque)
{
(void)opaque;
/* multiple files concatenated into single destination file using -o without -f */
if (action == FIO_RUST_MULTI_FILES_ACTION_QUIET_ABORT) {
/* quiet mode => no prompt => fail automatically */
DISPLAYLEVEL(1, "Concatenating multiple processed inputs into a single output loses file metadata. \n");
DISPLAYLEVEL(1, "Aborting. \n");
return 1;
}
/* normal mode => prompt */
assert(action == FIO_RUST_MULTI_FILES_ACTION_CONFIRM);
return UTIL_requireUserConfirmation("Proceed? (y/n): ", "Aborting...", "yY", fCtx->hasStdinInput);
/* quiet mode => no prompt => fail automatically */
DISPLAYLEVEL(1, "Concatenating multiple processed inputs into a single output loses file metadata. \n");
DISPLAYLEVEL(1, "Aborting. \n");
}
static int
FIO_multiFilesConcatRequireConfirmation(void* opaque, int hasStdinInput)
{
(void)opaque;
return UTIL_requireUserConfirmation("Proceed? (y/n): ", "Aborting...", "yY", hasStdinInput);
}
static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs, const char* outFileName, int displayLevelCutoff)
{
FIO_multiFilesConcatCallbackContext_t context = { fCtx, prefs };
FIO_rust_multi_files_concat_callbacks_t callbacks;
callbacks.opaque = &context;
callbacks.fatalStdoutRemove = FIO_multiFilesConcatFatalStdoutRemove;
callbacks.fatalTestRemove = FIO_multiFilesConcatFatalTestRemove;
callbacks.displayWarning = FIO_multiFilesConcatDisplayWarning;
callbacks.disableRemove = FIO_multiFilesConcatDisableRemove;
callbacks.displayQuietAbort = FIO_multiFilesConcatDisplayQuietAbort;
callbacks.requireConfirmation = FIO_multiFilesConcatRequireConfirmation;
return FIO_rust_multiFilesConcatWarning(
fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode,
outFileName != NULL, prefs->removeSrcFile, prefs->overwrite,
g_display_prefs.displayLevel, displayLevelCutoff, fCtx->hasStdinInput,
outFileName, &callbacks);
}
#ifndef ZSTD_NOCOMPRESS