diff --git a/programs/fileio.c b/programs/fileio.c index 61a512e82..787ab7cc6 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -334,6 +334,16 @@ int FIO_rust_adjustParamsForPatchFromMode(FIO_prefs_t* prefs, const char* FIO_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix); const char* FIO_rust_determineDstName(const char* srcFileName, const char* outDirName, const char* const* suffixList, const char* suffixListStr); +enum { + FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO = 0, + FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES = 1 +}; +typedef char FIO_rust_compress_dst_transfer_stat_values[ + (FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO == 0 + && FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES == 1) ? 1 : -1]; +int FIO_rust_compressDestinationTransferStat(int sourceIsStdin, + int destinationIsStdout, + int sourceIsRegular); int FIO_rust_adjustMemLimitForPatchFromMode(FIO_prefs_t* prefs, unsigned long long dictSize, unsigned long long maxSrcFileSize); @@ -3258,8 +3268,9 @@ static void FIO_rust_compressDestinationRemove(void* opaque, /*! FIO_compressFilename_dstFile() : * open the destination, or pass through if the write pool already owns it, * then start compression with FIO_compressFilename_internal(). - * Rust owns the lifecycle ordering; C retains opaque resources, diagnostics, - * metadata operations, and format-specific compression callbacks. + * Rust owns the lifecycle ordering and scalar metadata-transfer policy; C + * retains opaque resources, the private stat probe, metadata operations, and + * format-specific compression callbacks. * note : ress.readCtx must already have a source file attached, * so reach this function through FIO_compressFilename_srcFile(). * @return : 0 : compression completed correctly, @@ -3293,10 +3304,12 @@ static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx, projection.destinationAlreadyOpen = AIO_WritePool_getFile(ress.writeCtx) != NULL; projection.sourceIsStdin = !strcmp(srcFileName, stdinmark); projection.destinationIsStdout = !strcmp(dstFileName, stdoutmark); - projection.sourceIsRegular = (projection.sourceIsStdin - || projection.destinationIsStdout) - ? 0 - : UTIL_isRegularFileStat(srcFileStat); + /* C probes the private stat_t; Rust owns the stdin/stdout exceptions and + * normalizes the scalar consumed by the Rust destination lifecycle. */ + projection.sourceIsRegular = FIO_rust_compressDestinationTransferStat( + projection.sourceIsStdin, + projection.destinationIsStdout, + UTIL_isRegularFileStat(srcFileStat)); projection.openDestination = FIO_rust_compressDestinationOpen; projection.attachDestination = FIO_rust_compressDestinationAttach; projection.addHandler = FIO_rust_compressAddHandler; diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index ea49abe80..4a21db278 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -48,7 +48,14 @@ pub const FIO_RUST_REMOVE_FILE_ACTION_STAT_FAILED: c_int = 1; pub const FIO_RUST_REMOVE_FILE_ACTION_NON_REGULAR: c_int = 2; pub const FIO_RUST_REMOVE_FILE_ACTION_FAILED: c_int = 3; pub const FIO_RUST_REMOVE_FILE_ACTION_INVALID: c_int = 4; +const FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO: c_int = 0; +const FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES: c_int = 1; const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX; + +const _: () = { + assert!(FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO == 0); + assert!(FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES == 1); +}; const COMPRESSED_FILE_EXTENSIONS: &[&[u8]] = &[ b".zst", b".tzst", @@ -764,6 +771,37 @@ pub extern "C" fn FIO_rust_removeFileAction(status: c_int) -> c_int { remove_file_action(status) } +#[inline] +fn compress_destination_transfer_stat( + source_is_stdin: c_int, + destination_is_stdout: c_int, + source_is_regular: c_int, +) -> c_int { + if source_is_stdin != 0 || destination_is_stdout != 0 { + FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO + } else { + c_int::from(source_is_regular != 0) + } +} + +/// Select whether compression should transfer source metadata to its output. +/// +/// C retains the private `stat_t` probe and the actual metadata operations. +/// Rust owns the scalar policy: stdin and stdout never transfer metadata, and +/// otherwise a regular source enables the transfer. +#[no_mangle] +pub extern "C" fn FIO_rust_compressDestinationTransferStat( + source_is_stdin: c_int, + destination_is_stdout: c_int, + source_is_regular: c_int, +) -> c_int { + compress_destination_transfer_stat( + source_is_stdin, + destination_is_stdout, + source_is_regular, + ) +} + /// Callback projection for the C-owned `--list` file operations. #[repr(C)] pub struct FIO_listMultipleFilesCallbacks_t { @@ -2677,6 +2715,36 @@ mod tests { } } + #[test] + fn compression_destination_transfer_stat_preserves_metadata_exceptions() { + for (source_is_stdin, destination_is_stdout, source_is_regular, expected) in [ + (0, 0, 0, FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO), + (0, 0, 1, FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES), + (-1, 0, 1, FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO), + (0, 1, 1, FIO_RUST_COMPRESS_DST_TRANSFER_STAT_NO), + (0, 0, -1, FIO_RUST_COMPRESS_DST_TRANSFER_STAT_YES), + ] { + assert_eq!( + compress_destination_transfer_stat( + source_is_stdin, + destination_is_stdout, + source_is_regular, + ), + expected, + "unexpected metadata policy for stdin={source_is_stdin}, stdout={destination_is_stdout}, regular={source_is_regular}" + ); + assert_eq!( + FIO_rust_compressDestinationTransferStat( + source_is_stdin, + destination_is_stdout, + source_is_regular, + ), + expected, + "C ABI disagrees for stdin={source_is_stdin}, stdout={destination_is_stdout}, regular={source_is_regular}" + ); + } + } + #[test] fn source_exclusion_preserves_case_sensitive_suffixes_and_null_policy() { let cases = [