diff --git a/programs/fileio.c b/programs/fileio.c index 7aad30953..9bba8dfb9 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1388,6 +1388,44 @@ typedef char FIO_rust_compress_multiple_projection_size[ int FIO_rust_compressMultipleFilenames( const FIO_rust_compress_multiple_projection_t* projection); +typedef int (*FIO_rust_multiple_destination_warning_fn)(void* opaque); +typedef void* (*FIO_rust_multiple_destination_open_fn)(void* opaque); +typedef void (*FIO_rust_multiple_destination_attach_fn)( + void* opaque, void* destination); +typedef int (*FIO_rust_multiple_destination_close_fn)(void* opaque); +typedef struct { + const FIO_rust_compress_multiple_projection_t* files; + FIO_rust_multiple_destination_warning_fn warning; + FIO_rust_multiple_destination_open_fn openDestination; + FIO_rust_multiple_destination_attach_fn attachDestination; + FIO_rust_multiple_destination_close_fn closeDestination; +} FIO_rust_compress_multiple_destination_projection_t; +typedef char FIO_rust_compress_multiple_destination_files_offset[ + (offsetof(FIO_rust_compress_multiple_destination_projection_t, files) + == 0) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_warning_offset[ + (offsetof(FIO_rust_compress_multiple_destination_projection_t, warning) + == sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_open_offset[ + (offsetof(FIO_rust_compress_multiple_destination_projection_t, openDestination) + == 2 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_attach_offset[ + (offsetof(FIO_rust_compress_multiple_destination_projection_t, attachDestination) + == 3 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_close_offset[ + (offsetof(FIO_rust_compress_multiple_destination_projection_t, closeDestination) + == 4 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_callback_size[ + (sizeof(FIO_rust_multiple_destination_warning_fn) == sizeof(void*) + && sizeof(FIO_rust_multiple_destination_open_fn) == sizeof(void*) + && sizeof(FIO_rust_multiple_destination_attach_fn) == sizeof(void*) + && sizeof(FIO_rust_multiple_destination_close_fn) == sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_compress_multiple_destination_projection_size[ + (sizeof(FIO_rust_compress_multiple_destination_projection_t) + == 5 * sizeof(void*)) ? 1 : -1]; +int FIO_rust_compressMultipleFilenamesWithDestination( + const FIO_rust_compress_multiple_destination_projection_t* projection); + typedef int (*FIO_rust_compress_multiple_separate_file_fn)( void* opaque, const char* srcFileName); /* Rust selects the destination-mode callback; both callbacks retain their @@ -1456,6 +1494,40 @@ typedef char FIO_rust_decompress_multiple_projection_size[ int FIO_rust_decompressMultipleFilenames( const FIO_rust_decompress_multiple_projection_t* projection); +typedef struct { + const FIO_rust_decompress_multiple_projection_t* files; + int destinationEnabled; + FIO_rust_multiple_destination_warning_fn warning; + FIO_rust_multiple_destination_open_fn openDestination; + FIO_rust_multiple_destination_attach_fn attachDestination; + FIO_rust_multiple_destination_close_fn closeDestination; +} FIO_rust_decompress_multiple_destination_projection_t; +typedef char FIO_rust_decompress_multiple_destination_files_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, files) + == 0) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_enabled_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, destinationEnabled) + == sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_warning_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, warning) + == 2 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_open_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, openDestination) + == 3 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_attach_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, attachDestination) + == 4 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_close_offset[ + (offsetof(FIO_rust_decompress_multiple_destination_projection_t, closeDestination) + == 5 * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_int_size[ + (sizeof(int) <= sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_multiple_destination_projection_size[ + (sizeof(FIO_rust_decompress_multiple_destination_projection_t) + == 6 * sizeof(void*)) ? 1 : -1]; +int FIO_rust_decompressMultipleFilenamesWithDestination( + const FIO_rust_decompress_multiple_destination_projection_t* projection); + typedef int (*FIO_rust_decompress_multiple_separate_file_fn)( void* opaque, const char* srcFileName); typedef struct { @@ -3692,8 +3764,43 @@ typedef struct { FIO_prefs_t* prefs; cRess_t* ress; int compressionLevel; + const char* outFileName; } FIO_rust_compress_multiple_context_t; +static int FIO_rust_compressMultipleDestinationWarning(void* opaque) +{ + FIO_rust_compress_multiple_context_t* const context = + (FIO_rust_compress_multiple_context_t*)opaque; + return FIO_multiFilesConcatWarning( + context->fCtx, context->prefs, context->outFileName, 1 /* displayLevelCutoff */); +} + +static void* FIO_rust_compressMultipleDestinationOpen(void* opaque) +{ + FIO_rust_compress_multiple_context_t* const context = + (FIO_rust_compress_multiple_context_t*)opaque; + return (void*)FIO_openDstFile( + context->fCtx, context->prefs, NULL, + context->outFileName, DEFAULT_FILE_PERMISSIONS); +} + +static void FIO_rust_compressMultipleDestinationAttach(void* opaque, void* destination) +{ + FIO_rust_compress_multiple_context_t* const context = + (FIO_rust_compress_multiple_context_t*)opaque; + AIO_WritePool_setFile(context->ress->writeCtx, (FILE*)destination); +} + +static int FIO_rust_compressMultipleDestinationClose(void* opaque) +{ + FIO_rust_compress_multiple_context_t* const context = + (FIO_rust_compress_multiple_context_t*)opaque; + if (AIO_WritePool_closeFile(context->ress->writeCtx)) + EXM_THROW(29, "Write error (%s) : cannot properly close %s", + strerror(errno), context->outFileName); + return 0; +} + static int FIO_rust_compressMultipleFileCallback(void* opaque, const char* dstFileName, const char* srcFileName) @@ -3771,28 +3878,21 @@ int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx, /* init */ assert(outFileName != NULL || suffix != NULL); if (outFileName != NULL) { /* output into a single destination (stdout typically) */ - FILE *dstFile; - if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) { - FIO_freeCResources(&ress); - return 1; - } - dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS); - if (dstFile == NULL) { /* could not open outFileName */ - error = 1; - } else { - FIO_rust_compress_multiple_context_t callbackContext = { - fCtx, prefs, &ress, compressionLevel - }; - FIO_rust_compress_multiple_projection_t projection = { - fCtx, inFileNamesTable, outFileName, - &callbackContext, FIO_rust_compressMultipleFileCallback - }; - AIO_WritePool_setFile(ress.writeCtx, dstFile); - error = FIO_rust_compressMultipleFilenames(&projection); - if (AIO_WritePool_closeFile(ress.writeCtx)) - EXM_THROW(29, "Write error (%s) : cannot properly close %s", - strerror(errno), outFileName); - } + FIO_rust_compress_multiple_context_t callbackContext = { + fCtx, prefs, &ress, compressionLevel, outFileName + }; + FIO_rust_compress_multiple_projection_t files = { + fCtx, inFileNamesTable, outFileName, + &callbackContext, FIO_rust_compressMultipleFileCallback + }; + FIO_rust_compress_multiple_destination_projection_t projection = { + &files, + FIO_rust_compressMultipleDestinationWarning, + FIO_rust_compressMultipleDestinationOpen, + FIO_rust_compressMultipleDestinationAttach, + FIO_rust_compressMultipleDestinationClose + }; + error = FIO_rust_compressMultipleFilenamesWithDestination(&projection); } else { FIO_rust_compress_multiple_separate_context_t callbackContext = { fCtx, prefs, &ress, outMirroredRootDirName, @@ -4899,8 +4999,45 @@ typedef struct { FIO_ctx_t* fCtx; FIO_prefs_t* prefs; dRess_t* ress; + const char* outFileName; } FIO_rust_decompress_multiple_context_t; +static int FIO_rust_decompressMultipleDestinationWarning(void* opaque) +{ + FIO_rust_decompress_multiple_context_t* const context = + (FIO_rust_decompress_multiple_context_t*)opaque; + return FIO_multiFilesConcatWarning( + context->fCtx, context->prefs, context->outFileName, 1 /* displayLevelCutoff */); +} + +static void* FIO_rust_decompressMultipleDestinationOpen(void* opaque) +{ + FIO_rust_decompress_multiple_context_t* const context = + (FIO_rust_decompress_multiple_context_t*)opaque; + FILE* const dstFile = FIO_openDstFile( + context->fCtx, context->prefs, NULL, + context->outFileName, DEFAULT_FILE_PERMISSIONS); + if (dstFile == 0) EXM_THROW(19, "cannot open %s", context->outFileName); + return (void*)dstFile; +} + +static void FIO_rust_decompressMultipleDestinationAttach(void* opaque, void* destination) +{ + FIO_rust_decompress_multiple_context_t* const context = + (FIO_rust_decompress_multiple_context_t*)opaque; + AIO_WritePool_setFile(context->ress->writeCtx, (FILE*)destination); +} + +static int FIO_rust_decompressMultipleDestinationClose(void* opaque) +{ + FIO_rust_decompress_multiple_context_t* const context = + (FIO_rust_decompress_multiple_context_t*)opaque; + if (AIO_WritePool_closeFile(context->ress->writeCtx)) + EXM_THROW(72, "Write error : %s : cannot properly close output file", + strerror(errno)); + return 0; +} + /* Keep private source/destination operations, format dispatch, and * diagnostics in C; Rust owns per-file ordering and removal policy. */ static int FIO_rust_decompressMultipleFileCallback(void* opaque, @@ -5015,28 +5152,22 @@ FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx, dRess_t ress = FIO_createDResources(prefs, dictFileName); if (outFileName) { - if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) { - FIO_freeDResources(ress); - return 1; - } - if (!prefs->testMode) { - FILE* dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS); - if (dstFile == 0) EXM_THROW(19, "cannot open %s", outFileName); - AIO_WritePool_setFile(ress.writeCtx, dstFile); - } - { - FIO_rust_decompress_multiple_context_t callbackContext = { - fCtx, prefs, &ress - }; - FIO_rust_decompress_multiple_projection_t projection = { - fCtx, srcNamesTable, outFileName, - &callbackContext, FIO_rust_decompressMultipleFileCallback - }; - error = FIO_rust_decompressMultipleFilenames(&projection); - } - if ((!prefs->testMode) && (AIO_WritePool_closeFile(ress.writeCtx))) - EXM_THROW(72, "Write error : %s : cannot properly close output file", - strerror(errno)); + FIO_rust_decompress_multiple_context_t callbackContext = { + fCtx, prefs, &ress, outFileName + }; + FIO_rust_decompress_multiple_projection_t files = { + fCtx, srcNamesTable, outFileName, + &callbackContext, FIO_rust_decompressMultipleFileCallback + }; + FIO_rust_decompress_multiple_destination_projection_t projection = { + &files, + !prefs->testMode, + FIO_rust_decompressMultipleDestinationWarning, + FIO_rust_decompressMultipleDestinationOpen, + FIO_rust_decompressMultipleDestinationAttach, + FIO_rust_decompressMultipleDestinationClose + }; + error = FIO_rust_decompressMultipleFilenamesWithDestination(&projection); } else { FIO_rust_decompress_multiple_separate_context_t callbackContext = { fCtx, prefs, &ress, outMirroredRootDirName, outDirName diff --git a/rust/src/fileio_asyncio.rs b/rust/src/fileio_asyncio.rs index 269904925..9de3bc86f 100644 --- a/rust/src/fileio_asyncio.rs +++ b/rust/src/fileio_asyncio.rs @@ -602,6 +602,14 @@ pub type FIO_rust_compress_multiple_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int; pub type FIO_rust_compress_multiple_separate_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; +pub type FIO_rust_multiple_destination_warning_fn = + unsafe extern "C" fn(*mut c_void) -> c_int; +pub type FIO_rust_multiple_destination_open_fn = + unsafe extern "C" fn(*mut c_void) -> *mut c_void; +pub type FIO_rust_multiple_destination_attach_fn = + unsafe extern "C" fn(*mut c_void, *mut c_void); +pub type FIO_rust_multiple_destination_close_fn = + unsafe extern "C" fn(*mut c_void) -> c_int; /// Rust owns only the shared-destination file iteration and aggregate error /// handling. C retains the private preferences/resources and supplies one @@ -636,6 +644,52 @@ const _: () = { assert!(size_of::() == 5 * size_of::()); }; +/// Rust owns the shared-destination lifecycle around the file iterator. C +/// retains warning, filesystem, and write-pool operations behind callbacks. +#[repr(C)] +pub struct FIO_rust_compress_multiple_destination_projection_t { + pub files: *const FIO_rust_compress_multiple_projection_t, + pub warning: Option, + pub open_destination: Option, + pub attach_destination: Option, + pub close_destination: Option, +} +const _: () = { + assert!( + std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, files) == 0 + ); + assert!( + std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, warning) + == size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_compress_multiple_destination_projection_t, + open_destination + ) == 2 * size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_compress_multiple_destination_projection_t, + attach_destination + ) == 3 * size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_compress_multiple_destination_projection_t, + close_destination + ) == 4 * size_of::() + ); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!( + size_of::() + == 5 * size_of::() + ); +}; + /// Rust owns separate-destination mode selection, file iteration, and /// aggregate error handling. C retains destination-name construction, private /// preferences/resources, diagnostics, and compression dispatch in the two @@ -724,6 +778,54 @@ const _: () = { assert!(size_of::() == 5 * size_of::()); }; +/// Rust owns the shared-destination lifecycle around the decompression file +/// iterator. Test mode skips the destination callbacks just as the original +/// C path did. +#[repr(C)] +pub struct FIO_rust_decompress_multiple_destination_projection_t { + pub files: *const FIO_rust_decompress_multiple_projection_t, + pub destination_enabled: c_int, + pub warning: Option, + pub open_destination: Option, + pub attach_destination: Option, + pub close_destination: Option, +} +const _: () = { + assert!( + std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, files) == 0 + ); + assert!( + std::mem::offset_of!( + FIO_rust_decompress_multiple_destination_projection_t, + destination_enabled + ) == size_of::() + ); + assert!( + std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, warning) + == 2 * size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_decompress_multiple_destination_projection_t, + open_destination + ) == 3 * size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_decompress_multiple_destination_projection_t, + attach_destination + ) == 4 * size_of::() + ); + assert!( + std::mem::offset_of!( + FIO_rust_decompress_multiple_destination_projection_t, + close_destination + ) == 5 * size_of::() + ); + assert!(size_of::() <= size_of::()); + assert!(size_of::() == 6 * size_of::()); +}; + /// Rust owns only the separate-destination file iteration and aggregate error /// handling. C retains destination-name construction, source opening, format /// dispatch, diagnostics, and source removal through the per-source callback. @@ -3372,6 +3474,49 @@ pub unsafe extern "C" fn FIO_rust_compressMultipleFilenames( error } +/// Open, attach, iterate, and close one destination shared by all input +/// files. C retains the private destination and write-pool operations. +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_compressMultipleFilenamesWithDestination( + projection: *const FIO_rust_compress_multiple_destination_projection_t, +) -> c_int { + assert!(!projection.is_null()); + let projection = unsafe { &*projection }; + assert!(!projection.files.is_null()); + let files = unsafe { &*projection.files }; + assert!(!files.f_ctx.is_null()); + assert!(!files.input_file_names.is_null()); + assert!(!files.output_file_name.is_null()); + assert!(!files.opaque.is_null()); + assert!(files.compress_file.is_some()); + + let warning = projection + .warning + .expect("shared-destination warning callback is required"); + if unsafe { warning(files.opaque) } != 0 { + return 1; + } + + let open_destination = projection + .open_destination + .expect("shared-destination open callback is required"); + let destination = unsafe { open_destination(files.opaque) }; + if destination.is_null() { + return 1; + } + + let attach_destination = projection + .attach_destination + .expect("shared-destination attach callback is required"); + unsafe { attach_destination(files.opaque, destination) }; + + let result = unsafe { FIO_rust_compressMultipleFilenames(projection.files) }; + let close_destination = projection + .close_destination + .expect("shared-destination close callback is required"); + result | unsafe { close_destination(files.opaque) } +} + /// Select the destination-mode callback once, then iterate files that each /// receive a separate destination. C retains destination-name construction, /// resource state, diagnostics, and compression dispatch; Rust preserves the @@ -3449,6 +3594,53 @@ pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenames( error } +/// Run the shared-destination decompression lifecycle. Test mode still runs +/// the warning and file iterator, but intentionally skips destination I/O. +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenamesWithDestination( + projection: *const FIO_rust_decompress_multiple_destination_projection_t, +) -> c_int { + assert!(!projection.is_null()); + let projection = unsafe { &*projection }; + assert!(!projection.files.is_null()); + let files = unsafe { &*projection.files }; + assert!(!files.f_ctx.is_null()); + assert!(!files.src_names_table.is_null()); + assert!(!files.out_file_name.is_null()); + assert!(!files.opaque.is_null()); + assert!(files.decompress_file.is_some()); + + let warning = projection + .warning + .expect("shared-destination warning callback is required"); + if unsafe { warning(files.opaque) } != 0 { + return 1; + } + + if projection.destination_enabled == 0 { + return unsafe { FIO_rust_decompressMultipleFilenames(projection.files) }; + } + + let open_destination = projection + .open_destination + .expect("shared-destination open callback is required"); + let destination = unsafe { open_destination(files.opaque) }; + if destination.is_null() { + return 1; + } + + let attach_destination = projection + .attach_destination + .expect("shared-destination attach callback is required"); + unsafe { attach_destination(files.opaque, destination) }; + + let result = unsafe { FIO_rust_decompressMultipleFilenames(projection.files) }; + let close_destination = projection + .close_destination + .expect("shared-destination close callback is required"); + result | unsafe { close_destination(files.opaque) } +} + /// Iterate files that each receive a separate destination. The C callback /// retains destination-name construction, private I/O, format dispatch, and /// diagnostics; Rust owns per-file ordering and removal policy. @@ -7240,6 +7432,191 @@ mod tests { status } + #[derive(Default)] + struct MultipleDestinationSessionState { + events: Vec<&'static str>, + sources: Vec<*const c_char>, + destinations: Vec<*const c_char>, + statuses: Vec, + allow_open: bool, + warning_status: c_int, + close_status: c_int, + } + + unsafe extern "C" fn record_multiple_destination_warning(opaque: *mut c_void) -> c_int { + let state = unsafe { &mut *opaque.cast::() }; + state.events.push("warning"); + state.warning_status + } + + unsafe extern "C" fn record_multiple_destination_open(opaque: *mut c_void) -> *mut c_void { + let state = unsafe { &mut *opaque.cast::() }; + state.events.push("open"); + if state.allow_open { + ptr::dangling_mut::() + } else { + ptr::null_mut() + } + } + + unsafe extern "C" fn record_multiple_destination_attach( + opaque: *mut c_void, + destination: *mut c_void, + ) { + assert!(!destination.is_null()); + let state = unsafe { &mut *opaque.cast::() }; + state.events.push("attach"); + } + + unsafe extern "C" fn record_multiple_destination_close(opaque: *mut c_void) -> c_int { + let state = unsafe { &mut *opaque.cast::() }; + state.events.push("close"); + state.close_status + } + + unsafe extern "C" fn record_multiple_destination_file( + opaque: *mut c_void, + destination: *const c_char, + source: *const c_char, + ) -> c_int { + let state = unsafe { &mut *opaque.cast::() }; + let status = state + .statuses + .get(state.sources.len()) + .copied() + .unwrap_or(0); + state.events.push("file"); + state.sources.push(source); + state.destinations.push(destination); + status + } + + #[test] + fn compression_multiple_destination_session_owns_lifecycle() { + let source_names = [c"one".as_ptr(), c"two".as_ptr()]; + let destination = c"archive.zst"; + let mut context = FIO_rust_compression_context_t { + nbFilesTotal: 2, + hasStdinInput: 0, + hasStdoutOutput: 0, + currFileIdx: 0, + nbFilesProcessed: 0, + totalBytesInput: 0, + totalBytesOutput: 0, + }; + let mut state = MultipleDestinationSessionState { + allow_open: true, + statuses: vec![0, 4], + ..MultipleDestinationSessionState::default() + }; + let files = FIO_rust_compress_multiple_projection_t { + f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), + input_file_names: source_names.as_ptr(), + output_file_name: destination.as_ptr(), + opaque: (&mut state as *mut MultipleDestinationSessionState).cast(), + compress_file: Some(record_multiple_destination_file), + }; + let projection = FIO_rust_compress_multiple_destination_projection_t { + files: &files, + warning: Some(record_multiple_destination_warning), + open_destination: Some(record_multiple_destination_open), + attach_destination: Some(record_multiple_destination_attach), + close_destination: Some(record_multiple_destination_close), + }; + + assert_eq!( + unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) }, + 4 + ); + assert_eq!(state.events, ["warning", "open", "attach", "file", "file", "close"]); + assert_eq!(state.sources, source_names); + assert_eq!(state.destinations, vec![destination.as_ptr(); 2]); + assert_eq!(context.currFileIdx, 2); + assert_eq!(context.nbFilesProcessed, 1); + } + + #[test] + fn compression_multiple_destination_session_stops_on_open_failure() { + let source_names = [c"one".as_ptr()]; + let destination = c"archive.zst"; + let mut context = FIO_rust_compression_context_t { + nbFilesTotal: 1, + hasStdinInput: 0, + hasStdoutOutput: 0, + currFileIdx: 0, + nbFilesProcessed: 3, + totalBytesInput: 0, + totalBytesOutput: 0, + }; + let mut state = MultipleDestinationSessionState::default(); + let files = FIO_rust_compress_multiple_projection_t { + f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), + input_file_names: source_names.as_ptr(), + output_file_name: destination.as_ptr(), + opaque: (&mut state as *mut MultipleDestinationSessionState).cast(), + compress_file: Some(record_multiple_destination_file), + }; + let projection = FIO_rust_compress_multiple_destination_projection_t { + files: &files, + warning: Some(record_multiple_destination_warning), + open_destination: Some(record_multiple_destination_open), + attach_destination: Some(record_multiple_destination_attach), + close_destination: Some(record_multiple_destination_close), + }; + + assert_eq!( + unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) }, + 1 + ); + assert_eq!(state.events, ["warning", "open"]); + assert!(state.sources.is_empty()); + assert_eq!(context.currFileIdx, 0); + assert_eq!(context.nbFilesProcessed, 3); + } + + #[test] + fn decompression_multiple_destination_session_skips_io_in_test_mode() { + let source_names = [c"one".as_ptr(), c"two".as_ptr()]; + let destination = c"archive"; + let mut context = FIO_rust_compression_context_t { + nbFilesTotal: 2, + hasStdinInput: 0, + hasStdoutOutput: 0, + currFileIdx: 0, + nbFilesProcessed: 0, + totalBytesInput: 0, + totalBytesOutput: 0, + }; + let mut state = MultipleDestinationSessionState { + statuses: vec![0, 8], + ..MultipleDestinationSessionState::default() + }; + let files = FIO_rust_decompress_multiple_projection_t { + f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), + src_names_table: source_names.as_ptr(), + out_file_name: destination.as_ptr(), + opaque: (&mut state as *mut MultipleDestinationSessionState).cast(), + decompress_file: Some(record_multiple_destination_file), + }; + let projection = FIO_rust_decompress_multiple_destination_projection_t { + files: &files, + destination_enabled: 0, + warning: Some(record_multiple_destination_warning), + open_destination: Some(record_multiple_destination_open), + attach_destination: Some(record_multiple_destination_attach), + close_destination: Some(record_multiple_destination_close), + }; + + assert_eq!( + unsafe { FIO_rust_decompressMultipleFilenamesWithDestination(&projection) }, + 8 + ); + assert_eq!(state.events, ["warning", "file", "file"]); + assert_eq!(state.sources, source_names); + assert_eq!(context.currFileIdx, 2); + assert_eq!(context.nbFilesProcessed, 1); + } + #[test] fn compression_multiple_separate_destinations_selects_mirror_callback_only() { let source_names = [c"one".as_ptr(), c"two".as_ptr()];