diff --git a/programs/fileio.c b/programs/fileio.c index 22fc28025..9f79b5c57 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -286,6 +286,23 @@ typedef char FIO_rust_ctx_size[ int FIO_rust_shouldDisplayFileSummary(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) { 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) { - if (fCtx->hasStdoutOutput) { - if (prefs->removeSrcFile) - /* 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 (prefs->testMode) { - if (prefs->removeSrcFile) - /* 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."); - return 0; - } + int action = FIO_rust_multiFilesConcatAction( + fCtx->nbFilesTotal, fCtx->hasStdoutOutput, prefs->testMode, + outFileName != NULL, prefs->removeSrcFile, prefs->overwrite, + g_display_prefs.displayLevel, displayLevelCutoff); + + 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."); if (fCtx->nbFilesTotal == 1) return 0; assert(fCtx->nbFilesTotal > 1); - if (!outFileName) return 0; + if (!outFileName || prefs->testMode) return 0; if (fCtx->hasStdoutOutput) { 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") /* 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"); 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 (prefs->overwrite) return 0; + if (action == FIO_RUST_MULTI_FILES_ACTION_PROCEED) return 0; /* 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 */ 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); } diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index 7c2002d2a..101db5fba 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -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_UNKNOWN_SIZE: c_int = 1; 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 ZSTD_WINDOWLOG_MAX: u32 = if size_of::() == 4 { 30 } else { 31 }; 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) } +#[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(sizes: I) -> u64 where I: IntoIterator, @@ -1112,6 +1179,72 @@ mod tests { 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] fn buffer_shims_preserve_fields_and_c_layout() { let input_word = size_of::<*const c_void>();