feat(cli): move multi-file output policy to Rust
Port the scalar decision matrix for multi-input concatenation behind the CLI Rust ABI. C retains warnings, preference mutation, fatal diagnostics, and confirmation prompts; Rust now classifies protection, remove-file disabling, quiet abort, and confirmation paths with focused coverage. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml fileio_prefs::tests::multi_files_concat - cargo clippy --manifest-path rust/cli/Cargo.toml - cargo clippy --manifest-path rust/cli/Cargo.toml --benches - cargo clippy --manifest-path rust/cli/Cargo.toml --tests - cargo +nightly fmt --manifest-path rust/Cargo.toml --all - make -B -C programs -j2 zstd - make -C tests -j2 test-cli-tests
This commit is contained in:
+43
-22
@@ -286,6 +286,23 @@ typedef char FIO_rust_ctx_size[
|
|||||||
int FIO_rust_shouldDisplayFileSummary(const FIO_ctx_t* fCtx);
|
int FIO_rust_shouldDisplayFileSummary(const FIO_ctx_t* fCtx);
|
||||||
int FIO_rust_shouldDisplayMultipleFileSummary(const FIO_ctx_t* fCtx);
|
int FIO_rust_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);
|
||||||
|
|
||||||
static int FIO_shouldDisplayFileSummary(FIO_ctx_t const* fCtx)
|
static int FIO_shouldDisplayFileSummary(FIO_ctx_t const* fCtx)
|
||||||
{
|
{
|
||||||
return FIO_rust_shouldDisplayFileSummary(fCtx);
|
return FIO_rust_shouldDisplayFileSummary(fCtx);
|
||||||
@@ -761,28 +778,28 @@ static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs,
|
|||||||
*/
|
*/
|
||||||
static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs, const char* outFileName, int displayLevelCutoff)
|
static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs, const char* outFileName, int displayLevelCutoff)
|
||||||
{
|
{
|
||||||
if (fCtx->hasStdoutOutput) {
|
int action = FIO_rust_multiFilesConcatAction(
|
||||||
if (prefs->removeSrcFile)
|
fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode,
|
||||||
/* this should not happen ; hard fail, to protect user's data
|
outFileName != NULL, prefs->removeSrcFile, prefs->overwrite,
|
||||||
* 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 */
|
g_display_prefs.displayLevel, displayLevelCutoff);
|
||||||
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. "
|
if (action == FIO_RUST_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE)
|
||||||
"This is a programming error. File an issue for it to be fixed.");
|
/* 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 */
|
||||||
if (prefs->testMode) {
|
EXM_THROW(43, "It's not allowed to remove input files when processed output is piped to stdout. "
|
||||||
if (prefs->removeSrcFile)
|
"This scenario is not supposed to be possible. "
|
||||||
/* this should not happen ; hard fail, to protect user's data
|
"This is a programming error. File an issue for it to be fixed.");
|
||||||
* 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 */
|
if (action == FIO_RUST_MULTI_FILES_ACTION_FATAL_TEST_REMOVE)
|
||||||
EXM_THROW(43, "Test mode shall not remove input files! "
|
/* this should not happen ; hard fail, to protect user's data
|
||||||
"This scenario is not supposed to be possible. "
|
* 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 */
|
||||||
"This is a programming error. File an issue for it to be fixed.");
|
EXM_THROW(43, "Test mode shall not remove input files! "
|
||||||
return 0;
|
"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;
|
if (fCtx->nbFilesTotal == 1) return 0;
|
||||||
assert(fCtx->nbFilesTotal > 1);
|
assert(fCtx->nbFilesTotal > 1);
|
||||||
|
|
||||||
if (!outFileName) return 0;
|
if (!outFileName || prefs->testMode) return 0;
|
||||||
|
|
||||||
if (fCtx->hasStdoutOutput) {
|
if (fCtx->hasStdoutOutput) {
|
||||||
DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. \n");
|
DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. \n");
|
||||||
@@ -792,22 +809,26 @@ static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs
|
|||||||
DISPLAYLEVEL(2, "The concatenated output CANNOT regenerate original file names nor directory structure. \n")
|
DISPLAYLEVEL(2, "The concatenated output CANNOT regenerate original file names nor directory structure. \n")
|
||||||
|
|
||||||
/* multi-input into single output : --rm is not allowed */
|
/* multi-input into single output : --rm is not allowed */
|
||||||
if (prefs->removeSrcFile) {
|
if (action == FIO_RUST_MULTI_FILES_ACTION_DISABLE_REMOVE) {
|
||||||
DISPLAYLEVEL(2, "Since it's a destructive operation, input files will not be removed. \n");
|
DISPLAYLEVEL(2, "Since it's a destructive operation, input files will not be removed. \n");
|
||||||
prefs->removeSrcFile = 0;
|
prefs->removeSrcFile = 0;
|
||||||
|
action = FIO_rust_multiFilesConcatAction(
|
||||||
|
fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode,
|
||||||
|
1, 0, prefs->overwrite, g_display_prefs.displayLevel,
|
||||||
|
displayLevelCutoff);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fCtx->hasStdoutOutput) return 0;
|
if (action == FIO_RUST_MULTI_FILES_ACTION_PROCEED) return 0;
|
||||||
if (prefs->overwrite) return 0;
|
|
||||||
|
|
||||||
/* multiple files concatenated into single destination file using -o without -f */
|
/* multiple files concatenated into single destination file using -o without -f */
|
||||||
if (g_display_prefs.displayLevel <= displayLevelCutoff) {
|
if (action == FIO_RUST_MULTI_FILES_ACTION_QUIET_ABORT) {
|
||||||
/* quiet mode => no prompt => fail automatically */
|
/* quiet mode => no prompt => fail automatically */
|
||||||
DISPLAYLEVEL(1, "Concatenating multiple processed inputs into a single output loses file metadata. \n");
|
DISPLAYLEVEL(1, "Concatenating multiple processed inputs into a single output loses file metadata. \n");
|
||||||
DISPLAYLEVEL(1, "Aborting. \n");
|
DISPLAYLEVEL(1, "Aborting. \n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* normal mode => prompt */
|
/* normal mode => prompt */
|
||||||
|
assert(action == FIO_RUST_MULTI_FILES_ACTION_CONFIRM);
|
||||||
return UTIL_requireUserConfirmation("Proceed? (y/n): ", "Aborting...", "yY", fCtx->hasStdinInput);
|
return UTIL_requireUserConfirmation("Proceed? (y/n): ", "Aborting...", "yY", fCtx->hasStdinInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ const FIO_LDM_PARAM_NOTSET: c_int = 9999;
|
|||||||
const FIO_PATCH_MEM_LIMIT_SUCCESS: c_int = 0;
|
const FIO_PATCH_MEM_LIMIT_SUCCESS: c_int = 0;
|
||||||
const FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE: c_int = 1;
|
const FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE: c_int = 1;
|
||||||
const FIO_PATCH_MEM_LIMIT_TOO_LARGE: c_int = 2;
|
const FIO_PATCH_MEM_LIMIT_TOO_LARGE: c_int = 2;
|
||||||
|
const FIO_MULTI_FILES_ACTION_PROCEED: c_int = 0;
|
||||||
|
const FIO_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE: c_int = 1;
|
||||||
|
const FIO_MULTI_FILES_ACTION_FATAL_TEST_REMOVE: c_int = 2;
|
||||||
|
const FIO_MULTI_FILES_ACTION_DISABLE_REMOVE: c_int = 3;
|
||||||
|
const FIO_MULTI_FILES_ACTION_QUIET_ABORT: c_int = 4;
|
||||||
|
const FIO_MULTI_FILES_ACTION_CONFIRM: c_int = 5;
|
||||||
const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
|
const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
|
||||||
const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 };
|
const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 };
|
||||||
const ZSTD_BTLAZY2: c_int = 6;
|
const ZSTD_BTLAZY2: c_int = 6;
|
||||||
@@ -603,6 +609,67 @@ pub extern "C" fn FIO_rust_cycleLog(hash_log: c_uint, strategy: c_int) -> c_uint
|
|||||||
cycle_log(hash_log, strategy)
|
cycle_log(hash_log, strategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[inline]
|
||||||
|
fn multi_files_concat_action(
|
||||||
|
nb_files_total: c_int,
|
||||||
|
has_stdout_output: c_int,
|
||||||
|
test_mode: c_int,
|
||||||
|
has_output_file: c_int,
|
||||||
|
remove_src_file: c_int,
|
||||||
|
overwrite: c_int,
|
||||||
|
display_level: c_int,
|
||||||
|
display_level_cutoff: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
if has_stdout_output != 0 && remove_src_file != 0 {
|
||||||
|
return FIO_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE;
|
||||||
|
}
|
||||||
|
if test_mode != 0 && remove_src_file != 0 {
|
||||||
|
return FIO_MULTI_FILES_ACTION_FATAL_TEST_REMOVE;
|
||||||
|
}
|
||||||
|
if test_mode != 0 || nb_files_total <= 1 || has_output_file == 0 {
|
||||||
|
return FIO_MULTI_FILES_ACTION_PROCEED;
|
||||||
|
}
|
||||||
|
if remove_src_file != 0 {
|
||||||
|
return FIO_MULTI_FILES_ACTION_DISABLE_REMOVE;
|
||||||
|
}
|
||||||
|
if has_stdout_output != 0 || overwrite != 0 {
|
||||||
|
return FIO_MULTI_FILES_ACTION_PROCEED;
|
||||||
|
}
|
||||||
|
if display_level <= display_level_cutoff {
|
||||||
|
FIO_MULTI_FILES_ACTION_QUIET_ABORT
|
||||||
|
} else {
|
||||||
|
FIO_MULTI_FILES_ACTION_CONFIRM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select the warning/prompt action for multiple inputs sharing one output.
|
||||||
|
///
|
||||||
|
/// C retains the user-facing diagnostics, preference mutation, and prompt;
|
||||||
|
/// Rust only evaluates the scalar policy and returns the action enum.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn FIO_rust_multiFilesConcatAction(
|
||||||
|
nb_files_total: c_int,
|
||||||
|
has_stdout_output: c_int,
|
||||||
|
test_mode: c_int,
|
||||||
|
has_output_file: c_int,
|
||||||
|
remove_src_file: c_int,
|
||||||
|
overwrite: c_int,
|
||||||
|
display_level: c_int,
|
||||||
|
display_level_cutoff: c_int,
|
||||||
|
) -> c_int {
|
||||||
|
multi_files_concat_action(
|
||||||
|
nb_files_total,
|
||||||
|
has_stdout_output,
|
||||||
|
test_mode,
|
||||||
|
has_output_file,
|
||||||
|
remove_src_file,
|
||||||
|
overwrite,
|
||||||
|
display_level,
|
||||||
|
display_level_cutoff,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn largest_file_size<I>(sizes: I) -> u64
|
fn largest_file_size<I>(sizes: I) -> u64
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = u64>,
|
I: IntoIterator<Item = u64>,
|
||||||
@@ -1112,6 +1179,72 @@ mod tests {
|
|||||||
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 + 3), 19);
|
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 + 3), 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_files_concat_policy_protects_stdout_and_test_mode_removal() {
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 1, 0, 1, 1, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_FATAL_STDOUT_REMOVE
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 1, 1, 1, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_FATAL_TEST_REMOVE
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 1, 1, 0, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_PROCEED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_files_concat_policy_keeps_nonconcatenating_paths_quiet() {
|
||||||
|
for (nb_files, has_output_file) in [(1, 1), (3, 0)] {
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(nb_files, 0, 0, has_output_file, 0, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_PROCEED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_files_concat_policy_disables_remove_before_other_decisions() {
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 0, 1, 1, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_DISABLE_REMOVE
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 0, 1, 0, 0, 1, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_QUIET_ABORT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_files_concat_policy_allows_stdout_and_overwrite() {
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 1, 0, 1, 0, 0, 0, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_PROCEED
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 0, 1, 0, 1, 0, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_PROCEED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_files_concat_policy_uses_quiet_and_confirmation_boundaries() {
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 0, 1, 0, 0, 1, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_QUIET_ABORT
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
multi_files_concat_action(3, 0, 0, 1, 0, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_CONFIRM
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FIO_rust_multiFilesConcatAction(3, 0, 0, 1, 0, 0, 2, 1),
|
||||||
|
FIO_MULTI_FILES_ACTION_CONFIRM
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn buffer_shims_preserve_fields_and_c_layout() {
|
fn buffer_shims_preserve_fields_and_c_layout() {
|
||||||
let input_word = size_of::<*const c_void>();
|
let input_word = size_of::<*const c_void>();
|
||||||
|
|||||||
Reference in New Issue
Block a user