#![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::too_many_arguments)] //! Rust implementation of the exported asynchronous file-I/O pools. //! //! The C header exposes the read-pool buffer fields directly to `fileio.c`, //! while the rest of each context is private implementation detail. The //! public context types below are therefore deliberately opaque. Contexts //! are allocated with the C allocator and begin with the exact C layout; the //! layout is calculated at runtime because `threading.h` changes the mutex //! and condition-variable fields when multithreading is disabled. //! //! The C translation unit remains in the program source list as a declaration //! shim. The exported symbols below are the single implementation linked into //! program, library, and C-test archives. use std::collections::VecDeque; use std::ffi::{c_char, c_void}; use std::mem::{align_of, offset_of, size_of}; #[cfg(any(windows, not(any(unix, windows))))] use std::os::raw::c_long; use std::os::raw::{c_int, c_uint}; use std::ptr; use std::sync::{Arc, Condvar, Mutex}; use std::thread::{self, JoinHandle}; use std::time::{Duration, Instant}; #[cfg(feature = "compression")] use crate::zstd_compress::{ZSTD_inBuffer, ZSTD_outBuffer}; #[cfg(all(feature = "compression", not(test)))] unsafe extern "C" { fn ZSTD_toFlushNow(cctx: *mut c_void) -> usize; } const MAX_IO_JOBS: usize = 10; const IO_QUEUE_SIZE: usize = MAX_IO_JOBS - 2; const SPARSE_SEGMENT_SIZE: usize = 32 * 1024; const SPARSE_SKIP_CHUNK: u64 = 1 << 30; const PASS_THROUGH_MAX_BLOCK_SIZE: usize = 64 * 1024; #[cfg(feature = "decompression")] const ZSTD_FRAMEHEADERSIZE_MAX: usize = 18; pub const FIO_RUST_ZSTD_FRAME_OK: c_int = 0; pub const FIO_RUST_ZSTD_FRAME_DECODING_ERROR: c_int = 1; pub const FIO_RUST_ZSTD_FRAME_PREMATURE_END: c_int = 2; pub const FIO_RUST_DECOMPRESS_OK: c_int = 0; pub const FIO_RUST_DECOMPRESS_PASS_THROUGH: c_int = 1; pub const FIO_RUST_DECOMPRESS_EMPTY_INPUT: c_int = 2; pub const FIO_RUST_DECOMPRESS_SHORT_INPUT: c_int = 3; pub const FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED: c_int = 4; pub const FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED: c_int = 5; pub const FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED: c_int = 6; pub const FIO_RUST_DECOMPRESS_FRAME_ERROR: c_int = 7; pub const FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT: c_int = 8; pub const FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR: c_int = 9; pub const FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED: c_int = 10; pub const FIO_RUST_DECOMPRESS_ACTION_NOOP: c_int = 0; pub const FIO_RUST_DECOMPRESS_ACTION_EMPTY_INPUT: c_int = 1; pub const FIO_RUST_DECOMPRESS_ACTION_SHORT_INPUT: c_int = 2; pub const FIO_RUST_DECOMPRESS_ACTION_GZIP_UNSUPPORTED: c_int = 3; pub const FIO_RUST_DECOMPRESS_ACTION_LZMA_UNSUPPORTED: c_int = 4; pub const FIO_RUST_DECOMPRESS_ACTION_LZ4_UNSUPPORTED: c_int = 5; pub const FIO_RUST_DECOMPRESS_ACTION_UNSUPPORTED_FORMAT: c_int = 6; pub const FIO_RUST_DECOMPRESS_ACTION_INVALID: c_int = 7; /// Resolves the legacy automatic pass-through mode without touching the /// destination name or any C-owned file state. Explicit values are returned /// unchanged so the C assertion keeps its original validation behavior. #[inline] fn decompress_pass_through_policy( pass_through: c_int, overwrite: c_int, destination_is_stdout: c_int, ) -> c_int { if pass_through == -1 { (overwrite != 0 && destination_is_stdout != 0) as c_int } else { pass_through } } #[no_mangle] pub extern "C" fn FIO_rust_decompressPassThroughPolicy( pass_through: c_int, overwrite: c_int, destination_is_stdout: c_int, ) -> c_int { decompress_pass_through_policy(pass_through, overwrite, destination_is_stdout) } /// Maps decompression results to the scalar actions consumed by the C CLI. /// Rust owns this status policy; C retains the exact diagnostics, source-name /// handling, and display operations for the returned action. #[inline] fn decompress_status_action(status: c_int) -> c_int { match status { FIO_RUST_DECOMPRESS_EMPTY_INPUT => FIO_RUST_DECOMPRESS_ACTION_EMPTY_INPUT, FIO_RUST_DECOMPRESS_SHORT_INPUT => FIO_RUST_DECOMPRESS_ACTION_SHORT_INPUT, FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_GZIP_UNSUPPORTED, FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_LZMA_UNSUPPORTED, FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_LZ4_UNSUPPORTED, FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT => FIO_RUST_DECOMPRESS_ACTION_UNSUPPORTED_FORMAT, FIO_RUST_DECOMPRESS_OK | FIO_RUST_DECOMPRESS_PASS_THROUGH | FIO_RUST_DECOMPRESS_FRAME_ERROR | FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR | FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_NOOP, _ => FIO_RUST_DECOMPRESS_ACTION_INVALID, } } #[no_mangle] pub extern "C" fn FIO_rust_decompressStatusAction(status: c_int) -> c_int { decompress_status_action(status) } type FIO_rust_frame_progress_fn = Option; pub type FIO_rust_decompress_frame_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64, *mut u64, *mut usize, c_int) -> c_int; type FIO_rust_zstd_frame_decoding_error_fn = unsafe extern "C" fn(*mut c_void, *const c_char, usize); type FIO_rust_zstd_frame_premature_end_fn = unsafe extern "C" fn(*mut c_void, *const c_char); pub type FIO_rust_pass_through_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_decompress_status_fn = unsafe extern "C" fn(*mut c_void, c_int, *const c_char); pub type FIO_rust_decompress_finish_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64); #[cfg(feature = "decompression")] type FIO_zstd_reset_fn = unsafe extern "C" fn(*mut c_void, c_int) -> usize; #[cfg(feature = "decompression")] type FIO_zstd_decompress_fn = unsafe extern "C" fn( *mut c_void, *mut crate::zstd_decompress::ZSTD_outBuffer, *mut crate::zstd_decompress::ZSTD_inBuffer, ) -> usize; #[cfg(feature = "decompression")] type FIO_zstd_in_size_fn = extern "C" fn() -> usize; #[cfg(feature = "decompression")] type FIO_zstd_is_frame_fn = unsafe extern "C" fn(*const c_void, usize) -> c_uint; /// Rust owns the default zstd-frame result policy. C supplies only the exact /// display callbacks and the private asynchronous-pool pointers needed by the /// frame loop. #[repr(C)] pub struct FIO_rust_zstd_frame_policy_state { callback_context: *mut c_void, f_ctx: *mut c_void, dctx: *mut c_void, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, src_file_name: *const c_char, already_decoded: u64, progress: FIO_rust_frame_progress_fn, display_decoding_error: Option, display_premature_end: Option, } const _: () = { assert!(offset_of!(FIO_rust_zstd_frame_policy_state, callback_context) == 0); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, f_ctx) == size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, dctx) == 2 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, read_ctx) == 3 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, write_ctx) == 4 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, src_file_name) == 5 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, already_decoded) == 6 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, progress) == 7 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, display_decoding_error) == 8 * size_of::()); assert!(offset_of!(FIO_rust_zstd_frame_policy_state, display_premature_end) == 9 * size_of::()); assert!(size_of::() == 10 * size_of::()); assert!(size_of::() == size_of::()); assert!(size_of::() == size_of::()); }; /// C supplies format-specific decoders through this projection. The opaque /// value is normally a pointer to C's private `dRess_t`; Rust only drives the /// callbacks and never depends on that platform-sensitive layout. #[repr(C)] pub struct FIO_rust_decompress_callbacks_t { pub opaque: *mut c_void, pub decode_zstd: Option, pub decode_gzip: Option, pub decode_lzma: Option, pub decode_lz4: Option, pub pass_through: Option, pub report_status: Option, pub finish: Option, } pub type FIO_rust_decompress_read_fill_fn = unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize); pub type FIO_rust_decompress_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize); pub type FIO_rust_decompress_write_acquire_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize); pub type FIO_rust_decompress_write_enqueue_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize); pub type FIO_rust_decompress_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void); pub type FIO_rust_decompress_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void); /// The optional-format leaves use the same opaque asynchronous I/O projection. /// `read_fill(0, ...)` returns the bytes already staged by the mixed-format /// dispatcher; non-zero requests refill the C-owned read pool after the Rust /// loop has consumed the current input. #[repr(C)] pub struct FIO_rust_decompress_io_projection_t { pub read_opaque: *mut c_void, pub write_opaque: *mut c_void, pub read_buffer_size: usize, pub read_fill: Option, pub read_consume: Option, pub write_acquire: Option, pub write_enqueue: Option, pub write_release: Option, pub sparse_write_end: Option, } pub const FIO_RUST_GZIP_DECOMPRESS_OK: c_int = 0; pub const FIO_RUST_GZIP_DECOMPRESS_INIT_ERROR: c_int = 1; pub const FIO_RUST_GZIP_DECOMPRESS_BUF_ERROR: c_int = 2; pub const FIO_RUST_GZIP_DECOMPRESS_INFLATE_ERROR: c_int = 3; pub const FIO_RUST_GZIP_DECOMPRESS_END_ERROR: c_int = 4; pub const FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION: c_int = 5; const FIO_RUST_GZIP_DECOMPRESS_Z_OK: c_int = 0; const FIO_RUST_GZIP_DECOMPRESS_Z_STREAM_END: c_int = 1; const FIO_RUST_GZIP_DECOMPRESS_Z_BUF_ERROR: c_int = -5; const FIO_RUST_GZIP_DECOMPRESS_Z_NO_FLUSH: c_int = 0; const FIO_RUST_GZIP_DECOMPRESS_Z_FINISH: c_int = 4; pub type FIO_rust_gzip_decompress_init_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_gzip_decompress_inflate_fn = unsafe extern "C" fn( *mut c_void, *const u8, usize, *mut u8, usize, c_int, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_gzip_decompress_end_fn = unsafe extern "C" fn(*mut c_void) -> c_int; /// Rust owns the gzip read/inflate/output loop. The C side retains only the /// zlib stream and its calls, plus the opaque asynchronous pool operations. #[repr(C)] pub struct FIO_rust_gzip_decompress_projection_t { pub io: FIO_rust_decompress_io_projection_t, pub zlib_opaque: *mut c_void, pub zlib_init: Option, pub zlib_inflate: Option, pub zlib_end: Option, } pub const FIO_RUST_LZMA_DECOMPRESS_OK: c_int = 0; pub const FIO_RUST_LZMA_DECOMPRESS_INIT_ERROR: c_int = 1; pub const FIO_RUST_LZMA_DECOMPRESS_BUF_ERROR: c_int = 2; pub const FIO_RUST_LZMA_DECOMPRESS_CODE_ERROR: c_int = 3; pub const FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION: c_int = 4; const FIO_RUST_LZMA_DECOMPRESS_OK_CODE: c_int = 0; const FIO_RUST_LZMA_DECOMPRESS_STREAM_END: c_int = 1; const FIO_RUST_LZMA_DECOMPRESS_BUF_ERROR_CODE: c_int = 10; const FIO_RUST_LZMA_DECOMPRESS_RUN: c_int = 0; const FIO_RUST_LZMA_DECOMPRESS_FINISH: c_int = 3; pub type FIO_rust_lzma_decompress_init_fn = unsafe extern "C" fn(*mut c_void, c_int) -> c_int; pub type FIO_rust_lzma_decompress_code_fn = unsafe extern "C" fn( *mut c_void, *const u8, usize, *mut u8, usize, c_int, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_lzma_decompress_end_fn = unsafe extern "C" fn(*mut c_void); /// Rust owns the xz/LZMA read/code/output loop while liblzma's stream remains /// an opaque C-owned handle. #[repr(C)] pub struct FIO_rust_lzma_decompress_projection_t { pub io: FIO_rust_decompress_io_projection_t, pub lzma_opaque: *mut c_void, pub lzma_init: Option, pub lzma_code: Option, pub lzma_end: Option, } pub const FIO_RUST_LZ4_DECOMPRESS_OK: c_int = 0; pub const FIO_RUST_LZ4_DECOMPRESS_CREATE_ERROR: c_int = 1; pub const FIO_RUST_LZ4_DECOMPRESS_CODE_ERROR: c_int = 2; pub const FIO_RUST_LZ4_DECOMPRESS_UNFINISHED: c_int = 3; pub const FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION: c_int = 4; pub type FIO_rust_lz4_decompress_create_fn = unsafe extern "C" fn(*mut c_void, c_uint, *mut usize) -> c_int; pub type FIO_rust_lz4_decompress_code_fn = unsafe extern "C" fn( *mut c_void, *mut u8, *mut usize, *const u8, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_lz4_decompress_free_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_lz4_decompress_progress_fn = unsafe extern "C" fn(*mut c_void, u64); /// Rust owns the LZ4 frame loop. LZ4F's context, error values, and calls are /// kept behind callbacks so no optional-library or private-handle layout is /// part of the Rust/C projection. #[repr(C)] pub struct FIO_rust_lz4_decompress_projection_t { pub io: FIO_rust_decompress_io_projection_t, pub codec_opaque: *mut c_void, pub progress_opaque: *mut c_void, pub version: c_uint, pub create: Option, pub code: Option, pub free_context: Option, pub progress: Option, } pub const FIO_RUST_COMPRESS_OK: c_int = 0; pub const FIO_RUST_COMPRESS_GZIP_UNSUPPORTED: c_int = 1; pub const FIO_RUST_COMPRESS_LZMA_UNSUPPORTED: c_int = 2; pub const FIO_RUST_COMPRESS_LZ4_UNSUPPORTED: c_int = 3; pub const FIO_RUST_COMPRESS_ZSTD_UNSUPPORTED: c_int = 4; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_OK: c_int = 0; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_GZIP_UNSUPPORTED: c_int = 1; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_LZMA_UNSUPPORTED: c_int = 2; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_LZ4_UNSUPPORTED: c_int = 3; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_ZSTD_UNSUPPORTED: c_int = 4; pub const FIO_RUST_COMPRESS_DIAGNOSTIC_UNKNOWN: c_int = 5; /// Classifies the aggregate compression-selector result while leaving the /// format-specific exception text and fallback assertion in C. #[no_mangle] pub extern "C" fn FIO_rust_compressFilenameDiagnostic(status: c_int) -> c_int { match status { FIO_RUST_COMPRESS_OK => FIO_RUST_COMPRESS_DIAGNOSTIC_OK, FIO_RUST_COMPRESS_GZIP_UNSUPPORTED => FIO_RUST_COMPRESS_DIAGNOSTIC_GZIP_UNSUPPORTED, FIO_RUST_COMPRESS_LZMA_UNSUPPORTED => FIO_RUST_COMPRESS_DIAGNOSTIC_LZMA_UNSUPPORTED, FIO_RUST_COMPRESS_LZ4_UNSUPPORTED => FIO_RUST_COMPRESS_DIAGNOSTIC_LZ4_UNSUPPORTED, FIO_RUST_COMPRESS_ZSTD_UNSUPPORTED => FIO_RUST_COMPRESS_DIAGNOSTIC_ZSTD_UNSUPPORTED, _ => FIO_RUST_COMPRESS_DIAGNOSTIC_UNKNOWN, } } const FIO_ZSTD_COMPRESSION: c_int = 0; const FIO_GZIP_COMPRESSION: c_int = 1; const FIO_XZ_COMPRESSION: c_int = 2; const FIO_LZMA_COMPRESSION: c_int = 3; const FIO_LZ4_COMPRESSION: c_int = 4; pub type FIO_rust_compress_zstd_fn = unsafe extern "C" fn( *mut c_void, *mut c_void, *mut c_void, *const c_char, u64, c_int, *mut u64, ) -> u64; pub type FIO_rust_compress_gzip_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64, c_int, *mut u64) -> u64; pub type FIO_rust_compress_lzma_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64, c_int, *mut u64, c_int) -> u64; pub type FIO_rust_compress_lz4_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64, c_int, c_int, *mut u64) -> u64; pub type FIO_rust_compress_input_display_fn = unsafe extern "C" fn(*mut c_void, *const c_char, u64); pub type FIO_rust_compress_status_display_fn = unsafe extern "C" fn(*mut c_void, *mut c_void, *const c_char, *const c_char, u64, u64); /// C-owned compression codecs and CLI display hooks used by the Rust file /// compression selector. The codec callbacks deliberately receive opaque /// resource pointers: Rust owns the selection/accounting loop but never /// assumes the private `cRess_t` layout. #[repr(C)] pub struct FIO_rust_compress_callbacks_t { pub opaque: *mut c_void, pub compress_zstd: Option, pub compress_gzip: Option, pub compress_lzma: Option, pub compress_lz4: Option, pub display_input: Option, pub display_status: Option, } pub const FIO_RUST_COMPRESS_SRC_STAT_FAILED: c_int = 0; pub const FIO_RUST_COMPRESS_SRC_STAT_OK: c_int = 1; pub const FIO_RUST_COMPRESS_SRC_DIRECTORY: c_int = 2; pub const FIO_RUST_COMPRESS_SRC_DICT_COLLISION: c_int = 3; const FIO_RUST_COMPRESS_SRC_OPEN_OK: c_int = 0; const FIO_RUST_COMPRESS_SRC_ASYNC_THRESHOLD: u64 = (1 << 17) * 3; const FIO_RUST_COMPRESS_SRC_UNKNOWN_SIZE: u64 = u64::MAX; pub type FIO_rust_compress_src_stat_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_rust_compress_src_excluded_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_rust_compress_src_open_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *mut u64) -> c_int; pub type FIO_rust_compress_src_async_fn = unsafe extern "C" fn(*mut c_void, c_int); pub type FIO_rust_compress_src_attach_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_compress_src_compress_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, c_int) -> c_int; pub type FIO_rust_compress_src_close_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_compress_src_remove_fn = unsafe extern "C" fn(*mut c_void, *const c_char); /// Rust owns the source-file policy and ordering. C retains the private /// context/resource/stat objects and implements each operation behind opaque /// callbacks, including diagnostics, signal handling, and actual compression. #[repr(C)] pub struct FIO_rust_compress_src_projection_t { pub opaque: *mut c_void, pub dst_file_name: *const c_char, pub src_file_name: *const c_char, pub compression_level: c_int, pub source_is_stdin: c_int, pub exclude_compressed_files: c_int, pub remove_src_file: c_int, pub stat_source: Option, pub source_is_excluded: Option, pub open_source: Option, pub set_async: Option, pub attach_source: Option, pub compress: Option, pub close_source: Option, pub remove_source: Option, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_compress_src_projection_t, opaque) == 0); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, dst_file_name) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, src_file_name) == 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, compression_level) == 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, source_is_stdin) == 3 * size_of::() + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, exclude_compressed_files) == 3 * size_of::() + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, remove_src_file) == 3 * size_of::() + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, stat_source) == 3 * size_of::() + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, source_is_excluded) == 3 * size_of::() + 4 * size_of::() + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, open_source) == 3 * size_of::() + 4 * size_of::() + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, set_async) == 3 * size_of::() + 4 * size_of::() + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, attach_source) == 3 * size_of::() + 4 * size_of::() + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, compress) == 3 * size_of::() + 4 * size_of::() + 5 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, close_source) == 3 * size_of::() + 4 * size_of::() + 6 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_src_projection_t, remove_source) == 3 * size_of::() + 4 * size_of::() + 7 * size_of::() ); assert!( size_of::() == 3 * size_of::() + 4 * size_of::() + usize::BITS as usize ); }; pub const FIO_RUST_COMPRESS_DST_OPEN_OK: c_int = 0; pub type FIO_rust_compress_dst_open_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, c_int, *mut c_int) -> c_int; pub type FIO_rust_compress_dst_handler_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_compress_dst_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, c_int) -> c_int; pub type FIO_rust_compress_dst_stat_fn = unsafe extern "C" fn(*mut c_void, c_int, *const c_char); pub type FIO_rust_compress_dst_close_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_compress_dst_remove_fn = unsafe extern "C" fn(*mut c_void, *const c_char); /// Rust owns the per-file compression destination lifecycle. C retains the /// private resource and file handles, diagnostics, metadata operations, and /// format dispatch behind opaque callbacks. #[repr(C)] pub struct FIO_rust_compress_dst_projection_t { pub opaque: *mut c_void, pub dst_file_name: *const c_char, pub src_file_name: *const c_char, pub compression_level: c_int, pub destination_already_open: c_int, pub source_is_stdin: c_int, pub destination_is_stdout: c_int, pub source_is_regular: c_int, pub open_destination: Option, pub attach_destination: Option, pub add_handler: Option, pub compress: Option, pub clear_handler: Option, pub set_fd_stat: Option, pub close_destination: Option, pub utime_destination: Option, pub remove_destination: Option, } const _: () = { let callback_offset = (3 * size_of::() + 5 * size_of::()) .div_ceil(size_of::()) * size_of::(); assert!(std::mem::offset_of!(FIO_rust_compress_dst_projection_t, opaque) == 0); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, dst_file_name) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, src_file_name) == 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, compression_level) == 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, destination_already_open) == 3 * size_of::() + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, source_is_stdin) == 3 * size_of::() + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, destination_is_stdout) == 3 * size_of::() + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, source_is_regular) == 3 * size_of::() + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, open_destination) == callback_offset ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, attach_destination) == callback_offset + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, add_handler) == callback_offset + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, compress) == callback_offset + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, clear_handler) == callback_offset + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, set_fd_stat) == callback_offset + 5 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, close_destination) == callback_offset + 6 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, utime_destination) == callback_offset + 7 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_dst_projection_t, remove_destination) == callback_offset + usize::BITS as usize ); assert!( size_of::() == callback_offset + 9 * size_of::() ); }; 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 /// callback for each source file. #[repr(C)] pub struct FIO_rust_compress_multiple_projection_t { pub f_ctx: *mut c_void, pub input_file_names: *const *const c_char, pub output_file_name: *const c_char, pub opaque: *mut c_void, pub compress_file: Option, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, f_ctx) == 0); assert!( std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, input_file_names) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, output_file_name) == 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, opaque) == 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, compress_file) == 4 * size_of::() ); assert!(size_of::() == size_of::()); 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 /// opaque per-source callbacks. #[repr(C)] pub struct FIO_rust_compress_multiple_separate_projection_t { pub f_ctx: *mut c_void, pub input_file_names: *const *const c_char, pub opaque: *mut c_void, pub mirror_output: c_int, pub compress_mirrored_file: Option, pub compress_flat_file: Option, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_compress_multiple_separate_projection_t, f_ctx) == 0); assert!( std::mem::offset_of!( FIO_rust_compress_multiple_separate_projection_t, input_file_names ) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_compress_multiple_separate_projection_t, opaque) == 2 * size_of::() ); assert!( std::mem::offset_of!( FIO_rust_compress_multiple_separate_projection_t, mirror_output ) == 3 * size_of::() ); assert!( std::mem::offset_of!( FIO_rust_compress_multiple_separate_projection_t, compress_mirrored_file ) == 4 * size_of::() ); assert!( std::mem::offset_of!( FIO_rust_compress_multiple_separate_projection_t, compress_flat_file ) == 5 * size_of::() ); assert!(size_of::() <= size_of::()); assert!(size_of::() == size_of::()); assert!( size_of::() == 6 * size_of::() ); }; pub type FIO_rust_decompress_multiple_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int; pub type FIO_rust_decompress_multiple_separate_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; /// Rust owns only the shared-destination file iteration and aggregate error /// handling. C retains source opening, format dispatch, diagnostics, and /// source removal through the per-source callback. #[repr(C)] pub struct FIO_rust_decompress_multiple_projection_t { pub f_ctx: *mut c_void, pub src_names_table: *const *const c_char, pub out_file_name: *const c_char, pub opaque: *mut c_void, pub decompress_file: Option, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, f_ctx) == 0); assert!( std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, src_names_table) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, out_file_name) == 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, opaque) == 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, decompress_file) == 4 * size_of::() ); assert!(size_of::() == size_of::()); 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. #[repr(C)] pub struct FIO_rust_decompress_multiple_separate_projection_t { pub f_ctx: *mut c_void, pub src_names_table: *const *const c_char, pub opaque: *mut c_void, pub decompress_file: Option, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_decompress_multiple_separate_projection_t, f_ctx) == 0); assert!( std::mem::offset_of!( FIO_rust_decompress_multiple_separate_projection_t, src_names_table ) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_multiple_separate_projection_t, opaque) == 2 * size_of::() ); assert!( std::mem::offset_of!( FIO_rust_decompress_multiple_separate_projection_t, decompress_file ) == 3 * size_of::() ); assert!(size_of::() == size_of::()); assert!( size_of::() == 4 * size_of::() ); }; pub const FIO_RUST_DECOMPRESS_SRC_OPEN_OK: c_int = 0; const FIO_RUST_DECOMPRESS_SRC_ASYNC_THRESHOLD: u64 = (1 << 17) * 3; const FIO_RUST_DECOMPRESS_SRC_UNKNOWN_SIZE: u64 = u64::MAX; pub type FIO_rust_decompress_src_open_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *mut u64, *mut c_int) -> c_int; pub type FIO_rust_decompress_src_directory_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_rust_decompress_src_async_fn = unsafe extern "C" fn(*mut c_void, c_int); pub type FIO_rust_decompress_src_attach_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_decompress_src_close_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_decompress_dst_open_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, c_int, *mut c_int) -> c_int; pub type FIO_rust_decompress_dst_attach_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_decompress_handler_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_decompress_file_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int; pub type FIO_rust_decompress_dst_stat_fn = unsafe extern "C" fn(*mut c_void, c_int, *const c_char); pub type FIO_rust_decompress_dst_close_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_decompress_dst_remove_fn = unsafe extern "C" fn(*mut c_void, *const c_char); pub type FIO_rust_decompress_src_remove_fn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; /// C-owned resources and private I/O handles are projected as callbacks while /// Rust owns the per-file decompression ordering and result policy. #[repr(C)] pub struct FIO_rust_decompress_file_projection_t { pub opaque: *mut c_void, pub dst_file_name: *const c_char, pub src_file_name: *const c_char, pub destination_already_open: c_int, pub test_mode: c_int, pub source_is_stdin: c_int, pub destination_is_stdout: c_int, pub remove_source: c_int, pub open_source: Option, pub set_async: Option, pub attach_source: Option, pub detach_source: Option, pub close_source: Option, pub open_destination: Option, pub attach_destination: Option, pub add_handler: Option, pub decompress: Option, pub clear_handler: Option, pub set_fd_stat: Option, pub close_destination: Option, pub utime_destination: Option, pub remove_destination: Option, pub remove_source_file: Option, pub source_is_directory: Option, } const FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET: usize = (3 * size_of::() + 5 * size_of::() + size_of::() - 1) & !(size_of::() - 1); const _: () = { assert!(std::mem::offset_of!(FIO_rust_decompress_file_projection_t, opaque) == 0); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, dst_file_name) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, src_file_name) == 2 * size_of::() ); assert!( std::mem::offset_of!( FIO_rust_decompress_file_projection_t, destination_already_open ) == 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, test_mode) == 3 * size_of::() + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, source_is_stdin) == 3 * size_of::() + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, destination_is_stdout) == 3 * size_of::() + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_source) == 3 * size_of::() + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, open_source) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, set_async) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, attach_source) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, detach_source) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 3 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, close_source) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, open_destination) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 5 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, attach_destination) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 6 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, add_handler) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 7 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, decompress) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + usize::BITS as usize ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, clear_handler) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 9 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, set_fd_stat) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 10 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, close_destination) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 11 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, utime_destination) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 12 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_destination) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 13 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_source_file) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 14 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_decompress_file_projection_t, source_is_directory) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 15 * size_of::() ); assert!( size_of::() == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 16 * size_of::() ); }; pub const FIO_RUST_ZSTD_OK: c_int = 0; pub const FIO_RUST_ZSTD_COMPRESS_ERROR: c_int = 1; pub const FIO_RUST_ZSTD_INCOMPLETE_INPUT: c_int = 2; pub const FIO_RUST_ZSTD_INVALID_PROJECTION: c_int = 3; pub const FIO_RUST_ZSTD_DIAGNOSTIC_OK: c_int = 0; pub const FIO_RUST_ZSTD_DIAGNOSTIC_COMPRESS_ERROR: c_int = 1; pub const FIO_RUST_ZSTD_DIAGNOSTIC_INCOMPLETE_INPUT: c_int = 2; pub const FIO_RUST_ZSTD_DIAGNOSTIC_INVALID_PROJECTION: c_int = 3; pub const FIO_RUST_ZSTD_DIAGNOSTIC_UNKNOWN: c_int = 4; /// Classifies zstd compression results while leaving zstd-specific error /// names and CLI exception text in the C callback. #[no_mangle] pub extern "C" fn FIO_rust_zstdCompressionDiagnostic(status: c_int) -> c_int { match status { FIO_RUST_ZSTD_OK => FIO_RUST_ZSTD_DIAGNOSTIC_OK, FIO_RUST_ZSTD_COMPRESS_ERROR => FIO_RUST_ZSTD_DIAGNOSTIC_COMPRESS_ERROR, FIO_RUST_ZSTD_INCOMPLETE_INPUT => FIO_RUST_ZSTD_DIAGNOSTIC_INCOMPLETE_INPUT, FIO_RUST_ZSTD_INVALID_PROJECTION => FIO_RUST_ZSTD_DIAGNOSTIC_INVALID_PROJECTION, _ => FIO_RUST_ZSTD_DIAGNOSTIC_UNKNOWN, } } const FIO_RUST_ZSTD_E_CONTINUE: c_int = 0; const FIO_RUST_ZSTD_E_END: c_int = 2; const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX; pub type FIO_rust_zstd_read_fill_fn = unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize) -> usize; pub type FIO_rust_zstd_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize); pub type FIO_rust_zstd_write_acquire_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize); pub type FIO_rust_zstd_write_enqueue_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize); pub type FIO_rust_zstd_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void); pub type FIO_rust_zstd_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_zstd_compress_stream_fn = unsafe extern "C" fn( *mut c_void, *const c_char, c_int, *const u8, usize, usize, *mut u8, usize, *mut usize, *mut usize, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_zstd_compress_display_fn = unsafe extern "C" fn(c_int, usize, usize, usize); pub type FIO_rust_zstd_iteration_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_int, usize, usize, usize); pub const FIO_RUST_ZSTD_ADAPT_NO_CHANGE: c_int = 0; pub const FIO_RUST_ZSTD_ADAPT_SLOWER: c_int = 1; pub const FIO_RUST_ZSTD_ADAPT_FASTER: c_int = 2; pub const FIO_RUST_ZSTD_ADAPT_INVALID_PROJECTION: c_int = -1; pub const FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED: c_int = 0; pub const FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG: c_int = 1; pub const FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION: c_int = 2; pub const FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT: c_int = 3; pub const FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER: c_int = 4; pub const FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER: c_int = 5; pub const FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED: c_int = 0; pub const FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG: c_int = 1; pub const FIO_RUST_ZSTD_ADAPT_DIAG_CHECK: c_int = 2; pub const FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION: c_int = 3; pub const FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STATS: c_int = 4; pub const FIO_RUST_ZSTD_ADAPT_DIAG_RECOMMEND_FASTER: c_int = 5; pub const FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL: c_int = 6; pub const FIO_RUST_ZSTD_ADAPT_DIAG_FASTER_LEVEL: c_int = 7; /// Scalar inputs for the adaptive decision policy. C keeps the /// `FIO_rust_zstd_projection_context_t`, `ZSTD_frameProgression`, and /// preference layouts private; this projection carries only the values used /// by the policy predicates and level clamps. #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct FIO_rust_zstd_adapt_projection_t { pub consumed: u64, pub previous_consumed: u64, pub nb_active_workers: c_uint, pub newly_produced: u64, pub newly_flushed: u64, pub flush_waiting: c_uint, pub input_blocked: c_uint, pub input_presented: c_uint, pub newly_ingested: u64, pub newly_consumed: u64, pub compression_level: c_int, pub min_adapt_level: c_int, pub max_adapt_level: c_int, pub max_c_level: c_int, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, consumed) == 0); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, previous_consumed) == size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, nb_active_workers) == 2 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_produced) == if size_of::() == 8 { 24 } else { 20 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_flushed) == if size_of::() == 8 { 32 } else { 28 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, flush_waiting) == if size_of::() == 8 { 40 } else { 36 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, input_blocked) == if size_of::() == 8 { 44 } else { 40 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, input_presented) == if size_of::() == 8 { 48 } else { 44 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_ingested) == if size_of::() == 8 { 56 } else { 48 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_consumed) == if size_of::() == 8 { 64 } else { 56 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, compression_level) == if size_of::() == 8 { 72 } else { 64 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, min_adapt_level) == if size_of::() == 8 { 76 } else { 68 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, max_adapt_level) == if size_of::() == 8 { 80 } else { 72 } ); assert!( std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, max_c_level) == if size_of::() == 8 { 84 } else { 76 } ); assert!( size_of::() == if size_of::() == 8 { 88 } else { 80 } ); }; /// Scalar frame progression returned by C's progression callback. The public /// C structure itself never crosses this boundary. #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct FIO_rust_zstd_progression_t { pub ingested: u64, pub consumed: u64, pub produced: u64, pub flushed: u64, pub current_job_id: c_uint, pub nb_active_workers: c_uint, } const _: () = { assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, ingested) == 0); assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, consumed) == size_of::()); assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, produced) == 2 * size_of::()); assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, flushed) == 3 * size_of::()); assert!( std::mem::offset_of!(FIO_rust_zstd_progression_t, current_job_id) == 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_progression_t, nb_active_workers) == 4 * size_of::() + size_of::() ); assert!( size_of::() == 4 * size_of::() + 2 * size_of::() ); }; pub type FIO_rust_zstd_adaptive_progression_fn = unsafe extern "C" fn(*mut c_void, *mut FIO_rust_zstd_progression_t); pub type FIO_rust_zstd_adaptive_set_parameter_fn = unsafe extern "C" fn(*mut c_void, c_int); pub type FIO_rust_zstd_adaptive_diagnostic_fn = unsafe extern "C" fn(*mut c_void, c_int, *const FIO_rust_zstd_adapt_projection_t); /// Rust owns the scalar adaptive state, progression ordering, and level /// decision. C retains diagnostics and all codec/private state in callbacks. #[repr(C)] pub struct FIO_rust_zstd_compress_projection_t { pub read_opaque: *mut c_void, pub write_opaque: *mut c_void, pub codec_opaque: *mut c_void, pub policy_opaque: *mut c_void, pub read_buffer_size: usize, pub read_fill: Option, pub read_consume: Option, pub write_acquire: Option, pub write_enqueue: Option, pub write_release: Option, pub sparse_write_end: Option, pub compress_stream: Option, pub iteration: Option, pub compress_display: Option, pub adaptive_mode: c_int, pub nb_workers: c_int, pub min_adapt_level: c_int, pub max_adapt_level: c_int, pub max_c_level: c_int, pub adaptive_progression: Option, pub adaptive_set_parameter: Option, pub adaptive_diagnostic: Option, } const _: () = { let callback_offset = (14 * size_of::() + 5 * size_of::()) .div_ceil(size_of::()) * size_of::(); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_mode) == 14 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, nb_workers) == 14 * size_of::() + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, max_c_level) == 14 * size_of::() + 4 * size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_progression) == callback_offset ); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_set_parameter) == callback_offset + size_of::() ); assert!( std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_diagnostic) == callback_offset + 2 * size_of::() ); assert!( size_of::() == callback_offset + 3 * size_of::() ); }; pub const FIO_RUST_GZIP_OK: c_int = 0; pub const FIO_RUST_GZIP_INIT_ERROR: c_int = 1; pub const FIO_RUST_GZIP_DEFLATE_ERROR: c_int = 2; pub const FIO_RUST_GZIP_FINISH_ERROR: c_int = 3; pub const FIO_RUST_GZIP_END_ERROR: c_int = 4; pub const FIO_RUST_GZIP_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_GZIP_DIAGNOSTIC_OK: c_int = 0; pub const FIO_RUST_GZIP_DIAGNOSTIC_INIT_ERROR: c_int = 1; pub const FIO_RUST_GZIP_DIAGNOSTIC_DEFLATE_ERROR: c_int = 2; pub const FIO_RUST_GZIP_DIAGNOSTIC_FINISH_ERROR: c_int = 3; pub const FIO_RUST_GZIP_DIAGNOSTIC_END_ERROR: c_int = 4; pub const FIO_RUST_GZIP_DIAGNOSTIC_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_GZIP_DIAGNOSTIC_UNKNOWN: c_int = 6; /// Classifies gzip compression results while leaving zlib error values and /// the CLI exception text in the C callback. #[no_mangle] pub extern "C" fn FIO_rust_gzipCompressionDiagnostic(status: c_int) -> c_int { match status { FIO_RUST_GZIP_OK => FIO_RUST_GZIP_DIAGNOSTIC_OK, FIO_RUST_GZIP_INIT_ERROR => FIO_RUST_GZIP_DIAGNOSTIC_INIT_ERROR, FIO_RUST_GZIP_DEFLATE_ERROR => FIO_RUST_GZIP_DIAGNOSTIC_DEFLATE_ERROR, FIO_RUST_GZIP_FINISH_ERROR => FIO_RUST_GZIP_DIAGNOSTIC_FINISH_ERROR, FIO_RUST_GZIP_END_ERROR => FIO_RUST_GZIP_DIAGNOSTIC_END_ERROR, FIO_RUST_GZIP_INVALID_PROJECTION => FIO_RUST_GZIP_DIAGNOSTIC_INVALID_PROJECTION, _ => FIO_RUST_GZIP_DIAGNOSTIC_UNKNOWN, } } const FIO_RUST_GZIP_Z_OK: c_int = 0; const FIO_RUST_GZIP_Z_STREAM_END: c_int = 1; const FIO_RUST_GZIP_Z_BUF_ERROR: c_int = -5; const FIO_RUST_GZIP_Z_NO_FLUSH: c_int = 0; const FIO_RUST_GZIP_Z_FINISH: c_int = 4; const FIO_RUST_GZIP_BEST_COMPRESSION: c_int = 9; pub type FIO_rust_gzip_read_fill_fn = unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize); pub type FIO_rust_gzip_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize); pub type FIO_rust_gzip_write_acquire_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize); pub type FIO_rust_gzip_write_enqueue_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize); pub type FIO_rust_gzip_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void); pub type FIO_rust_gzip_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_gzip_zlib_init_fn = unsafe extern "C" fn(*mut c_void, c_int) -> c_int; pub type FIO_rust_gzip_zlib_deflate_fn = unsafe extern "C" fn( *mut c_void, *const u8, usize, *mut u8, usize, c_int, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_gzip_zlib_end_fn = unsafe extern "C" fn(*mut c_void) -> c_int; pub type FIO_rust_gzip_progress_fn = unsafe extern "C" fn(*mut c_void, u64, u64, u64); /// Rust owns the gzip read/deflate/flush loop. C supplies opaque pool and /// zlib operations so neither the private `cRess_t` nor `z_stream` layout /// crosses this ABI. #[repr(C)] pub struct FIO_rust_gzip_compress_projection_t { pub read_opaque: *mut c_void, pub write_opaque: *mut c_void, pub zlib_opaque: *mut c_void, pub progress_opaque: *mut c_void, pub read_buffer_size: usize, pub read_fill: Option, pub read_consume: Option, pub write_acquire: Option, pub write_enqueue: Option, pub write_release: Option, pub sparse_write_end: Option, pub zlib_init: Option, pub zlib_deflate: Option, pub zlib_end: Option, pub progress: Option, } pub const FIO_RUST_LZMA_OK: c_int = 0; pub const FIO_RUST_LZMA_INIT_PRESET_ERROR: c_int = 1; pub const FIO_RUST_LZMA_INIT_ALONE_ERROR: c_int = 2; pub const FIO_RUST_LZMA_INIT_XZ_ERROR: c_int = 3; pub const FIO_RUST_LZMA_CODE_ERROR: c_int = 4; pub const FIO_RUST_LZMA_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_LZMA_DIAGNOSTIC_OK: c_int = 0; pub const FIO_RUST_LZMA_DIAGNOSTIC_INIT_PRESET_ERROR: c_int = 1; pub const FIO_RUST_LZMA_DIAGNOSTIC_INIT_ALONE_ERROR: c_int = 2; pub const FIO_RUST_LZMA_DIAGNOSTIC_INIT_XZ_ERROR: c_int = 3; pub const FIO_RUST_LZMA_DIAGNOSTIC_CODE_ERROR: c_int = 4; pub const FIO_RUST_LZMA_DIAGNOSTIC_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_LZMA_DIAGNOSTIC_UNKNOWN: c_int = 6; /// Classifies LZMA compression statuses without owning the C CLI's messages. /// The operational status values remain unchanged for the existing ABI. #[no_mangle] pub extern "C" fn FIO_rust_lzmaCompressionDiagnostic(status: c_int) -> c_int { match status { FIO_RUST_LZMA_OK => FIO_RUST_LZMA_DIAGNOSTIC_OK, FIO_RUST_LZMA_INIT_PRESET_ERROR => FIO_RUST_LZMA_DIAGNOSTIC_INIT_PRESET_ERROR, FIO_RUST_LZMA_INIT_ALONE_ERROR => FIO_RUST_LZMA_DIAGNOSTIC_INIT_ALONE_ERROR, FIO_RUST_LZMA_INIT_XZ_ERROR => FIO_RUST_LZMA_DIAGNOSTIC_INIT_XZ_ERROR, FIO_RUST_LZMA_CODE_ERROR => FIO_RUST_LZMA_DIAGNOSTIC_CODE_ERROR, FIO_RUST_LZMA_INVALID_PROJECTION => FIO_RUST_LZMA_DIAGNOSTIC_INVALID_PROJECTION, _ => FIO_RUST_LZMA_DIAGNOSTIC_UNKNOWN, } } const FIO_RUST_LZMA_OK_CODE: c_int = 0; const FIO_RUST_LZMA_STREAM_END: c_int = 1; const FIO_RUST_LZMA_RUN: c_int = 0; const FIO_RUST_LZMA_FINISH: c_int = 3; pub type FIO_rust_lzma_read_fill_fn = unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize); pub type FIO_rust_lzma_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize); pub type FIO_rust_lzma_write_acquire_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize); pub type FIO_rust_lzma_write_enqueue_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize); pub type FIO_rust_lzma_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void); pub type FIO_rust_lzma_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_lzma_init_fn = unsafe extern "C" fn(*mut c_void, c_int, c_int, *mut c_int) -> c_int; pub type FIO_rust_lzma_code_fn = unsafe extern "C" fn( *mut c_void, *const u8, usize, *mut u8, usize, c_int, *mut usize, *mut usize, ) -> c_int; pub type FIO_rust_lzma_end_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_lzma_progress_fn = unsafe extern "C" fn(*mut c_void, u64, u64, u64); /// Rust owns the LZMA/xz read, code, finish, and accounting loop. C keeps /// the liblzma stream, asynchronous pools, sparse-output behavior, and /// diagnostics behind opaque callbacks. #[repr(C)] pub struct FIO_rust_lzma_compress_projection_t { pub read_opaque: *mut c_void, pub write_opaque: *mut c_void, pub lzma_opaque: *mut c_void, pub progress_opaque: *mut c_void, pub read_buffer_size: usize, pub read_fill: Option, pub read_consume: Option, pub write_acquire: Option, pub write_enqueue: Option, pub write_release: Option, pub sparse_write_end: Option, pub lzma_init: Option, pub lzma_code: Option, pub lzma_end: Option, pub progress: Option, } pub const FIO_RUST_LZ4_OK: c_int = 0; pub const FIO_RUST_LZ4_CREATE_ERROR: c_int = 1; pub const FIO_RUST_LZ4_HEADER_ERROR: c_int = 2; pub const FIO_RUST_LZ4_UPDATE_ERROR: c_int = 3; pub const FIO_RUST_LZ4_END_ERROR: c_int = 4; pub const FIO_RUST_LZ4_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_LZ4_DIAGNOSTIC_OK: c_int = 0; pub const FIO_RUST_LZ4_DIAGNOSTIC_CREATE_ERROR: c_int = 1; pub const FIO_RUST_LZ4_DIAGNOSTIC_HEADER_ERROR: c_int = 2; pub const FIO_RUST_LZ4_DIAGNOSTIC_UPDATE_ERROR: c_int = 3; pub const FIO_RUST_LZ4_DIAGNOSTIC_END_ERROR: c_int = 4; pub const FIO_RUST_LZ4_DIAGNOSTIC_INVALID_PROJECTION: c_int = 5; pub const FIO_RUST_LZ4_DIAGNOSTIC_UNKNOWN: c_int = 6; /// Classifies LZ4 compression statuses without owning the C CLI's messages. /// The operational status values remain unchanged for the existing ABI. #[no_mangle] pub extern "C" fn FIO_rust_lz4CompressionDiagnostic(status: c_int) -> c_int { match status { FIO_RUST_LZ4_OK => FIO_RUST_LZ4_DIAGNOSTIC_OK, FIO_RUST_LZ4_CREATE_ERROR => FIO_RUST_LZ4_DIAGNOSTIC_CREATE_ERROR, FIO_RUST_LZ4_HEADER_ERROR => FIO_RUST_LZ4_DIAGNOSTIC_HEADER_ERROR, FIO_RUST_LZ4_UPDATE_ERROR => FIO_RUST_LZ4_DIAGNOSTIC_UPDATE_ERROR, FIO_RUST_LZ4_END_ERROR => FIO_RUST_LZ4_DIAGNOSTIC_END_ERROR, FIO_RUST_LZ4_INVALID_PROJECTION => FIO_RUST_LZ4_DIAGNOSTIC_INVALID_PROJECTION, _ => FIO_RUST_LZ4_DIAGNOSTIC_UNKNOWN, } } pub type FIO_rust_lz4_create_fn = unsafe extern "C" fn(*mut c_void, c_uint, *mut usize) -> c_int; pub type FIO_rust_lz4_prepare_fn = unsafe extern "C" fn(*mut c_void, u64, c_int, c_int, usize, usize); pub type FIO_rust_lz4_begin_fn = unsafe extern "C" fn(*mut c_void, *mut u8, usize, *mut usize) -> c_int; pub type FIO_rust_lz4_update_fn = unsafe extern "C" fn(*mut c_void, *mut u8, usize, *const u8, usize, *mut usize) -> c_int; pub type FIO_rust_lz4_end_fn = unsafe extern "C" fn(*mut c_void, *mut u8, usize, *mut usize) -> c_int; pub type FIO_rust_lz4_free_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_lz4_read_fill_fn = unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize) -> usize; pub type FIO_rust_lz4_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize); pub type FIO_rust_lz4_write_acquire_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize); pub type FIO_rust_lz4_write_enqueue_fn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize); pub type FIO_rust_lz4_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void); pub type FIO_rust_lz4_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_lz4_progress_fn = unsafe extern "C" fn(*mut c_void, u64, u64, u64); /// Rust owns the LZ4 frame loop. C keeps the LZ4F context/preferences and /// asynchronous pool objects private, exposing only the operations required /// to preserve the original header, block, progress, and cleanup ordering. #[repr(C)] pub struct FIO_rust_lz4_compress_projection_t { pub read_opaque: *mut c_void, pub write_opaque: *mut c_void, pub codec_opaque: *mut c_void, pub progress_opaque: *mut c_void, pub version: c_uint, pub block_size: usize, pub create: Option, pub prepare: Option, pub begin: Option, pub update: Option, pub end: Option, pub free_context: Option, pub read_fill: Option, pub read_consume: Option, pub write_acquire: Option, pub write_enqueue: Option, pub write_release: Option, pub sparse_write_end: Option, pub progress: Option, } /// C's `FIO_prefs_t` from `programs/fileio_types.h`. /// /// `fileio_prefs.rs` contains the same C layout for the preferences API. It /// is repeated here so this module remains independently compilable; both /// types are ABI-compatible and are only passed by pointer across the C ABI. #[repr(C)] pub struct FIO_prefs_t { pub compressionType: c_int, pub sparseFileSupport: c_int, pub dictIDFlag: c_int, pub checksumFlag: c_int, pub blockSize: c_int, pub overlapLog: c_int, pub adaptiveMode: c_int, pub useRowMatchFinder: c_int, pub rsyncable: c_int, pub minAdaptLevel: c_int, pub maxAdaptLevel: c_int, pub ldmFlag: c_int, pub ldmHashLog: c_int, pub ldmMinMatch: c_int, pub ldmBucketSizeLog: c_int, pub ldmHashRateLog: c_int, pub streamSrcSize: usize, pub targetCBlockSize: usize, pub srcSizeHint: c_int, pub testMode: c_int, pub literalCompressionMode: c_int, pub removeSrcFile: c_int, pub overwrite: c_int, pub asyncIO: c_int, pub memLimit: c_uint, pub nbWorkers: c_int, pub excludeCompressedFiles: c_int, pub patchFromMode: c_int, pub contentSize: c_int, pub allowBlockDevices: c_int, pub passThrough: c_int, pub mmapDict: c_int, } /// Opaque C context handles. The actual allocations start with the C /// `IOPoolCtx_t`, `ReadPoolCtx_t`, and `WritePoolCtx_t` layouts described in /// `programs/fileio_asyncio.h`; Rust accesses them through `AbiLayout` below. #[repr(C)] pub struct IOPoolCtx_t { _opaque: [u8; 0], } #[repr(C)] pub struct ReadPoolCtx_t { _opaque: [u8; 0], } #[repr(C)] pub struct WritePoolCtx_t { _opaque: [u8; 0], } /// Public job layout from `programs/fileio_asyncio.h`. #[repr(C)] pub struct IOJob_t { pub ctx: *mut c_void, pub file: *mut libc::FILE, pub buffer: *mut c_void, pub bufferSize: usize, pub usedBufferSize: usize, pub offset: u64, } /// The compression selector updates only these public counters in C's /// private `FIO_ctx_s`. The field order is kept explicit so no C-owned /// context implementation details cross the callback boundary. #[repr(C)] struct FIO_rust_compression_context_t { nbFilesTotal: c_int, hasStdinInput: c_int, hasStdoutOutput: c_int, currFileIdx: c_int, nbFilesProcessed: c_int, totalBytesInput: usize, totalBytesOutput: usize, } pub const FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION: c_int = 0; pub const FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION: c_int = 1; type FIO_rust_multiple_summary_progress_fn = unsafe extern "C" fn(*mut c_void); type FIO_rust_compress_multiple_summary_fn = unsafe extern "C" fn(*mut c_void, c_int, u64, u64); type FIO_rust_decompress_multiple_summary_fn = unsafe extern "C" fn(*mut c_void, c_int, u64); /// C supplies only display sinks. The multi-file counters are passed as /// scalars so the summary policy never depends on private FIO/cRess/dRess /// layouts or external codec state. #[repr(C)] pub struct FIO_rust_multiple_summary_callbacks_t { pub opaque: *mut c_void, pub display_progress: Option, pub display_compression: Option, pub display_decompression: Option, } /// Runs the final multi-file summary in the same position and order as the /// original C callers: clear progress first, then select the compression or /// decompression summary sink. C retains only the exact display formatting /// callbacks, preserving diagnostics and the CLI ABI. #[no_mangle] pub unsafe extern "C" fn FIO_rust_displayMultipleFileSummary( nb_files_total: c_int, nb_files_processed: c_int, total_bytes_input: u64, total_bytes_output: u64, summary_kind: c_int, callbacks: *const FIO_rust_multiple_summary_callbacks_t, ) { if nb_files_total <= 1 || nb_files_processed < 1 { return; } assert!(!callbacks.is_null()); let callbacks = unsafe { &*callbacks }; let display_progress = callbacks .display_progress .expect("multi-file summary progress callback is required"); unsafe { display_progress(callbacks.opaque) }; match summary_kind { FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION => { let display = callbacks .display_compression .expect("compression summary callback is required"); unsafe { display( callbacks.opaque, nb_files_processed, total_bytes_input, total_bytes_output, ) }; } FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION => { let display = callbacks .display_decompression .expect("decompression summary callback is required"); unsafe { display(callbacks.opaque, nb_files_processed, total_bytes_output) }; } _ => unreachable!("invalid multi-file summary kind"), } } type PoolFunction = unsafe extern "C" fn(*mut c_void); #[cfg(not(test))] unsafe extern "C" { /// Exported by `lib/common/pool.c`; this is the C preprocessor bridge for /// `ZSTD_MULTITHREAD` used by the Rust pool implementation as well. fn ZSTD_rust_pool_is_multithreaded() -> c_int; } #[cfg(windows)] unsafe extern "C" { fn _fseeki64(file: *mut libc::FILE, offset: i64, whence: c_int) -> c_int; } #[inline] fn multithreading_enabled() -> bool { #[cfg(test)] { true } #[cfg(not(test))] { unsafe { ZSTD_rust_pool_is_multithreaded() != 0 } } } /// A Windows `CRITICAL_SECTION` layout used only to reserve the C-visible /// field. Rust owns synchronization, so no value of this type is initialized. #[cfg(windows)] #[repr(C)] struct WindowsCriticalSection { debug_info: *mut c_void, lock_count: c_long, recursion_count: c_long, owning_thread: *mut c_void, lock_semaphore: *mut c_void, spin_count: usize, } #[cfg(windows)] #[repr(C)] struct WindowsConditionVariable { ptr: *mut c_void, } #[allow(dead_code)] #[derive(Clone, Copy, Debug)] struct AbiLayout { pointer_size: usize, read_size: usize, write_size: usize, thread_pool: usize, thread_pool_active: usize, total_io_jobs: usize, prefs: usize, pool_function: usize, file: usize, io_jobs_mutex: usize, available_jobs: usize, available_jobs_count: usize, job_buffer_size: usize, write_stored_skips: usize, read_reached_eof: usize, read_next_offset: usize, read_waiting_offset: usize, read_current_job: usize, read_coalesce_buffer: usize, read_src_buffer: usize, read_src_buffer_loaded: usize, read_completed_jobs: usize, read_completed_jobs_count: usize, read_job_completed_cond: usize, } #[inline] const fn align_up(value: usize, alignment: usize) -> usize { (value + alignment - 1) & !(alignment - 1) } fn abi_layout(threaded: bool) -> AbiLayout { let pointer_size = size_of::<*mut c_void>(); let pointer_align = align_of::<*mut c_void>(); let int_size = size_of::(); let int_align = align_of::(); let word_size = size_of::(); let word_align = align_of::(); let function_size = size_of::(); let function_align = align_of::(); let (mutex_size, mutex_align, condition_size, condition_align) = if !threaded { (int_size, int_align, int_size, int_align) } else if cfg!(all(feature = "debug-pthread", unix)) { // DEBUGLEVEL >= 1 uses pthread_mutex_t*/pthread_cond_t* in // threading.h so that forgotten init/destroy calls remain visible. (pointer_size, pointer_align, pointer_size, pointer_align) } else { #[cfg(unix)] { ( size_of::(), align_of::(), size_of::(), align_of::(), ) } #[cfg(windows)] { ( size_of::(), align_of::(), size_of::(), align_of::(), ) } #[cfg(not(any(unix, windows)))] { // `threading.h` assumes POSIX for other multithreaded targets. // Keep a pointer-sized opaque reservation for such targets until // their native synchronization representation is defined. (pointer_size, pointer_align, pointer_size, pointer_align) } }; let mut offset = 0; let thread_pool = offset; offset += pointer_size; let thread_pool_active = offset; offset += int_size; let total_io_jobs = offset; offset += int_size; offset = align_up(offset, pointer_align); let prefs = offset; offset += pointer_size; offset = align_up(offset, function_align); let pool_function = offset; offset += function_size; offset = align_up(offset, pointer_align); let file = offset; offset += pointer_size; offset = align_up(offset, mutex_align); let io_jobs_mutex = offset; offset += mutex_size; offset = align_up(offset, pointer_align); let available_jobs = offset; offset += MAX_IO_JOBS * pointer_size; let available_jobs_count = offset; offset += int_size; offset = align_up(offset, word_align); let job_buffer_size = offset; offset += word_size; let base_align = pointer_align .max(word_align) .max(mutex_align) .max(int_align) .max(function_align); let base_size = align_up(offset, base_align); let write_stored_skips = base_size; let write_align = base_align.max(int_align); let write_size = align_up(write_stored_skips + size_of::(), write_align); let read_reached_eof = base_size; offset = read_reached_eof + int_size; offset = align_up(offset, align_of::()); let read_next_offset = offset; offset += size_of::(); let read_waiting_offset = offset; offset += size_of::(); let read_current_job = offset; offset += pointer_size; let read_coalesce_buffer = offset; offset += pointer_size; let read_src_buffer = offset; offset += pointer_size; offset = align_up(offset, word_align); let read_src_buffer_loaded = offset; offset += word_size; let read_completed_jobs = offset; offset += MAX_IO_JOBS * pointer_size; let read_completed_jobs_count = offset; offset += int_size; offset = align_up(offset, condition_align); let read_job_completed_cond = offset; offset += condition_size; let read_align = base_align.max(condition_align).max(align_of::()); let read_size = align_up(offset, read_align); AbiLayout { pointer_size, read_size, write_size, thread_pool, thread_pool_active, total_io_jobs, prefs, pool_function, file, io_jobs_mutex, available_jobs, available_jobs_count, job_buffer_size, write_stored_skips, read_reached_eof, read_next_offset, read_waiting_offset, read_current_job, read_coalesce_buffer, read_src_buffer, read_src_buffer_loaded, read_completed_jobs, read_completed_jobs_count, read_job_completed_cond, } } #[inline] unsafe fn read_at(base: *const u8, offset: usize) -> T { unsafe { base.add(offset).cast::().read() } } #[inline] unsafe fn write_at(base: *mut u8, offset: usize, value: T) { unsafe { base.add(offset).cast::().write(value) }; } #[inline] unsafe fn base_file(base: *const u8, layout: AbiLayout) -> *mut libc::FILE { unsafe { read_at(base, layout.file) } } #[inline] unsafe fn base_inner(base: *mut u8) -> &'static PoolInner { let inner = unsafe { read_at::<*mut PoolInner>(base, 0) }; assert!(!inner.is_null()); unsafe { &*inner } } #[inline] fn fatal(code: c_int, message: &str) -> ! { eprintln!("zstd: error {code} : {message}"); std::process::exit(code); } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum PoolKind { Read, Write, } struct QueueState { queued: VecDeque<*mut IOJob_t>, running: bool, stopping: bool, } // A job is allocated by the C allocator and remains owned by the context // until its callback has returned. The C API supplies that lifetime proof. unsafe impl Send for QueueState {} struct AsyncQueue { state: Mutex, work_available: Condvar, queue_space: Condvar, idle: Condvar, worker: Mutex>>, threaded: bool, kind: PoolKind, } unsafe impl Send for AsyncQueue {} unsafe impl Sync for AsyncQueue {} impl AsyncQueue { fn new(threaded: bool, kind: PoolKind) -> Arc { let queue = Arc::new(Self { state: Mutex::new(QueueState { queued: VecDeque::with_capacity(IO_QUEUE_SIZE), running: false, stopping: false, }), work_available: Condvar::new(), queue_space: Condvar::new(), idle: Condvar::new(), worker: Mutex::new(None), threaded, kind, }); if threaded { let worker_queue = Arc::clone(&queue); let worker = thread::Builder::new() .spawn(move || worker_queue.worker_loop()) .unwrap_or_else(|_| fatal(104, "Failed creating I/O thread pool")); *queue .worker .lock() .unwrap_or_else(|error| error.into_inner()) = Some(worker); } queue } fn worker_loop(&self) { loop { let job = { let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner()); loop { if let Some(job) = state.queued.pop_front() { state.running = true; self.queue_space.notify_one(); break job; } if state.stopping { return; } state = self .work_available .wait(state) .unwrap_or_else(|error| error.into_inner()); } }; unsafe { execute_job(self.kind, job) }; let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner()); state.running = false; self.idle.notify_all(); if state.stopping && state.queued.is_empty() { return; } } } fn enqueue(&self, job: *mut IOJob_t) { if !self.threaded { unsafe { execute_job(self.kind, job) }; return; } let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner()); while state.queued.len() >= IO_QUEUE_SIZE && !state.stopping { state = self .queue_space .wait(state) .unwrap_or_else(|error| error.into_inner()); } if state.stopping { return; } state.queued.push_back(job); self.work_available.notify_one(); } fn join(&self) { if !self.threaded { return; } let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner()); while !state.queued.is_empty() || state.running { state = self .idle .wait(state) .unwrap_or_else(|error| error.into_inner()); } } fn shutdown(&self) { if !self.threaded { return; } { let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner()); state.stopping = true; self.work_available.notify_all(); } let worker = self .worker .lock() .unwrap_or_else(|error| error.into_inner()) .take(); if let Some(worker) = worker { let _ = worker.join(); } } } impl Drop for AsyncQueue { fn drop(&mut self) { self.shutdown(); } } struct JobState { available: Vec<*mut IOJob_t>, completed: Vec<*mut IOJob_t>, } unsafe impl Send for JobState {} struct PoolInner { context: *mut u8, layout: AbiLayout, prefs: *const FIO_prefs_t, kind: PoolKind, total_jobs: usize, jobs: Mutex, jobs_changed: Condvar, queue: Option>, } unsafe impl Send for PoolInner {} unsafe impl Sync for PoolInner {} impl PoolInner { fn new( context: *mut u8, layout: AbiLayout, prefs: *const FIO_prefs_t, kind: PoolKind, pool_exists: bool, worker_thread: bool, ) -> Self { let queue = pool_exists.then(|| AsyncQueue::new(worker_thread, kind)); let total_jobs = if pool_exists { MAX_IO_JOBS } else { 2 }; Self { context, layout, prefs, kind, total_jobs, jobs: Mutex::new(JobState { available: Vec::with_capacity(total_jobs), completed: Vec::with_capacity(MAX_IO_JOBS), }), jobs_changed: Condvar::new(), queue, } } unsafe fn init_jobs(&self, buffer_size: usize) { let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); for _ in 0..self.total_jobs { let job = libc::malloc(size_of::()).cast::(); if job.is_null() { fatal(101, "Allocation error: not enough memory"); } let allocation_size = buffer_size.max(1); let buffer = libc::malloc(allocation_size); if buffer.is_null() { libc::free(job.cast()); fatal(101, "Allocation error: not enough memory"); } unsafe { job.write(IOJob_t { ctx: self.context.cast(), file: ptr::null_mut(), buffer, bufferSize: buffer_size, usedBufferSize: 0, offset: 0, }); } state.available.push(job); } unsafe { self.sync_available_locked(&state) }; } unsafe fn sync_available_locked(&self, state: &JobState) { let base = self.context; for index in 0..MAX_IO_JOBS { let job = state .available .get(index) .copied() .unwrap_or(ptr::null_mut()); unsafe { write_at( base, self.layout.available_jobs + index * self.layout.pointer_size, job.cast::(), ); } } unsafe { write_at( base, self.layout.available_jobs_count, state.available.len() as c_int, ); } } unsafe fn sync_completed_locked(&self, state: &JobState) { let base = self.context; for index in 0..MAX_IO_JOBS { let job = state .completed .get(index) .copied() .unwrap_or(ptr::null_mut()); unsafe { write_at( base, self.layout.read_completed_jobs + index * self.layout.pointer_size, job.cast::(), ); } } unsafe { write_at( base, self.layout.read_completed_jobs_count, state.completed.len() as c_int, ); } } unsafe fn acquire_job(&self) -> *mut IOJob_t { let file = unsafe { base_file(self.context, self.layout) }; let test_mode = unsafe { !self.prefs.is_null() && (*self.prefs).testMode != 0 }; assert!(!file.is_null() || test_mode); let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); while state.available.is_empty() { state = self .jobs_changed .wait(state) .unwrap_or_else(|error| error.into_inner()); } let job = state.available.pop().unwrap(); unsafe { self.sync_available_locked(&state) }; drop(state); unsafe { (*job).usedBufferSize = 0; (*job).file = file; (*job).offset = 0; } job } unsafe fn release_job(&self, job: *mut IOJob_t) { assert!(!job.is_null()); let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); debug_assert!(state.available.len() < self.total_jobs); state.available.push(job); unsafe { self.sync_available_locked(&state) }; self.jobs_changed.notify_all(); } unsafe fn add_completed(&self, job: *mut IOJob_t) { let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); debug_assert!(state.completed.len() < MAX_IO_JOBS); state.completed.push(job); unsafe { self.sync_completed_locked(&state) }; self.jobs_changed.notify_all(); } unsafe fn release_all_completed(&self) { let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); let completed = std::mem::take(&mut state.completed); state.available.extend(completed); unsafe { self.sync_available_locked(&state); self.sync_completed_locked(&state); } self.jobs_changed.notify_all(); } unsafe fn get_next_completed(&self) -> *mut IOJob_t { let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); loop { let waiting = unsafe { read_at::(self.context, self.layout.read_waiting_offset) }; if let Some(index) = state .completed .iter() .position(|job| unsafe { (**job).offset == waiting }) { let job = state.completed.swap_remove(index); unsafe { write_at( self.context, self.layout.read_waiting_offset, waiting.wrapping_add((*job).usedBufferSize as u64), ); self.sync_completed_locked(&state); } return job; } let held = usize::from( !unsafe { read_at::<*mut c_void>(self.context, self.layout.read_current_job) } .is_null(), ); let reads_in_flight = self .total_jobs .saturating_sub(state.available.len() + state.completed.len() + held); if reads_in_flight == 0 { return ptr::null_mut(); } state = self .jobs_changed .wait(state) .unwrap_or_else(|error| error.into_inner()); } } unsafe fn enqueue_job(&self, job: *mut IOJob_t) { if self.is_active() { self.queue.as_ref().unwrap().enqueue(job); } else { unsafe { execute_job(self.kind, job) }; } } unsafe fn is_active(&self) -> bool { let active = unsafe { read_at::(self.context, self.layout.thread_pool_active) }; active != 0 && self.queue.is_some() } unsafe fn join(&self) { if let Some(queue) = &self.queue { queue.join(); } } unsafe fn set_async(&self, async_mode: c_int) { assert!(async_mode == 0 || async_mode == 1); let active = unsafe { read_at::(self.context, self.layout.thread_pool_active) }; if active != async_mode { unsafe { self.join() }; unsafe { write_at(self.context, self.layout.thread_pool_active, async_mode); } } } unsafe fn destroy_jobs(&self) { unsafe { self.join() }; let mut state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); debug_assert!(state.completed.is_empty()); let available = std::mem::take(&mut state.available); drop(state); for job in available { unsafe { libc::free((*job).buffer); libc::free(job.cast()); } } } } #[inline] unsafe fn execute_job(kind: PoolKind, job: *mut IOJob_t) { match kind { PoolKind::Read => unsafe { execute_read_job(job) }, PoolKind::Write => unsafe { execute_write_job(job) }, } } unsafe extern "C" fn read_pool_callback(opaque: *mut c_void) { unsafe { execute_read_job(opaque.cast()) }; } unsafe extern "C" fn write_pool_callback(opaque: *mut c_void) { unsafe { execute_write_job(opaque.cast()) }; } unsafe fn create_context(prefs: *const FIO_prefs_t, buffer_size: usize, kind: PoolKind) -> *mut u8 { assert!(!prefs.is_null()); let configured_threading = multithreading_enabled(); let layout = abi_layout(configured_threading); let context_size = match kind { PoolKind::Read => layout.read_size, PoolKind::Write => layout.write_size, }; let context = libc::malloc(context_size).cast::(); if context.is_null() { fatal(100, "Allocation error: not enough memory"); } unsafe { ptr::write_bytes(context, 0, context_size) }; let pool_exists = unsafe { (*prefs).asyncIO != 0 }; let inner = Box::new(PoolInner::new( context, layout, prefs, kind, pool_exists, pool_exists && configured_threading, )); let inner = Box::into_raw(inner); unsafe { write_at(context, layout.thread_pool, inner); write_at(context, layout.thread_pool_active, c_int::from(pool_exists)); write_at( context, layout.total_io_jobs, if pool_exists { MAX_IO_JOBS as c_int } else { 2 }, ); write_at(context, layout.prefs, prefs); write_at( context, layout.pool_function, match kind { PoolKind::Read => read_pool_callback, PoolKind::Write => write_pool_callback, } as PoolFunction, ); write_at(context, layout.file, ptr::null_mut::()); write_at(context, layout.available_jobs_count, 0 as c_int); write_at(context, layout.job_buffer_size, buffer_size); } unsafe { (&*inner).init_jobs(buffer_size) }; if kind == PoolKind::Write { unsafe { write_at(context, layout.write_stored_skips, 0 as c_uint) }; } else { let coalesce_size = buffer_size .checked_mul(2) .unwrap_or_else(|| fatal(100, "Allocation error: not enough memory")); let coalesce = libc::malloc(coalesce_size.max(1)).cast::(); if coalesce.is_null() { fatal(100, "Allocation error: not enough memory"); } unsafe { write_at(context, layout.read_reached_eof, 0 as c_int); write_at(context, layout.read_next_offset, 0_u64); write_at(context, layout.read_waiting_offset, 0_u64); write_at(context, layout.read_current_job, ptr::null_mut::()); write_at(context, layout.read_coalesce_buffer, coalesce); write_at(context, layout.read_src_buffer, coalesce); write_at(context, layout.read_src_buffer_loaded, 0_usize); write_at(context, layout.read_completed_jobs_count, 0 as c_int); } } context } #[inline] unsafe fn inner_for_job(job: *mut IOJob_t) -> &'static PoolInner { assert!(!job.is_null()); unsafe { base_inner((*job).ctx.cast()) } } #[inline] unsafe fn prefs_for(inner: &PoolInner) -> &FIO_prefs_t { assert!(!inner.prefs.is_null()); unsafe { &*inner.prefs } } #[inline] unsafe fn seek_relative(file: *mut libc::FILE, mut amount: u64) -> bool { while amount != 0 { let step = amount.min(SPARSE_SKIP_CHUNK); #[cfg(unix)] let result = unsafe { libc::fseeko(file, step as libc::off_t, libc::SEEK_CUR) }; #[cfg(windows)] let result = unsafe { _fseeki64(file, step as i64, libc::SEEK_CUR) }; #[cfg(not(any(unix, windows)))] let result = unsafe { libc::fseek(file, step as c_long, libc::SEEK_CUR) }; if result != 0 { return false; } amount -= step; } true } unsafe fn write_exact(file: *mut libc::FILE, buffer: *const c_void, size: usize, code: c_int) { if size != 0 && unsafe { libc::fwrite(buffer, 1, size, file) } != size { fatal(code, "Write error"); } } unsafe fn sparse_write( file: *mut libc::FILE, buffer: *const c_void, buffer_size: usize, prefs: &FIO_prefs_t, mut stored_skips: c_uint, ) -> c_uint { if prefs.testMode != 0 { return 0; } assert!(!file.is_null()); if prefs.sparseFileSupport == 0 { unsafe { write_exact(file, buffer, buffer_size, 70) }; return 0; } if u64::from(stored_skips) > SPARSE_SKIP_CHUNK { if !unsafe { seek_relative(file, SPARSE_SKIP_CHUNK) } { fatal(91, "1 GB skip error (sparse file support)"); } stored_skips = stored_skips.wrapping_sub(SPARSE_SKIP_CHUNK as c_uint); } let word_size = size_of::(); let segment_words = SPARSE_SEGMENT_SIZE / word_size; let word_count = buffer_size / word_size; let words = buffer.cast::(); let mut processed_words = 0; let mut remaining_words = word_count; while remaining_words != 0 { let segment = remaining_words.min(segment_words); let mut leading = 0; while leading < segment && unsafe { words.add(processed_words + leading).read() } == 0 { leading += 1; } stored_skips = stored_skips.wrapping_add((leading * word_size) as c_uint); if leading != segment { let nonzero_words = segment - leading; if !unsafe { seek_relative(file, u64::from(stored_skips)) } { fatal(92, "Sparse skip error; try --no-sparse"); } stored_skips = 0; let write_ptr = unsafe { words.add(processed_words + leading).cast::() }; if unsafe { libc::fwrite(write_ptr, word_size, nonzero_words, file) } != nonzero_words { fatal(93, "Write error: cannot write block"); } } processed_words += segment; remaining_words -= segment; } let remainder = buffer_size & (word_size - 1); if remainder != 0 { let rest_start = unsafe { buffer.cast::().add(word_count * word_size) }; let mut leading = 0; while leading < remainder && unsafe { rest_start.add(leading).read() } == 0 { leading += 1; } stored_skips = stored_skips.wrapping_add(leading as c_uint); if leading != remainder { if !unsafe { seek_relative(file, u64::from(stored_skips)) } { fatal(92, "Sparse skip error; try --no-sparse"); } let rest = unsafe { rest_start.add(leading) }; unsafe { write_exact(file, rest.cast(), remainder - leading, 95) }; stored_skips = 0; } } stored_skips } unsafe fn sparse_write_end(file: *mut libc::FILE, prefs: &FIO_prefs_t, stored_skips: c_uint) { if prefs.testMode != 0 { debug_assert_eq!(stored_skips, 0); return; } if stored_skips == 0 { return; } assert!(!file.is_null()); if !unsafe { seek_relative(file, u64::from(stored_skips - 1)) } { fatal(69, "Final skip error (sparse file support)"); } let zero = [0_u8; 1]; unsafe { write_exact(file, zero.as_ptr().cast(), 1, 69) }; } unsafe fn execute_write_job(job: *mut IOJob_t) { let inner = unsafe { inner_for_job(job) }; let stored = unsafe { read_at::(inner.context, inner.layout.write_stored_skips) }; let prefs = unsafe { prefs_for(inner) }; let new_stored = unsafe { sparse_write( (*job).file, (*job).buffer, (*job).usedBufferSize, prefs, stored, ) }; unsafe { write_at(inner.context, inner.layout.write_stored_skips, new_stored); inner.release_job(job); } } unsafe fn execute_read_job(job: *mut IOJob_t) { let inner = unsafe { inner_for_job(job) }; let reached_eof = unsafe { read_at::(inner.context, inner.layout.read_reached_eof) }; if reached_eof != 0 { unsafe { (*job).usedBufferSize = 0; inner.add_completed(job); } return; } let file = unsafe { (*job).file }; let size = unsafe { (*job).bufferSize }; let read = if file.is_null() || size == 0 { 0 } else { unsafe { libc::fread((*job).buffer, 1, size, file) } }; unsafe { (*job).usedBufferSize = read }; if read < size { if !file.is_null() && unsafe { libc::ferror(file) } != 0 { fatal(37, "Read error"); } else if !file.is_null() && unsafe { libc::feof(file) } != 0 { unsafe { write_at(inner.context, inner.layout.read_reached_eof, 1 as c_int) }; } else if !file.is_null() && size != 0 { fatal(37, "Unexpected short read"); } else { unsafe { write_at(inner.context, inner.layout.read_reached_eof, 1 as c_int) }; } } unsafe { inner.add_completed(job) }; } unsafe fn read_enqueue(inner: &PoolInner) { let job = unsafe { inner.acquire_job() }; let next = unsafe { read_at::(inner.context, inner.layout.read_next_offset) }; unsafe { (*job).offset = next; write_at( inner.context, inner.layout.read_next_offset, next.wrapping_add((*job).bufferSize as u64), ); inner.enqueue_job(job); } } unsafe fn read_start(inner: &PoolInner) { while unsafe { read_at::(inner.context, inner.layout.available_jobs_count) } > 0 { unsafe { read_enqueue(inner) }; } } unsafe fn read_release_current_and_get_next(inner: &PoolInner) -> *mut IOJob_t { let current = unsafe { read_at::<*mut IOJob_t>(inner.context, inner.layout.read_current_job) }; if !current.is_null() { unsafe { inner.release_job(current); write_at( inner.context, inner.layout.read_current_job, ptr::null_mut::(), ); read_enqueue(inner); } } let next = unsafe { inner.get_next_completed() }; unsafe { write_at(inner.context, inner.layout.read_current_job, next); } next } #[no_mangle] pub extern "C" fn AIO_supported() -> c_int { c_int::from(multithreading_enabled()) } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_releaseIoJob(job: *mut IOJob_t) { assert!(!job.is_null()); let inner = unsafe { inner_for_job(job) }; unsafe { inner.release_job(job) }; } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_acquireJob(ctx: *mut WritePoolCtx_t) -> *mut IOJob_t { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; unsafe { inner.acquire_job() } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_enqueueAndReacquireWriteJob(job: *mut *mut IOJob_t) { assert!(!job.is_null()); let queued = unsafe { *job }; assert!(!queued.is_null()); let inner = unsafe { inner_for_job(queued) }; unsafe { inner.enqueue_job(queued) }; unsafe { *job = inner.acquire_job() }; } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_sparseWriteEnd(ctx: *mut WritePoolCtx_t) { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; unsafe { inner.join() }; let stored = unsafe { read_at::(inner.context, inner.layout.write_stored_skips) }; let prefs = unsafe { prefs_for(inner) }; let file = unsafe { base_file(inner.context, inner.layout) }; unsafe { sparse_write_end(file, prefs, stored) }; unsafe { write_at(inner.context, inner.layout.write_stored_skips, 0 as c_uint); } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_setFile(ctx: *mut WritePoolCtx_t, file: *mut libc::FILE) { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; unsafe { inner.join() }; debug_assert!(unsafe { inner.all_jobs_available() }); debug_assert_eq!( unsafe { read_at::(inner.context, inner.layout.write_stored_skips) }, 0 ); unsafe { write_at(inner.context, inner.layout.file, file) }; } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_getFile(ctx: *const WritePoolCtx_t) -> *mut libc::FILE { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast_mut().cast()) }; unsafe { base_file(inner.context, inner.layout) } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_closeFile(ctx: *mut WritePoolCtx_t) -> c_int { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; let file = unsafe { base_file(inner.context, inner.layout) }; unsafe { AIO_WritePool_sparseWriteEnd(ctx) }; unsafe { write_at( inner.context, inner.layout.file, ptr::null_mut::(), ) }; if file.is_null() { return -1; } unsafe { libc::fclose(file) } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_create( prefs: *const FIO_prefs_t, buffer_size: usize, ) -> *mut WritePoolCtx_t { unsafe { create_context(prefs, buffer_size, PoolKind::Write).cast() } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_free(ctx: *mut WritePoolCtx_t) { if ctx.is_null() { return; } let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let file = unsafe { base_file(context, inner.layout) }; if !file.is_null() { unsafe { AIO_WritePool_closeFile(ctx) }; } let inner_ptr = unsafe { read_at::<*mut PoolInner>(context, inner.layout.thread_pool) }; unsafe { (&*inner_ptr).destroy_jobs() }; unsafe { drop(Box::from_raw(inner_ptr)); libc::free(context.cast()); } } #[no_mangle] pub unsafe extern "C" fn AIO_WritePool_setAsync(ctx: *mut WritePoolCtx_t, async_mode: c_int) { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; unsafe { inner.set_async(async_mode) }; } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_create( prefs: *const FIO_prefs_t, buffer_size: usize, ) -> *mut ReadPoolCtx_t { unsafe { create_context(prefs, buffer_size, PoolKind::Read).cast() } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_free(ctx: *mut ReadPoolCtx_t) { if ctx.is_null() { return; } let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let file = unsafe { base_file(context, inner.layout) }; if !file.is_null() { unsafe { AIO_ReadPool_closeFile(ctx) }; } else { unsafe { inner.join() }; unsafe { inner.release_all_completed() }; let current = unsafe { read_at::<*mut IOJob_t>(context, inner.layout.read_current_job) }; if !current.is_null() { unsafe { inner.release_job(current); write_at( context, inner.layout.read_current_job, ptr::null_mut::(), ); } } } let coalesce = unsafe { read_at::<*mut u8>(context, inner.layout.read_coalesce_buffer) }; let inner_ptr = unsafe { read_at::<*mut PoolInner>(context, inner.layout.thread_pool) }; unsafe { (&*inner_ptr).destroy_jobs() }; unsafe { libc::free(coalesce.cast()); drop(Box::from_raw(inner_ptr)); libc::free(context.cast()); } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_setAsync(ctx: *mut ReadPoolCtx_t, async_mode: c_int) { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; unsafe { inner.set_async(async_mode) }; } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_consumeBytes(ctx: *mut ReadPoolCtx_t, n: usize) { assert!(!ctx.is_null()); let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let loaded = unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }; assert!(n <= loaded); unsafe { write_at(context, inner.layout.read_src_buffer_loaded, loaded - n); if n != 0 { let src = read_at::<*mut u8>(context, inner.layout.read_src_buffer); write_at(context, inner.layout.read_src_buffer, src.add(n)); } } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_fillBuffer(ctx: *mut ReadPoolCtx_t, mut n: usize) -> usize { assert!(!ctx.is_null()); let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let buffer_size = unsafe { read_at::(context, inner.layout.job_buffer_size) }; n = n.min(buffer_size); let loaded = unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }; if loaded >= n { return 0; } let use_coalesce = loaded != 0; if use_coalesce { let src = unsafe { read_at::<*mut u8>(context, inner.layout.read_src_buffer) }; let coalesce = unsafe { read_at::<*mut u8>(context, inner.layout.read_coalesce_buffer) }; unsafe { ptr::copy(src, coalesce, loaded) }; unsafe { write_at(context, inner.layout.read_src_buffer, coalesce) }; } let job = unsafe { read_release_current_and_get_next(inner) }; if job.is_null() { return 0; } let used = unsafe { (*job).usedBufferSize }; if use_coalesce { let coalesce = unsafe { read_at::<*mut u8>(context, inner.layout.read_coalesce_buffer) }; unsafe { ptr::copy_nonoverlapping((*job).buffer.cast::(), coalesce.add(loaded), used); write_at(context, inner.layout.read_src_buffer_loaded, loaded + used); write_at(context, inner.layout.read_src_buffer, coalesce); } } else { unsafe { write_at( context, inner.layout.read_src_buffer, (*job).buffer.cast::(), ); write_at(context, inner.layout.read_src_buffer_loaded, used); } } used } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_consumeAndRefill(ctx: *mut ReadPoolCtx_t) -> usize { assert!(!ctx.is_null()); let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let loaded = unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }; unsafe { AIO_ReadPool_consumeBytes(ctx, loaded) }; unsafe { AIO_ReadPool_fillBuffer(ctx, read_at(context, inner.layout.job_buffer_size)) } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_setFile(ctx: *mut ReadPoolCtx_t, file: *mut libc::FILE) { assert!(!ctx.is_null()); let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; unsafe { inner.join() }; unsafe { inner.release_all_completed() }; let current = unsafe { read_at::<*mut IOJob_t>(context, inner.layout.read_current_job) }; if !current.is_null() { unsafe { inner.release_job(current); write_at( context, inner.layout.read_current_job, ptr::null_mut::(), ); } } debug_assert!(unsafe { inner.all_jobs_available() }); unsafe { write_at(context, inner.layout.file, file); write_at(context, inner.layout.read_next_offset, 0_u64); write_at(context, inner.layout.read_waiting_offset, 0_u64); write_at(context, inner.layout.read_reached_eof, 0 as c_int); let coalesce = read_at::<*mut u8>(context, inner.layout.read_coalesce_buffer); write_at(context, inner.layout.read_src_buffer, coalesce); write_at(context, inner.layout.read_src_buffer_loaded, 0_usize); } if !file.is_null() { unsafe { read_start(inner) }; } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_getFile(ctx: *const ReadPoolCtx_t) -> *mut libc::FILE { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast_mut().cast()) }; unsafe { base_file(inner.context, inner.layout) } } #[no_mangle] pub unsafe extern "C" fn AIO_ReadPool_closeFile(ctx: *mut ReadPoolCtx_t) -> c_int { assert!(!ctx.is_null()); let inner = unsafe { base_inner(ctx.cast()) }; let file = unsafe { base_file(inner.context, inner.layout) }; unsafe { AIO_ReadPool_setFile(ctx, ptr::null_mut()) }; if file.is_null() { return -1; } unsafe { libc::fclose(file) } } #[no_mangle] pub unsafe extern "C" fn FIO_rust_passThrough( read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, ) -> c_int { assert!(!read_ctx.is_null()); assert!(!write_ctx.is_null()); let read_context = read_ctx.cast::(); let write_context = write_ctx.cast::(); let read_inner = unsafe { base_inner(read_context) }; let write_inner = unsafe { base_inner(write_context) }; let block_size = PASS_THROUGH_MAX_BLOCK_SIZE .min(unsafe { read_at::(read_context, read_inner.layout.job_buffer_size) }) .min(unsafe { read_at::(write_context, write_inner.layout.job_buffer_size) }); let mut write_job = unsafe { AIO_WritePool_acquireJob(write_ctx) }; unsafe { AIO_ReadPool_fillBuffer(read_ctx, block_size) }; while unsafe { read_at::(read_context, read_inner.layout.read_src_buffer_loaded) } != 0 { let loaded = unsafe { read_at::(read_context, read_inner.layout.read_src_buffer_loaded) }; let write_size = block_size.min(loaded); assert!(write_size <= unsafe { (*write_job).bufferSize }); let source = unsafe { read_at::<*const u8>(read_context, read_inner.layout.read_src_buffer) }; unsafe { ptr::copy_nonoverlapping(source, (*write_job).buffer.cast::(), write_size); (*write_job).usedBufferSize = write_size; AIO_WritePool_enqueueAndReacquireWriteJob(&mut write_job); AIO_ReadPool_consumeBytes(read_ctx, write_size); AIO_ReadPool_fillBuffer(read_ctx, block_size); } } assert!(unsafe { read_at::(read_context, read_inner.layout.read_reached_eof) } != 0); unsafe { AIO_WritePool_releaseIoJob(write_job); AIO_WritePool_sparseWriteEnd(write_ctx); } 0 } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum CompressionFormat { Zstd, Gzip, Lzma { plain_lzma: c_int }, Lz4 { checksum: c_int }, } fn select_compression_format(compression_type: c_int, checksum: c_int) -> CompressionFormat { match compression_type { FIO_GZIP_COMPRESSION => CompressionFormat::Gzip, FIO_XZ_COMPRESSION => CompressionFormat::Lzma { plain_lzma: 0 }, FIO_LZMA_COMPRESSION => CompressionFormat::Lzma { plain_lzma: 1 }, FIO_LZ4_COMPRESSION => CompressionFormat::Lz4 { checksum }, FIO_ZSTD_COMPRESSION => CompressionFormat::Zstd, _ => CompressionFormat::Zstd, } } unsafe fn run_compression_callback( f_ctx: *mut c_void, prefs: *mut FIO_prefs_t, ress: *mut c_void, src_file_name: *const c_char, file_size: u64, compression_level: c_int, callbacks: &FIO_rust_compress_callbacks_t, ) -> Result<(u64, u64), c_int> { let format = select_compression_format(unsafe { (*prefs).compressionType }, unsafe { (*prefs).checksumFlag }); let mut read_size = 0_u64; let compressed_size = match format { CompressionFormat::Zstd => { let Some(callback) = callbacks.compress_zstd else { return Err(FIO_RUST_COMPRESS_ZSTD_UNSUPPORTED); }; unsafe { callback( f_ctx, prefs.cast(), ress, src_file_name, file_size, compression_level, &mut read_size, ) } } CompressionFormat::Gzip => { let Some(callback) = callbacks.compress_gzip else { return Err(FIO_RUST_COMPRESS_GZIP_UNSUPPORTED); }; unsafe { callback( ress, src_file_name, file_size, compression_level, &mut read_size, ) } } CompressionFormat::Lzma { plain_lzma } => { let Some(callback) = callbacks.compress_lzma else { return Err(FIO_RUST_COMPRESS_LZMA_UNSUPPORTED); }; unsafe { callback( ress, src_file_name, file_size, compression_level, &mut read_size, plain_lzma, ) } } CompressionFormat::Lz4 { checksum } => { let Some(callback) = callbacks.compress_lz4 else { return Err(FIO_RUST_COMPRESS_LZ4_UNSUPPORTED); }; unsafe { callback( ress, src_file_name, file_size, compression_level, checksum, &mut read_size, ) } } }; Ok((read_size, compressed_size)) } /// Runs the compression-side format selection and final file accounting. /// /// The codec implementations remain in C and are selected through the /// explicit callback table above. Rust updates the C-visible aggregate byte /// counters only after a codec returns, then asks C to perform the existing /// progress, summary, and elapsed-time display. Missing optional codecs are /// returned as status values so the C wrapper can preserve its exact /// `EXM_THROW()` diagnostics. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressFilenameInternal( f_ctx: *mut c_void, prefs: *mut FIO_prefs_t, ress: *mut c_void, dst_file_name: *const c_char, src_file_name: *const c_char, file_size: u64, compression_level: c_int, callbacks: *const FIO_rust_compress_callbacks_t, ) -> c_int { assert!(!f_ctx.is_null()); assert!(!prefs.is_null()); assert!(!ress.is_null()); assert!(!dst_file_name.is_null()); assert!(!src_file_name.is_null()); assert!(!callbacks.is_null()); let callbacks = unsafe { &*callbacks }; if let Some(display) = callbacks.display_input { unsafe { display(callbacks.opaque, src_file_name, file_size) }; } let (read_size, compressed_size) = match unsafe { run_compression_callback( f_ctx, prefs, ress, src_file_name, file_size, compression_level, callbacks, ) } { Ok(sizes) => sizes, Err(status) => return status, }; let context = unsafe { &mut *f_ctx.cast::() }; context.totalBytesInput = context.totalBytesInput.wrapping_add(read_size as usize); context.totalBytesOutput = context .totalBytesOutput .wrapping_add(compressed_size as usize); if let Some(display) = callbacks.display_status { unsafe { display( callbacks.opaque, f_ctx, dst_file_name, src_file_name, read_size, compressed_size, ) }; } FIO_RUST_COMPRESS_OK } /// Runs the source-file policy around the existing C-owned destination and /// compression callback. Rust owns the ordering: named-file checks, /// exclusion, source open, size-based async selection, source attachment, /// compression, close, and finally conditional source removal. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressFilenameSrcFile( projection: *const FIO_rust_compress_src_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.opaque.is_null()); assert!(!projection.dst_file_name.is_null()); assert!(!projection.src_file_name.is_null()); let src_file_name = projection.src_file_name; let source_is_stdin = projection.source_is_stdin != 0; if !source_is_stdin { let stat_source = projection .stat_source .expect("source stat callback is required for named files"); match unsafe { stat_source(projection.opaque, src_file_name) } { FIO_RUST_COMPRESS_SRC_STAT_FAILED | FIO_RUST_COMPRESS_SRC_STAT_OK => {} FIO_RUST_COMPRESS_SRC_DIRECTORY | FIO_RUST_COMPRESS_SRC_DICT_COLLISION => return 1, _ => unreachable!("invalid source stat status"), } } if projection.exclude_compressed_files != 0 { let source_is_excluded = projection .source_is_excluded .expect("compressed-file predicate is required when exclusion is enabled"); if unsafe { source_is_excluded(projection.opaque, src_file_name) } != 0 { return 0; } } let open_source = projection .open_source .expect("source open callback is required"); let mut file_size = FIO_RUST_COMPRESS_SRC_UNKNOWN_SIZE; let open_status = unsafe { open_source(projection.opaque, src_file_name, &mut file_size) }; if open_status != FIO_RUST_COMPRESS_SRC_OPEN_OK { return 1; } let async_mode = if file_size != FIO_RUST_COMPRESS_SRC_UNKNOWN_SIZE && file_size < FIO_RUST_COMPRESS_SRC_ASYNC_THRESHOLD { 0 } else { 1 }; let set_async = projection .set_async .expect("async-selection callback is required"); unsafe { set_async(projection.opaque, async_mode) }; let attach_source = projection .attach_source .expect("source attachment callback is required"); unsafe { attach_source(projection.opaque) }; let compress = projection .compress .expect("compression callback is required"); let result = unsafe { compress( projection.opaque, projection.dst_file_name, src_file_name, projection.compression_level, ) }; let close_source = projection .close_source .expect("source close callback is required"); unsafe { close_source(projection.opaque) }; if projection.remove_src_file != 0 && result == 0 && !source_is_stdin { let remove_source = projection .remove_source .expect("source removal callback is required when removal is enabled"); unsafe { remove_source(projection.opaque, src_file_name) }; } result } unsafe fn compress_destination(projection: &FIO_rust_compress_dst_projection_t) -> c_int { let compress = projection .compress .expect("compression callback is required"); if projection.destination_already_open != 0 { return unsafe { compress( projection.opaque, projection.dst_file_name, projection.src_file_name, projection.compression_level, ) }; } let transfer_stat = projection.source_is_stdin == 0 && projection.destination_is_stdout == 0 && projection.source_is_regular != 0; let mut destination_fd = -1; let open_destination = projection .open_destination .expect("destination open callback is required"); let open_status = unsafe { open_destination( projection.opaque, projection.src_file_name, projection.dst_file_name, c_int::from(transfer_stat), &mut destination_fd, ) }; if open_status != FIO_RUST_COMPRESS_DST_OPEN_OK { return 1; } let attach_destination = projection .attach_destination .expect("destination attachment callback is required"); unsafe { attach_destination(projection.opaque) }; let add_handler = projection .add_handler .expect("destination handler callback is required"); unsafe { add_handler(projection.opaque) }; let mut result = unsafe { compress( projection.opaque, projection.dst_file_name, projection.src_file_name, projection.compression_level, ) }; let clear_handler = projection .clear_handler .expect("handler-clear callback is required"); unsafe { clear_handler(projection.opaque) }; if transfer_stat { let set_fd_stat = projection .set_fd_stat .expect("destination stat callback is required"); unsafe { set_fd_stat(projection.opaque, destination_fd, projection.dst_file_name) }; } let close_destination = projection .close_destination .expect("destination close callback is required"); if unsafe { close_destination(projection.opaque) } != 0 { result = 1; } if transfer_stat { let utime_destination = projection .utime_destination .expect("destination timestamp callback is required"); unsafe { utime_destination(projection.opaque) }; } if result != 0 && projection.destination_is_stdout == 0 { let remove_destination = projection .remove_destination .expect("destination removal callback is required"); unsafe { remove_destination(projection.opaque, projection.dst_file_name) }; } result } /// Runs the per-file compression destination policy around C-owned resources /// and the existing format-dispatch callback. The callback order mirrors the /// original `FIO_compressFilename_dstFile()` lifecycle, including shared /// destinations, metadata, close errors, and failed-output cleanup. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressFilenameDstFile( projection: *const FIO_rust_compress_dst_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.opaque.is_null()); assert!(!projection.dst_file_name.is_null()); assert!(!projection.src_file_name.is_null()); unsafe { compress_destination(projection) } } unsafe fn decompress_destination( projection: &FIO_rust_decompress_file_projection_t, source_is_regular: c_int, ) -> c_int { let decompress = projection .decompress .expect("decompression callback is required"); let release_destination = projection.destination_already_open == 0 && projection.test_mode == 0; if !release_destination { return unsafe { decompress( projection.opaque, projection.dst_file_name, projection.src_file_name, ) }; } let transfer_stat = projection.source_is_stdin == 0 && projection.destination_is_stdout == 0 && source_is_regular != 0; let mut destination_fd = 0; let open_destination = projection .open_destination .expect("destination open callback is required"); let open_status = unsafe { open_destination( projection.opaque, projection.src_file_name, projection.dst_file_name, c_int::from(transfer_stat), &mut destination_fd, ) }; if open_status != 0 { return 1; } let attach_destination = projection .attach_destination .expect("destination attachment callback is required"); unsafe { attach_destination(projection.opaque) }; let add_handler = projection .add_handler .expect("destination handler callback is required"); unsafe { add_handler(projection.opaque) }; let mut result = unsafe { decompress( projection.opaque, projection.dst_file_name, projection.src_file_name, ) }; let clear_handler = projection .clear_handler .expect("handler-clear callback is required"); unsafe { clear_handler(projection.opaque) }; if transfer_stat { let set_fd_stat = projection .set_fd_stat .expect("destination stat callback is required"); unsafe { set_fd_stat(projection.opaque, destination_fd, projection.dst_file_name) }; } let close_destination = projection .close_destination .expect("destination close callback is required"); if unsafe { close_destination(projection.opaque) } != 0 { result = 1; } if transfer_stat { let utime_destination = projection .utime_destination .expect("destination timestamp callback is required"); unsafe { utime_destination(projection.opaque) }; } if result != 0 && projection.destination_is_stdout == 0 { let remove_destination = projection .remove_destination .expect("destination removal callback is required"); unsafe { remove_destination(projection.opaque, projection.dst_file_name) }; } result } /// Runs the source and destination policy around C-owned decompression /// resources and format callbacks. The callback order mirrors /// `FIO_decompressSrcFile()` and `FIO_decompressDstFile()` exactly. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressFilename( projection: *const FIO_rust_decompress_file_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.opaque.is_null()); assert!(!projection.dst_file_name.is_null()); assert!(!projection.src_file_name.is_null()); let source_is_directory = projection .source_is_directory .expect("source directory callback is required"); if unsafe { source_is_directory(projection.opaque, projection.src_file_name) } != 0 { return 1; } let open_source = projection .open_source .expect("source open callback is required"); let mut file_size = FIO_RUST_DECOMPRESS_SRC_UNKNOWN_SIZE; let mut source_is_regular = 0; let open_status = unsafe { open_source( projection.opaque, projection.src_file_name, &mut file_size, &mut source_is_regular, ) }; if open_status != FIO_RUST_DECOMPRESS_SRC_OPEN_OK { return 1; } let async_mode = if file_size != FIO_RUST_DECOMPRESS_SRC_UNKNOWN_SIZE && file_size < FIO_RUST_DECOMPRESS_SRC_ASYNC_THRESHOLD { 0 } else { 1 }; let set_async = projection .set_async .expect("async-selection callback is required"); unsafe { set_async(projection.opaque, async_mode) }; let attach_source = projection .attach_source .expect("source attachment callback is required"); unsafe { attach_source(projection.opaque) }; let mut result = unsafe { decompress_destination(projection, source_is_regular) }; let detach_source = projection .detach_source .expect("source detachment callback is required"); unsafe { detach_source(projection.opaque) }; let close_source = projection .close_source .expect("source close callback is required"); if unsafe { close_source(projection.opaque) } != 0 { return 1; } if projection.remove_source != 0 && result == 0 && projection.source_is_stdin == 0 { let clear_handler = projection .clear_handler .expect("handler-clear callback is required"); unsafe { clear_handler(projection.opaque) }; let remove_source = projection .remove_source_file .expect("source removal callback is required"); if unsafe { remove_source(projection.opaque, projection.src_file_name) } != 0 { result = 1; } } result } /// Iterate the files that share one already-open destination. The C /// callback retains source validation, resource state, diagnostics, and /// compression dispatch; Rust preserves the original non-short-circuiting /// loop and its counter updates. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressMultipleFilenames( projection: *const FIO_rust_compress_multiple_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.f_ctx.is_null()); assert!(!projection.input_file_names.is_null()); assert!(!projection.output_file_name.is_null()); let compress_file = projection .compress_file .expect("shared-destination compression callback is required"); let f_ctx = projection.f_ctx.cast::(); let mut error = 0; while unsafe { (*f_ctx).currFileIdx < (*f_ctx).nbFilesTotal } { let file_index = unsafe { (*f_ctx).currFileIdx as usize }; let src_file_name = unsafe { *projection.input_file_names.add(file_index) }; let status = unsafe { compress_file( projection.opaque, projection.output_file_name, src_file_name, ) }; if status == 0 { unsafe { (*f_ctx).nbFilesProcessed = (*f_ctx).nbFilesProcessed.wrapping_add(1); } } error |= status; unsafe { (*f_ctx).currFileIdx = (*f_ctx).currFileIdx.wrapping_add(1); } } 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 /// original non-short-circuiting loop and its counter updates. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressMultipleSeparateFilenames( projection: *const FIO_rust_compress_multiple_separate_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.f_ctx.is_null()); assert!(!projection.input_file_names.is_null()); let compress_file = if projection.mirror_output != 0 { projection .compress_mirrored_file .expect("mirrored separate-destination compression callback is required") } else { projection .compress_flat_file .expect("flat separate-destination compression callback is required") }; let f_ctx = projection.f_ctx.cast::(); let mut error = 0; while unsafe { (*f_ctx).currFileIdx < (*f_ctx).nbFilesTotal } { let file_index = unsafe { (*f_ctx).currFileIdx as usize }; let src_file_name = unsafe { *projection.input_file_names.add(file_index) }; let status = unsafe { compress_file(projection.opaque, src_file_name) }; if status == 0 { unsafe { (*f_ctx).nbFilesProcessed = (*f_ctx).nbFilesProcessed.wrapping_add(1); } } error |= status; unsafe { (*f_ctx).currFileIdx = (*f_ctx).currFileIdx.wrapping_add(1); } } error } /// Iterate files that share one already-open decompression destination. The C /// callback retains private source/destination operations, format dispatch, /// and diagnostics; Rust owns per-file ordering and removal policy. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenames( projection: *const FIO_rust_decompress_multiple_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.f_ctx.is_null()); assert!(!projection.src_names_table.is_null()); assert!(!projection.out_file_name.is_null()); let decompress_file = projection .decompress_file .expect("shared-destination decompression callback is required"); let f_ctx = projection.f_ctx.cast::(); let mut error = 0; while unsafe { (*f_ctx).currFileIdx < (*f_ctx).nbFilesTotal } { let file_index = unsafe { (*f_ctx).currFileIdx as usize }; let src_file_name = unsafe { *projection.src_names_table.add(file_index) }; let status = unsafe { decompress_file(projection.opaque, projection.out_file_name, src_file_name) }; if status == 0 { unsafe { (*f_ctx).nbFilesProcessed = (*f_ctx).nbFilesProcessed.wrapping_add(1); } } error |= status; unsafe { (*f_ctx).currFileIdx = (*f_ctx).currFileIdx.wrapping_add(1); } } 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. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressMultipleSeparateFilenames( projection: *const FIO_rust_decompress_multiple_separate_projection_t, ) -> c_int { assert!(!projection.is_null()); let projection = unsafe { &*projection }; assert!(!projection.f_ctx.is_null()); assert!(!projection.src_names_table.is_null()); let decompress_file = projection .decompress_file .expect("separate-destination decompression callback is required"); let f_ctx = projection.f_ctx.cast::(); let mut error = 0; while unsafe { (*f_ctx).currFileIdx < (*f_ctx).nbFilesTotal } { let file_index = unsafe { (*f_ctx).currFileIdx as usize }; let src_file_name = unsafe { *projection.src_names_table.add(file_index) }; let status = unsafe { decompress_file(projection.opaque, src_file_name) }; if status == 0 { unsafe { (*f_ctx).nbFilesProcessed = (*f_ctx).nbFilesProcessed.wrapping_add(1); } } error |= status; unsafe { (*f_ctx).currFileIdx = (*f_ctx).currFileIdx.wrapping_add(1); } } error } #[inline] fn zstd_adapt_level(projection: &FIO_rust_zstd_adapt_projection_t, slower: bool) -> c_int { if slower { let mut level = projection.compression_level.wrapping_add(1); if level > projection.max_c_level { level = projection.max_c_level; } if level > projection.max_adapt_level { level = projection.max_adapt_level; } level.wrapping_add(i32::from(level == 0)) } else { let mut level = projection.compression_level.wrapping_sub(1); if level < projection.min_adapt_level { level = projection.min_adapt_level; } level.wrapping_sub(i32::from(level == 0)) } } #[inline] fn zstd_adapt_policy(policy: c_int, projection: &FIO_rust_zstd_adapt_projection_t) -> c_int { match policy { FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED => { if projection.consumed == projection.previous_consumed && projection.nb_active_workers == 0 { FIO_RUST_ZSTD_ADAPT_SLOWER } else { FIO_RUST_ZSTD_ADAPT_NO_CHANGE } } FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG => { if projection.newly_produced > projection.newly_flushed.wrapping_mul(9) / 8 && projection.flush_waiting == 0 { FIO_RUST_ZSTD_ADAPT_SLOWER } else { FIO_RUST_ZSTD_ADAPT_NO_CHANGE } } FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION => { if projection.input_blocked == 0 { FIO_RUST_ZSTD_ADAPT_SLOWER } else { FIO_RUST_ZSTD_ADAPT_NO_CHANGE } } FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT => { if projection.input_blocked > projection.input_presented / 8 && projection.newly_flushed.wrapping_mul(33) / 32 > projection.newly_produced && projection.newly_ingested.wrapping_mul(33) / 32 > projection.newly_consumed { FIO_RUST_ZSTD_ADAPT_FASTER } else { FIO_RUST_ZSTD_ADAPT_NO_CHANGE } } FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER => zstd_adapt_level(projection, true), FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER => zstd_adapt_level(projection, false), _ => FIO_RUST_ZSTD_ADAPT_INVALID_PROJECTION, } } /// Evaluate one scalar adaptive-policy branch. C owns the caller's branch /// order and commits the returned decision or level through its private /// context and diagnostics. #[no_mangle] pub unsafe extern "C" fn FIO_rust_zstd_adapt( policy: c_int, projection: *const FIO_rust_zstd_adapt_projection_t, ) -> c_int { let Some(projection) = (unsafe { projection.as_ref() }) else { return FIO_RUST_ZSTD_ADAPT_INVALID_PROJECTION; }; zstd_adapt_policy(policy, projection) } const ZSTD_ADAPT_REFRESH_RATE: Duration = Duration::from_micros(1_000_000 / 6); #[derive(Debug, Default)] struct ZstdAdaptiveState { previous_update: FIO_rust_zstd_progression_t, previous_correction: FIO_rust_zstd_progression_t, speed_change: c_int, flush_waiting: c_uint, input_presented: c_uint, input_blocked: c_uint, last_job_id: c_uint, last_refresh: Option, } unsafe fn zstd_adaptive_report( projection: &FIO_rust_zstd_compress_projection_t, diagnostic: c_int, adapt_projection: Option<&FIO_rust_zstd_adapt_projection_t>, ) { let Some(report) = projection.adaptive_diagnostic else { return; }; let projection_ptr = adapt_projection.map_or(ptr::null(), |value| value as *const _); unsafe { report(projection.policy_opaque, diagnostic, projection_ptr); } } unsafe fn zstd_adaptive_iteration( state: &mut ZstdAdaptiveState, projection: &FIO_rust_zstd_compress_projection_t, compression_level: &mut c_int, old_input_pos: usize, new_input_pos: usize, to_flush_now: usize, ) { state.input_presented = state.input_presented.wrapping_add(1); if old_input_pos == new_input_pos { state.input_blocked = state.input_blocked.wrapping_add(1); } if to_flush_now == 0 { state.flush_waiting = 1; } if projection.adaptive_mode == 0 { return; } if let Some(last_refresh) = state.last_refresh.as_ref() { if last_refresh.elapsed() <= ZSTD_ADAPT_REFRESH_RATE { return; } } state.last_refresh = Some(Instant::now()); let Some(get_progression) = projection.adaptive_progression else { return; }; let mut progression = FIO_rust_zstd_progression_t::default(); unsafe { get_progression(projection.policy_opaque, &mut progression); } if progression.current_job_id > 1 { let newly_produced = progression .produced .wrapping_sub(state.previous_update.produced); let newly_flushed = progression .flushed .wrapping_sub(state.previous_update.flushed); assert!(progression.produced >= state.previous_update.produced); assert!(projection.nb_workers >= 1); let mut adapt_projection = FIO_rust_zstd_adapt_projection_t { consumed: progression.consumed, previous_consumed: state.previous_update.consumed, nb_active_workers: progression.nb_active_workers, ..FIO_rust_zstd_adapt_projection_t::default() }; if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED, &adapt_projection) == FIO_RUST_ZSTD_ADAPT_SLOWER { unsafe { zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED, None); } state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER; } state.previous_update = progression; adapt_projection.newly_produced = newly_produced; adapt_projection.newly_flushed = newly_flushed; adapt_projection.flush_waiting = state.flush_waiting; if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG, &adapt_projection) == FIO_RUST_ZSTD_ADAPT_SLOWER { unsafe { zstd_adaptive_report( projection, FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG, Some(&adapt_projection), ); } state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER; } state.flush_waiting = 0; } if progression.current_job_id > state.last_job_id { unsafe { zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_CHECK, None); } if progression.current_job_id > projection.nb_workers.wrapping_add(1) as c_uint { let mut adapt_projection = FIO_rust_zstd_adapt_projection_t { input_blocked: state.input_blocked, ..FIO_rust_zstd_adapt_projection_t::default() }; if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION, &adapt_projection) == FIO_RUST_ZSTD_ADAPT_SLOWER { unsafe { zstd_adaptive_report( projection, FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION, None, ); } state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER; } else if state.speed_change == FIO_RUST_ZSTD_ADAPT_NO_CHANGE { let newly_ingested = progression .ingested .wrapping_sub(state.previous_correction.ingested); let newly_consumed = progression .consumed .wrapping_sub(state.previous_correction.consumed); let newly_produced = progression .produced .wrapping_sub(state.previous_correction.produced); let newly_flushed = progression .flushed .wrapping_sub(state.previous_correction.flushed); state.previous_correction = progression; adapt_projection.input_blocked = state.input_blocked; adapt_projection.input_presented = state.input_presented; adapt_projection.newly_ingested = newly_ingested; adapt_projection.newly_consumed = newly_consumed; adapt_projection.newly_produced = newly_produced; adapt_projection.newly_flushed = newly_flushed; unsafe { zstd_adaptive_report( projection, FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STATS, Some(&adapt_projection), ); } if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT, &adapt_projection) == FIO_RUST_ZSTD_ADAPT_FASTER { unsafe { zstd_adaptive_report( projection, FIO_RUST_ZSTD_ADAPT_DIAG_RECOMMEND_FASTER, Some(&adapt_projection), ); } state.speed_change = FIO_RUST_ZSTD_ADAPT_FASTER; } } state.input_blocked = 0; state.input_presented = 0; } if state.speed_change == FIO_RUST_ZSTD_ADAPT_SLOWER { unsafe { zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL, None); } let adapt_level_projection = FIO_rust_zstd_adapt_projection_t { compression_level: *compression_level, max_adapt_level: projection.max_adapt_level, max_c_level: projection.max_c_level, ..FIO_rust_zstd_adapt_projection_t::default() }; *compression_level = zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER, &adapt_level_projection); if let Some(set_parameter) = projection.adaptive_set_parameter { unsafe { set_parameter(projection.policy_opaque, *compression_level); } } } if state.speed_change == FIO_RUST_ZSTD_ADAPT_FASTER { unsafe { zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_FASTER_LEVEL, None); } let adapt_level_projection = FIO_rust_zstd_adapt_projection_t { compression_level: *compression_level, min_adapt_level: projection.min_adapt_level, ..FIO_rust_zstd_adapt_projection_t::default() }; *compression_level = zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER, &adapt_level_projection); if let Some(set_parameter) = projection.adaptive_set_parameter { unsafe { set_parameter(projection.policy_opaque, *compression_level); } } } state.speed_change = FIO_RUST_ZSTD_ADAPT_NO_CHANGE; state.last_job_id = progression.current_job_id; } } #[cfg(feature = "compression")] type FIO_rust_zstd_to_flush_now_fn = unsafe extern "C" fn(*mut c_void) -> usize; #[cfg(feature = "compression")] type FIO_rust_zstd_compress_stream2_fn = unsafe extern "C" fn(*mut c_void, *mut ZSTD_outBuffer, *mut ZSTD_inBuffer, c_int) -> usize; /// Build the public stream-buffer views, call the Rust compressor, and publish /// the same scalar results as the former C-only callback. The codec context /// stays opaque; only the public buffer ABI crosses into the compressor. #[cfg(feature = "compression")] unsafe fn fio_zstd_compress_stream_with( cctx: *mut c_void, directive: c_int, input: *const u8, input_size: usize, input_pos: usize, output: *mut u8, output_size: usize, input_pos_after: *mut usize, output_produced: *mut usize, to_flush_now: *mut usize, zstd_result: *mut usize, to_flush_now_fn: FIO_rust_zstd_to_flush_now_fn, compress_stream2_fn: FIO_rust_zstd_compress_stream2_fn, ) -> c_int { let mut input_view = ZSTD_inBuffer { src: input.cast::(), size: input_size, pos: input_pos, }; let mut output_view = ZSTD_outBuffer { dst: output.cast::(), size: output_size, pos: 0, }; let pending_flush = unsafe { to_flush_now_fn(cctx) }; let result = unsafe { compress_stream2_fn(cctx, &mut output_view, &mut input_view, directive) }; unsafe { *input_pos_after = input_view.pos; *output_produced = output_view.pos; *to_flush_now = pending_flush; *zstd_result = result; } if !crate::errors::ERR_isError(result) { 0 } else { 1 } } /// The codec callback used by the zstd file-I/O projection. C retains only /// the iteration callback's private adaptive/diagnostic context; this callback /// receives the opaque `ZSTD_CCtx` directly and delegates to Rust's public /// `ZSTD_compressStream2` implementation. #[cfg(all(feature = "compression", not(test)))] #[no_mangle] pub unsafe extern "C" fn FIO_rust_zstd_compressStream( cctx: *mut c_void, _src_file_name: *const c_char, directive: c_int, input: *const u8, input_size: usize, input_pos: usize, output: *mut u8, output_size: usize, input_pos_after: *mut usize, output_produced: *mut usize, to_flush_now: *mut usize, zstd_result: *mut usize, ) -> c_int { unsafe { fio_zstd_compress_stream_with( cctx, directive, input, input_size, input_pos, output, output_size, input_pos_after, output_produced, to_flush_now, zstd_result, ZSTD_toFlushNow, crate::zstd_compress::ZSTD_compressStream2, ) } } /// Compresses one zstd frame through the C-owned zstd context and adaptive /// policy. Rust owns the stream loop and codec call; C callbacks retain the /// adaptive diagnostics and all private CLI state. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressZstdFrame( projection: *const FIO_rust_zstd_compress_projection_t, src_file_name: *const c_char, src_file_size: u64, compression_level: c_int, read_size: *mut u64, compressed_size: *mut u64, zstd_result: *mut usize, ) -> c_int { assert!(!projection.is_null()); assert!(!src_file_name.is_null()); assert!(!read_size.is_null()); assert!(!compressed_size.is_null()); assert!(!zstd_result.is_null()); unsafe { *read_size = 0; *compressed_size = 0; *zstd_result = 0; } let projection = unsafe { &*projection }; if projection.read_buffer_size == 0 { return FIO_RUST_ZSTD_INVALID_PROJECTION; } let Some(read_fill) = projection.read_fill else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(read_consume) = projection.read_consume else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(write_acquire) = projection.write_acquire else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.write_enqueue else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(write_release) = projection.write_release else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.sparse_write_end else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(compress_stream) = projection.compress_stream else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; let Some(iteration) = projection.iteration else { return FIO_RUST_ZSTD_INVALID_PROJECTION; }; if projection.adaptive_mode != 0 && (projection.adaptive_progression.is_none() || projection.adaptive_set_parameter.is_none() || projection.adaptive_diagnostic.is_none()) { return FIO_RUST_ZSTD_INVALID_PROJECTION; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.write_opaque, &mut job, &mut output, &mut output_size, ); } if job.is_null() || (output.is_null() && output_size != 0) { return FIO_RUST_ZSTD_INVALID_PROJECTION; } let mut input = ptr::null::(); let mut input_size = 0_usize; let mut input_pos = 0_usize; let mut in_file_size = 0_u64; let mut out_file_size = 0_u64; let mut directive = FIO_RUST_ZSTD_E_CONTINUE; let mut compression_level = compression_level; let mut adaptive_state = ZstdAdaptiveState { last_refresh: Some(Instant::now()), ..ZstdAdaptiveState::default() }; loop { if input_pos == input_size { let mut loaded = 0_usize; let added = unsafe { read_fill( projection.read_opaque, projection.read_buffer_size, &mut input, &mut loaded, ) }; if loaded != 0 && input.is_null() { return FIO_RUST_ZSTD_INVALID_PROJECTION; } input_size = loaded; input_pos = 0; in_file_size = in_file_size.wrapping_add(added as u64); unsafe { *read_size = in_file_size }; if loaded == 0 || (src_file_size != UTIL_FILESIZE_UNKNOWN && in_file_size == src_file_size) { directive = FIO_RUST_ZSTD_E_END; } } let mut still_to_flush = 1_usize; while input_pos != input_size || (directive == FIO_RUST_ZSTD_E_END && still_to_flush != 0) { let old_input_pos = input_pos; let mut new_input_pos = input_pos; let mut output_produced = 0_usize; let mut to_flush_now = 0_usize; let mut codec_result = 0_usize; let status = unsafe { compress_stream( projection.codec_opaque, src_file_name, directive, input, input_size, input_pos, output, output_size, &mut new_input_pos, &mut output_produced, &mut to_flush_now, &mut codec_result, ) }; if status != 0 { unsafe { *zstd_result = codec_result; *read_size = in_file_size; *compressed_size = out_file_size; } return FIO_RUST_ZSTD_COMPRESS_ERROR; } still_to_flush = codec_result; assert!(new_input_pos >= old_input_pos); assert!(new_input_pos <= input_size); assert!(output_produced <= output_size); if let Some(display) = projection.compress_display { unsafe { display(directive, new_input_pos, input_size, output_produced); } } unsafe { read_consume(projection.read_opaque, new_input_pos - old_input_pos) }; input_pos = new_input_pos; if output_produced != 0 { unsafe { write_enqueue( projection.write_opaque, &mut job, output_produced, &mut output, &mut output_size, ); } out_file_size = out_file_size.wrapping_add(output_produced as u64); unsafe { *compressed_size = out_file_size }; } unsafe { zstd_adaptive_iteration( &mut adaptive_state, projection, &mut compression_level, old_input_pos, new_input_pos, to_flush_now, ); iteration( projection.policy_opaque, src_file_name, &mut compression_level, old_input_pos, new_input_pos, to_flush_now, ) }; } if directive == FIO_RUST_ZSTD_E_END { break; } } if src_file_size != UTIL_FILESIZE_UNKNOWN && in_file_size != src_file_size { unsafe { *read_size = in_file_size; *compressed_size = out_file_size; } return FIO_RUST_ZSTD_INCOMPLETE_INPUT; } unsafe { *read_size = in_file_size; *compressed_size = out_file_size; write_release(projection.write_opaque, job); sparse_write_end(projection.write_opaque); } FIO_RUST_ZSTD_OK } /// Compresses one gzip member through the C-owned zlib and asynchronous /// resource callbacks. The loop mirrors the original `FIO_compressGzFrame` /// sequencing: input is counted when a read-pool buffer is loaded, consumed /// bytes are returned immediately after each zlib call, non-empty output is /// flushed before progress is reported, and the final empty job is released /// only after `deflateEnd()` succeeds. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressGzipFrame( projection: *const FIO_rust_gzip_compress_projection_t, src_file_name: *const c_char, src_file_size: u64, compression_level: c_int, read_size: *mut u64, compressed_size: *mut u64, zlib_result: *mut c_int, ) -> c_int { assert!(!projection.is_null()); assert!(!src_file_name.is_null()); assert!(!read_size.is_null()); assert!(!compressed_size.is_null()); assert!(!zlib_result.is_null()); unsafe { *read_size = 0; *compressed_size = 0; *zlib_result = FIO_RUST_GZIP_Z_OK; } let projection = unsafe { &*projection }; let Some(read_fill) = projection.read_fill else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(read_consume) = projection.read_consume else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(write_acquire) = projection.write_acquire else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.write_enqueue else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(write_release) = projection.write_release else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.sparse_write_end else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(zlib_init) = projection.zlib_init else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(zlib_deflate) = projection.zlib_deflate else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(zlib_end) = projection.zlib_end else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let Some(progress) = projection.progress else { return FIO_RUST_GZIP_INVALID_PROJECTION; }; let compression_level = compression_level.min(FIO_RUST_GZIP_BEST_COMPRESSION); let init_result = unsafe { zlib_init(projection.zlib_opaque, compression_level) }; if init_result != FIO_RUST_GZIP_Z_OK { unsafe { *zlib_result = init_result }; return FIO_RUST_GZIP_INIT_ERROR; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.write_opaque, &mut job, &mut output, &mut output_size, ); } assert!(!job.is_null()); assert!(!output.is_null() || output_size == 0); let mut input = ptr::null(); let mut input_size = 0_usize; let mut in_file_size = 0_u64; let mut out_file_size = 0_u64; loop { if input_size == 0 { let mut loaded = 0_usize; unsafe { read_fill( projection.read_opaque, projection.read_buffer_size, &mut input, &mut loaded, ); } if loaded == 0 { break; } assert!(!input.is_null()); input_size = loaded; in_file_size = in_file_size.wrapping_add(loaded as u64); } let mut consumed = 0_usize; let mut produced = 0_usize; let result = unsafe { zlib_deflate( projection.zlib_opaque, input, input_size, output, output_size, FIO_RUST_GZIP_Z_NO_FLUSH, &mut consumed, &mut produced, ) }; assert!(consumed <= input_size); assert!(produced <= output_size); unsafe { read_consume(projection.read_opaque, consumed) }; if consumed != 0 { input = unsafe { input.add(consumed) }; } input_size -= consumed; if result != FIO_RUST_GZIP_Z_OK { unsafe { *zlib_result = result }; return FIO_RUST_GZIP_DEFLATE_ERROR; } if produced != 0 { unsafe { write_enqueue( projection.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } out_file_size = out_file_size.wrapping_add(produced as u64); } unsafe { progress( projection.progress_opaque, src_file_size, in_file_size, out_file_size, ) }; } loop { let mut consumed = 0_usize; let mut produced = 0_usize; let result = unsafe { zlib_deflate( projection.zlib_opaque, ptr::null(), 0, output, output_size, FIO_RUST_GZIP_Z_FINISH, &mut consumed, &mut produced, ) }; assert_eq!(consumed, 0); assert!(produced <= output_size); if produced != 0 { unsafe { write_enqueue( projection.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } out_file_size = out_file_size.wrapping_add(produced as u64); } if result == FIO_RUST_GZIP_Z_STREAM_END { break; } if result != FIO_RUST_GZIP_Z_BUF_ERROR { unsafe { *zlib_result = result }; return FIO_RUST_GZIP_FINISH_ERROR; } } let end_result = unsafe { zlib_end(projection.zlib_opaque) }; if end_result != FIO_RUST_GZIP_Z_OK { unsafe { *zlib_result = end_result }; return FIO_RUST_GZIP_END_ERROR; } unsafe { *read_size = in_file_size; *compressed_size = out_file_size; write_release(projection.write_opaque, job); sparse_write_end(projection.write_opaque); } FIO_RUST_GZIP_OK } /// Compresses one LZ4 frame through C-owned LZ4F and asynchronous resource /// callbacks. The callback projection deliberately reports bytes added by a /// read-pool fill separately from the currently loaded byte count: the former /// is the accounting used by the original C leaf, while the latter drives the /// current block update. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressLz4Frame( projection: *const FIO_rust_lz4_compress_projection_t, _src_file_name: *const c_char, src_file_size: u64, compression_level: c_int, checksum_flag: c_int, read_size: *mut u64, compressed_size: *mut u64, lz4_result: *mut usize, ) -> c_int { assert!(!projection.is_null()); assert!(!read_size.is_null()); assert!(!compressed_size.is_null()); assert!(!lz4_result.is_null()); unsafe { *read_size = 0; *compressed_size = 0; *lz4_result = 0; } let projection = unsafe { &*projection }; if projection.block_size == 0 { return FIO_RUST_LZ4_INVALID_PROJECTION; } let Some(create) = projection.create else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(prepare) = projection.prepare else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(begin) = projection.begin else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(update) = projection.update else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(end) = projection.end else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(free_context) = projection.free_context else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(read_fill) = projection.read_fill else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(read_consume) = projection.read_consume else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(write_acquire) = projection.write_acquire else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.write_enqueue else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(write_release) = projection.write_release else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.sparse_write_end else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let Some(progress) = projection.progress else { return FIO_RUST_LZ4_INVALID_PROJECTION; }; let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.write_opaque, &mut job, &mut output, &mut output_size, ); } if job.is_null() || (output.is_null() && output_size != 0) { return FIO_RUST_LZ4_INVALID_PROJECTION; } let mut result = 0_usize; let create_status = unsafe { create(projection.codec_opaque, projection.version, &mut result) }; if create_status != 0 { unsafe { *lz4_result = result }; return FIO_RUST_LZ4_CREATE_ERROR; } unsafe { prepare( projection.codec_opaque, src_file_size, compression_level, checksum_flag, projection.block_size, output_size, ); } result = 0; let begin_status = unsafe { begin(projection.codec_opaque, output, output_size, &mut result) }; if begin_status != 0 { unsafe { *lz4_result = result }; return FIO_RUST_LZ4_HEADER_ERROR; } let header_size = result; unsafe { write_enqueue( projection.write_opaque, &mut job, header_size, &mut output, &mut output_size, ); } let mut in_file_size = 0_u64; let mut out_file_size = header_size as u64; let mut input = ptr::null::(); let mut loaded = 0_usize; in_file_size = in_file_size.wrapping_add(unsafe { read_fill( projection.read_opaque, projection.block_size, &mut input, &mut loaded, ) as u64 }); while loaded != 0 { if input.is_null() { return FIO_RUST_LZ4_INVALID_PROJECTION; } let input_size = projection.block_size.min(loaded); result = 0; let update_status = unsafe { update( projection.codec_opaque, output, output_size, input, input_size, &mut result, ) }; if update_status != 0 { unsafe { *lz4_result = result }; return FIO_RUST_LZ4_UPDATE_ERROR; } out_file_size = out_file_size.wrapping_add(result as u64); unsafe { progress( projection.progress_opaque, src_file_size, in_file_size, out_file_size, ); write_enqueue( projection.write_opaque, &mut job, result, &mut output, &mut output_size, ); read_consume(projection.read_opaque, input_size); } in_file_size = in_file_size.wrapping_add(unsafe { read_fill( projection.read_opaque, projection.block_size, &mut input, &mut loaded, ) as u64 }); } result = 0; let end_status = unsafe { end(projection.codec_opaque, output, output_size, &mut result) }; if end_status != 0 { unsafe { *lz4_result = result }; return FIO_RUST_LZ4_END_ERROR; } unsafe { write_enqueue( projection.write_opaque, &mut job, result, &mut output, &mut output_size, ); } out_file_size = out_file_size.wrapping_add(result as u64); unsafe { *read_size = in_file_size; *compressed_size = out_file_size; free_context(projection.codec_opaque); write_release(projection.write_opaque, job); sparse_write_end(projection.write_opaque); } FIO_RUST_LZ4_OK } /// Compresses one LZMA or xz member through the C-owned liblzma stream and /// asynchronous resource callbacks. The action transition and accounting /// intentionally mirror the original C loop: input is counted when a pool /// buffer is filled, consumed bytes are returned immediately after each /// `lzma_code()` call, non-empty output is enqueued before progress is shown, /// and `LZMA_FINISH` is retried until `LZMA_STREAM_END`. #[no_mangle] pub unsafe extern "C" fn FIO_rust_compressLzmaFrame( projection: *const FIO_rust_lzma_compress_projection_t, _src_file_name: *const c_char, src_file_size: u64, compression_level: c_int, plain_lzma: c_int, read_size: *mut u64, compressed_size: *mut u64, lzma_result: *mut c_int, ) -> c_int { assert!(!projection.is_null()); assert!(!read_size.is_null()); assert!(!compressed_size.is_null()); assert!(!lzma_result.is_null()); unsafe { *read_size = 0; *compressed_size = 0; *lzma_result = FIO_RUST_LZMA_OK_CODE; } let projection = unsafe { &*projection }; let Some(read_fill) = projection.read_fill else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(read_consume) = projection.read_consume else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(write_acquire) = projection.write_acquire else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.write_enqueue else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(write_release) = projection.write_release else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.sparse_write_end else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(lzma_init) = projection.lzma_init else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(lzma_code) = projection.lzma_code else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(lzma_end) = projection.lzma_end else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let Some(progress) = projection.progress else { return FIO_RUST_LZMA_INVALID_PROJECTION; }; let compression_level = compression_level.clamp(0, 9); let init_result = unsafe { lzma_init( projection.lzma_opaque, compression_level, plain_lzma, lzma_result, ) }; if init_result != FIO_RUST_LZMA_OK { return init_result; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.write_opaque, &mut job, &mut output, &mut output_size, ); } assert!(!job.is_null()); assert!(!output.is_null() || output_size == 0); let mut action = FIO_RUST_LZMA_RUN; let mut input = ptr::null(); let mut input_size = 0_usize; let mut in_file_size = 0_u64; let mut out_file_size = 0_u64; loop { if input_size == 0 { let mut loaded = 0_usize; unsafe { read_fill( projection.read_opaque, projection.read_buffer_size, &mut input, &mut loaded, ); } if loaded == 0 { action = FIO_RUST_LZMA_FINISH; } else { assert!(!input.is_null()); input_size = loaded; in_file_size = in_file_size.wrapping_add(loaded as u64); } } let mut consumed = 0_usize; let mut produced = 0_usize; let result = unsafe { lzma_code( projection.lzma_opaque, input, input_size, output, output_size, action, &mut consumed, &mut produced, ) }; assert!(consumed <= input_size); assert!(produced <= output_size); unsafe { read_consume(projection.read_opaque, consumed) }; if consumed != 0 { input = unsafe { input.add(consumed) }; } input_size -= consumed; if result != FIO_RUST_LZMA_OK_CODE && result != FIO_RUST_LZMA_STREAM_END { unsafe { *lzma_result = result }; return FIO_RUST_LZMA_CODE_ERROR; } if produced != 0 { unsafe { write_enqueue( projection.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } out_file_size = out_file_size.wrapping_add(produced as u64); } unsafe { progress( projection.progress_opaque, src_file_size, in_file_size, out_file_size, ) }; if result == FIO_RUST_LZMA_STREAM_END { break; } } unsafe { lzma_end(projection.lzma_opaque) }; unsafe { *read_size = in_file_size; *compressed_size = out_file_size; write_release(projection.write_opaque, job); sparse_write_end(projection.write_opaque); } FIO_RUST_LZMA_OK } /// Decompresses exactly one zstd frame using the existing asynchronous pools. /// /// C retains the frame dispatcher and all user-facing diagnostics. This ABI /// deliberately exposes only opaque contexts, scalar counters, and output /// parameters so the C-owned `dRess_t` never crosses into Rust. The input /// pool is advanced only after a successful decoder call; in particular, an /// error leaves the current input buffer untouched for `FIO_zstdErrorHelp()`. #[cfg(feature = "decompression")] unsafe fn decompress_zstd_frame_with( f_ctx: *mut c_void, dctx: *mut c_void, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, src_file_name: *const c_char, already_decoded: u64, frame_size: *mut u64, zstd_error: *mut usize, progress: FIO_rust_frame_progress_fn, reset: FIO_zstd_reset_fn, decompress: FIO_zstd_decompress_fn, dstream_in_size: FIO_zstd_in_size_fn, ) -> c_int { assert!(!dctx.is_null()); assert!(!read_ctx.is_null()); assert!(!write_ctx.is_null()); assert!(!src_file_name.is_null()); assert!(!frame_size.is_null()); assert!(!zstd_error.is_null()); unsafe { *frame_size = 0; *zstd_error = 0; } let mut write_job = unsafe { AIO_WritePool_acquireJob(write_ctx) }; assert!(!write_job.is_null()); unsafe { // The C implementation intentionally ignores this reset result. reset(dctx, 1); // ZSTD_reset_session_only AIO_ReadPool_fillBuffer(read_ctx, ZSTD_FRAMEHEADERSIZE_MAX); } loop { let mut input = crate::zstd_decompress::ZSTD_inBuffer { src: unsafe { read_buffer_ptr(read_ctx) }.cast::(), size: unsafe { read_buffer_loaded(read_ctx) }, pos: 0, }; let mut output = crate::zstd_decompress::ZSTD_outBuffer { dst: unsafe { (*write_job).buffer }, size: unsafe { (*write_job).bufferSize }, pos: 0, }; let read_size_hint = unsafe { decompress(dctx, &mut output, &mut input) }; if crate::errors::ERR_isError(read_size_hint) { unsafe { *zstd_error = read_size_hint; AIO_WritePool_releaseIoJob(write_job); } return FIO_RUST_ZSTD_FRAME_DECODING_ERROR; } let progress_size = unsafe { already_decoded.wrapping_add(*frame_size) }; unsafe { (*write_job).usedBufferSize = output.pos; AIO_WritePool_enqueueAndReacquireWriteJob(&mut write_job); *frame_size = (*frame_size).wrapping_add(output.pos as u64); AIO_ReadPool_consumeBytes(read_ctx, input.pos); } if let Some(callback) = progress { unsafe { callback(f_ctx, src_file_name, progress_size) }; } if read_size_hint == 0 { unsafe { AIO_WritePool_releaseIoJob(write_job); AIO_WritePool_sparseWriteEnd(write_ctx); } return FIO_RUST_ZSTD_FRAME_OK; } let to_decode = read_size_hint.min(dstream_in_size()); let loaded = unsafe { read_buffer_loaded(read_ctx) }; if loaded < to_decode { let read_size = unsafe { AIO_ReadPool_fillBuffer(read_ctx, to_decode) }; if read_size == 0 { unsafe { AIO_WritePool_releaseIoJob(write_job) }; return FIO_RUST_ZSTD_FRAME_PREMATURE_END; } } } } /// Decompresses consecutive zstd frames until the next format boundary. /// /// The caller has already identified the first zstd frame. After each frame, /// this helper fills only enough input to inspect the next four bytes and /// leaves a non-zstd header or a short trailing buffer untouched for C's /// mixed-format dispatcher. Completed-frame output remains accounted for if /// a later frame reports an error. #[cfg(feature = "decompression")] unsafe fn decompress_zstd_frames_with( f_ctx: *mut c_void, dctx: *mut c_void, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, src_file_name: *const c_char, already_decoded: u64, decoded_size: *mut u64, zstd_error: *mut usize, progress: FIO_rust_frame_progress_fn, reset: FIO_zstd_reset_fn, decompress: FIO_zstd_decompress_fn, dstream_in_size: FIO_zstd_in_size_fn, is_frame: FIO_zstd_is_frame_fn, ) -> c_int { assert!(!dctx.is_null()); assert!(!read_ctx.is_null()); assert!(!write_ctx.is_null()); assert!(!src_file_name.is_null()); assert!(!decoded_size.is_null()); assert!(!zstd_error.is_null()); unsafe { *decoded_size = 0; *zstd_error = 0; } loop { unsafe { AIO_ReadPool_fillBuffer(read_ctx, 4) }; let loaded = unsafe { read_buffer_loaded(read_ctx) }; if loaded < 4 { return FIO_RUST_ZSTD_FRAME_OK; } let source = unsafe { read_buffer_ptr(read_ctx) }; let is_zstd_frame = unsafe { is_frame(source.cast::(), loaded) != 0 }; if !is_zstd_frame { return FIO_RUST_ZSTD_FRAME_OK; } let mut frame_size = 0; let decoded_before_frame = unsafe { *decoded_size }; let status = unsafe { decompress_zstd_frame_with( f_ctx, dctx, read_ctx, write_ctx, src_file_name, already_decoded.wrapping_add(decoded_before_frame), &mut frame_size, zstd_error, progress, reset, decompress, dstream_in_size, ) }; if status != FIO_RUST_ZSTD_FRAME_OK { return status; } unsafe { *decoded_size = decoded_before_frame.wrapping_add(frame_size); } } } #[cfg(feature = "decompression")] unsafe extern "C" fn fio_zstd_reset(dctx: *mut c_void, reset: c_int) -> usize { unsafe { crate::zstd_decompress::ZSTD_DCtx_reset( dctx.cast::(), reset, ) } } #[cfg(feature = "decompression")] unsafe extern "C" fn fio_zstd_decompress( dctx: *mut c_void, output: *mut crate::zstd_decompress::ZSTD_outBuffer, input: *mut crate::zstd_decompress::ZSTD_inBuffer, ) -> usize { unsafe { crate::zstd_decompress::ZSTD_decompressStream( dctx.cast::(), output, input, ) } } #[no_mangle] #[cfg(feature = "decompression")] pub unsafe extern "C" fn FIO_rust_decompressZstdFrame( f_ctx: *mut c_void, dctx: *mut c_void, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, src_file_name: *const c_char, already_decoded: u64, frame_size: *mut u64, zstd_error: *mut usize, progress: FIO_rust_frame_progress_fn, ) -> c_int { unsafe { decompress_zstd_frame_with( f_ctx, dctx, read_ctx, write_ctx, src_file_name, already_decoded, frame_size, zstd_error, progress, fio_zstd_reset, fio_zstd_decompress, crate::zstd_decompress::ZSTD_DStreamInSize, ) } } #[no_mangle] #[cfg(feature = "decompression")] pub unsafe extern "C" fn FIO_rust_decompressZstdFrames( f_ctx: *mut c_void, dctx: *mut c_void, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, src_file_name: *const c_char, already_decoded: u64, decoded_size: *mut u64, zstd_error: *mut usize, progress: FIO_rust_frame_progress_fn, ) -> c_int { unsafe { decompress_zstd_frames_with( f_ctx, dctx, read_ctx, write_ctx, src_file_name, already_decoded, decoded_size, zstd_error, progress, fio_zstd_reset, fio_zstd_decompress, crate::zstd_decompress::ZSTD_DStreamInSize, crate::zstd_decompress::ZSTD_isFrame, ) } } #[cfg(feature = "decompression")] const FIO_ERROR_FRAME_DECODING: u64 = u64::MAX - 1; #[inline] #[cfg(feature = "decompression")] unsafe fn zstd_frame_policy_result( callback_context: *mut c_void, src_file_name: *const c_char, status: c_int, decoded_size: u64, zstd_error: usize, display_decoding_error: FIO_rust_zstd_frame_decoding_error_fn, display_premature_end: FIO_rust_zstd_frame_premature_end_fn, ) -> u64 { match status { FIO_RUST_ZSTD_FRAME_OK => decoded_size, FIO_RUST_ZSTD_FRAME_DECODING_ERROR => { unsafe { display_decoding_error(callback_context, src_file_name, zstd_error) }; FIO_ERROR_FRAME_DECODING } FIO_RUST_ZSTD_FRAME_PREMATURE_END => { unsafe { display_premature_end(callback_context, src_file_name) }; FIO_ERROR_FRAME_DECODING } _ => FIO_ERROR_FRAME_DECODING, } } /// Run the default zstd-frame loop and apply its historical result/error /// policy. C retains only exact diagnostics and private resource callbacks. #[no_mangle] #[cfg(feature = "decompression")] pub unsafe extern "C" fn FIO_rust_decompressZstdFramePolicy( state: *const FIO_rust_zstd_frame_policy_state, ) -> u64 { let Some(state) = (unsafe { state.as_ref() }) else { return FIO_ERROR_FRAME_DECODING; }; let Some(display_decoding_error) = state.display_decoding_error else { return FIO_ERROR_FRAME_DECODING; }; let Some(display_premature_end) = state.display_premature_end else { return FIO_ERROR_FRAME_DECODING; }; let mut decoded_size = 0; let mut zstd_error = 0; let status = unsafe { FIO_rust_decompressZstdFrames( state.f_ctx, state.dctx, state.read_ctx, state.write_ctx, state.src_file_name, state.already_decoded, &mut decoded_size, &mut zstd_error, state.progress, ) }; unsafe { zstd_frame_policy_result( state.callback_context, state.src_file_name, status, decoded_size, zstd_error, display_decoding_error, display_premature_end, ) } } /// Decompresses one gzip member through a C-owned zlib stream and the common /// asynchronous I/O projection. Input is consumed immediately after each /// inflate call; this is equivalent to the original C leaf's final /// `avail_in` accounting while keeping any following member or format in the /// read pool. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressGzipFrame( projection: *const FIO_rust_gzip_decompress_projection_t, frame_size: *mut u64, zlib_result: *mut c_int, ) -> c_int { assert!(!projection.is_null()); assert!(!frame_size.is_null()); assert!(!zlib_result.is_null()); unsafe { *frame_size = 0; *zlib_result = FIO_RUST_GZIP_DECOMPRESS_Z_OK; } let projection = unsafe { &*projection }; if projection.io.read_buffer_size == 0 { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; } let Some(read_fill) = projection.io.read_fill else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(read_consume) = projection.io.read_consume else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_acquire) = projection.io.write_acquire else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.io.write_enqueue else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_release) = projection.io.write_release else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.io.sparse_write_end else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(zlib_init) = projection.zlib_init else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(zlib_inflate) = projection.zlib_inflate else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let Some(zlib_end) = projection.zlib_end else { return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION; }; let init_result = unsafe { zlib_init(projection.zlib_opaque) }; if init_result != FIO_RUST_GZIP_DECOMPRESS_Z_OK { unsafe { *zlib_result = init_result }; return FIO_RUST_GZIP_DECOMPRESS_INIT_ERROR; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.io.write_opaque, &mut job, &mut output, &mut output_size, ); } assert!(!job.is_null()); assert!(!output.is_null() || output_size == 0); let mut input = ptr::null(); let mut input_size = 0_usize; unsafe { read_fill(projection.io.read_opaque, 0, &mut input, &mut input_size); } let mut flush = FIO_RUST_GZIP_DECOMPRESS_Z_NO_FLUSH; let mut decoded = 0_u64; let mut status = FIO_RUST_GZIP_DECOMPRESS_OK; loop { if input_size == 0 { unsafe { read_fill( projection.io.read_opaque, projection.io.read_buffer_size, &mut input, &mut input_size, ); } if input_size == 0 { flush = FIO_RUST_GZIP_DECOMPRESS_Z_FINISH; } } let mut consumed = 0_usize; let mut produced = 0_usize; let result = unsafe { zlib_inflate( projection.zlib_opaque, input, input_size, output, output_size, flush, &mut consumed, &mut produced, ) }; assert!(consumed <= input_size); assert!(produced <= output_size); unsafe { read_consume(projection.io.read_opaque, consumed) }; if consumed != 0 { input = unsafe { input.add(consumed) }; } input_size -= consumed; if result == FIO_RUST_GZIP_DECOMPRESS_Z_BUF_ERROR { unsafe { *zlib_result = result }; status = FIO_RUST_GZIP_DECOMPRESS_BUF_ERROR; break; } if result != FIO_RUST_GZIP_DECOMPRESS_Z_OK && result != FIO_RUST_GZIP_DECOMPRESS_Z_STREAM_END { unsafe { *zlib_result = result }; status = FIO_RUST_GZIP_DECOMPRESS_INFLATE_ERROR; break; } if produced != 0 { unsafe { write_enqueue( projection.io.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } decoded = decoded.wrapping_add(produced as u64); } if result == FIO_RUST_GZIP_DECOMPRESS_Z_STREAM_END { break; } } let end_result = unsafe { zlib_end(projection.zlib_opaque) }; if status == FIO_RUST_GZIP_DECOMPRESS_OK && end_result != FIO_RUST_GZIP_DECOMPRESS_Z_OK { unsafe { *zlib_result = end_result }; status = FIO_RUST_GZIP_DECOMPRESS_END_ERROR; } unsafe { write_release(projection.io.write_opaque, job); sparse_write_end(projection.io.write_opaque); } if status == FIO_RUST_GZIP_DECOMPRESS_OK { unsafe { *frame_size = decoded }; } status } /// Decompresses one xz or plain-LZMA stream through a C-owned liblzma /// stream. The action switches to `LZMA_FINISH` only after the read pool /// reaches EOF, matching the legacy C loop and preserving unread trailing /// bytes after `LZMA_STREAM_END`. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressLzmaFrame( projection: *const FIO_rust_lzma_decompress_projection_t, plain_lzma: c_int, frame_size: *mut u64, lzma_result: *mut c_int, ) -> c_int { assert!(!projection.is_null()); assert!(!frame_size.is_null()); assert!(!lzma_result.is_null()); unsafe { *frame_size = 0; *lzma_result = FIO_RUST_LZMA_DECOMPRESS_OK_CODE; } let projection = unsafe { &*projection }; if projection.io.read_buffer_size == 0 { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; } let Some(read_fill) = projection.io.read_fill else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(read_consume) = projection.io.read_consume else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_acquire) = projection.io.write_acquire else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.io.write_enqueue else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_release) = projection.io.write_release else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.io.sparse_write_end else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(lzma_init) = projection.lzma_init else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(lzma_code) = projection.lzma_code else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let Some(lzma_end) = projection.lzma_end else { return FIO_RUST_LZMA_DECOMPRESS_INVALID_PROJECTION; }; let init_result = unsafe { lzma_init(projection.lzma_opaque, plain_lzma) }; if init_result != FIO_RUST_LZMA_DECOMPRESS_OK_CODE { unsafe { *lzma_result = init_result }; return FIO_RUST_LZMA_DECOMPRESS_INIT_ERROR; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.io.write_opaque, &mut job, &mut output, &mut output_size, ); } assert!(!job.is_null()); assert!(!output.is_null() || output_size == 0); let mut input = ptr::null(); let mut input_size = 0_usize; unsafe { read_fill(projection.io.read_opaque, 0, &mut input, &mut input_size); } let mut action = FIO_RUST_LZMA_DECOMPRESS_RUN; let mut decoded = 0_u64; let mut status = FIO_RUST_LZMA_DECOMPRESS_OK; loop { if input_size == 0 { unsafe { read_fill( projection.io.read_opaque, projection.io.read_buffer_size, &mut input, &mut input_size, ); } if input_size == 0 { action = FIO_RUST_LZMA_DECOMPRESS_FINISH; } } let mut consumed = 0_usize; let mut produced = 0_usize; let result = unsafe { lzma_code( projection.lzma_opaque, input, input_size, output, output_size, action, &mut consumed, &mut produced, ) }; assert!(consumed <= input_size); assert!(produced <= output_size); unsafe { read_consume(projection.io.read_opaque, consumed) }; if consumed != 0 { input = unsafe { input.add(consumed) }; } input_size -= consumed; if result == FIO_RUST_LZMA_DECOMPRESS_BUF_ERROR_CODE { unsafe { *lzma_result = result }; status = FIO_RUST_LZMA_DECOMPRESS_BUF_ERROR; break; } if result != FIO_RUST_LZMA_DECOMPRESS_OK_CODE && result != FIO_RUST_LZMA_DECOMPRESS_STREAM_END { unsafe { *lzma_result = result }; status = FIO_RUST_LZMA_DECOMPRESS_CODE_ERROR; break; } if produced != 0 { unsafe { write_enqueue( projection.io.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } decoded = decoded.wrapping_add(produced as u64); } if result == FIO_RUST_LZMA_DECOMPRESS_STREAM_END { break; } } unsafe { lzma_end(projection.lzma_opaque) }; unsafe { write_release(projection.io.write_opaque, job); sparse_write_end(projection.io.write_opaque); } if status == FIO_RUST_LZMA_DECOMPRESS_OK { unsafe { *frame_size = decoded }; } status } /// Decompresses one LZ4 frame while preserving LZ4F's recommended input /// request and full-output-buffer retry protocol. A successful frame ends at /// the first zero `next_to_load`; any EOF while that hint remains non-zero is /// reported as an unfinished stream. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressLz4Frame( projection: *const FIO_rust_lz4_decompress_projection_t, frame_size: *mut u64, lz4_result: *mut usize, ) -> c_int { assert!(!projection.is_null()); assert!(!frame_size.is_null()); assert!(!lz4_result.is_null()); unsafe { *frame_size = 0; *lz4_result = 0; } let projection = unsafe { &*projection }; let Some(read_fill) = projection.io.read_fill else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(read_consume) = projection.io.read_consume else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_acquire) = projection.io.write_acquire else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_enqueue) = projection.io.write_enqueue else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(write_release) = projection.io.write_release else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(sparse_write_end) = projection.io.sparse_write_end else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(create) = projection.create else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(code) = projection.code else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(free_context) = projection.free_context else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let Some(progress) = projection.progress else { return FIO_RUST_LZ4_DECOMPRESS_INVALID_PROJECTION; }; let mut create_result = 0_usize; let create_status = unsafe { create( projection.codec_opaque, projection.version, &mut create_result, ) }; if create_status != 0 { unsafe { *lz4_result = create_result }; return FIO_RUST_LZ4_DECOMPRESS_CREATE_ERROR; } let mut job = ptr::null_mut::(); let mut output = ptr::null_mut::(); let mut output_size = 0_usize; unsafe { write_acquire( projection.io.write_opaque, &mut job, &mut output, &mut output_size, ); } assert!(!job.is_null()); assert!(!output.is_null() || output_size == 0); let mut next_to_load = 4_usize; let mut decoded = 0_u64; let mut status = FIO_RUST_LZ4_DECOMPRESS_OK; while next_to_load != 0 { let mut input = ptr::null(); let mut loaded = 0_usize; unsafe { read_fill( projection.io.read_opaque, next_to_load, &mut input, &mut loaded, ); } if loaded == 0 { break; } let mut pos = 0_usize; let mut full_buffer_decoded = false; let mut unchanged_input_calls = 0_usize; while pos < loaded || full_buffer_decoded { let previous_next_to_load = next_to_load; let mut produced = output_size; let mut remaining = loaded - pos; let mut next = 0_usize; let result = unsafe { code( projection.codec_opaque, output, &mut produced, input.add(pos), &mut remaining, &mut next, ) }; if result != 0 { /* The C adapter returns a boolean failure status but keeps * LZ4F's actual error code in the next-input slot. */ unsafe { *lz4_result = next }; status = FIO_RUST_LZ4_DECOMPRESS_CODE_ERROR; next_to_load = 0; break; } assert!(produced <= output_size); assert!(remaining <= loaded - pos); pos += remaining; next_to_load = next; full_buffer_decoded = produced == output_size; if produced != 0 { unsafe { write_enqueue( projection.io.write_opaque, &mut job, produced, &mut output, &mut output_size, ); } decoded = decoded.wrapping_add(produced as u64); unsafe { progress(projection.progress_opaque, decoded) }; } if next_to_load == 0 { break; } /* A codec must eventually consume input or change its next-input * request. Without this guard, a faulty callback can keep * producing output while receiving an empty input slice forever, * which would turn an ordinary decode error into an unbounded * output/memory loop. */ if remaining == 0 && next == previous_next_to_load { unchanged_input_calls += 1; if unchanged_input_calls >= 2 { status = FIO_RUST_LZ4_DECOMPRESS_UNFINISHED; break; } } else { unchanged_input_calls = 0; } } unsafe { read_consume(projection.io.read_opaque, pos) }; if status != FIO_RUST_LZ4_DECOMPRESS_OK { break; } } if status == FIO_RUST_LZ4_DECOMPRESS_OK && next_to_load != 0 { status = FIO_RUST_LZ4_DECOMPRESS_UNFINISHED; } unsafe { free_context(projection.codec_opaque); write_release(projection.io.write_opaque, job); sparse_write_end(projection.io.write_opaque); } if status == FIO_RUST_LZ4_DECOMPRESS_OK { unsafe { *frame_size = decoded }; } status } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum DecompressionFormat { Zstd, Gzip, Xz, Lzma, Lz4, ShortHeader, Unsupported, } fn classify_decompression_format(buffer: &[u8], is_zstd_frame: F) -> DecompressionFormat where F: Fn(&[u8]) -> bool, { if buffer.len() < 4 { return DecompressionFormat::ShortHeader; } if is_zstd_frame(buffer) { return DecompressionFormat::Zstd; } if buffer[0] == 31 && buffer[1] == 139 { return DecompressionFormat::Gzip; } if (buffer[0] == 0xFD && buffer[1] == 0x37) || (buffer[0] == 0x5D && buffer[1] == 0x00) { return if buffer[0] == 0xFD { DecompressionFormat::Xz } else { DecompressionFormat::Lzma }; } if u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) == 0x184D2204 { return DecompressionFormat::Lz4; } DecompressionFormat::Unsupported } #[inline] unsafe fn is_zstd_frame_for_dispatch(buffer: &[u8]) -> bool { #[cfg(any(test, not(feature = "decompression")))] { /* Standalone Rust tests do not link the C legacy-decoder shim. The * dispatch tests only need the modern frame magic; production keeps * the complete public predicate below. */ buffer.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]) } #[cfg(all(feature = "decompression", not(test)))] { unsafe { crate::zstd_decompress::ZSTD_isFrame(buffer.as_ptr().cast(), buffer.len()) != 0 } } } unsafe fn run_pass_through_callback(callbacks: &FIO_rust_decompress_callbacks_t) -> c_int { let Some(callback) = callbacks.pass_through else { return FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR; }; if unsafe { callback(callbacks.opaque) } == 0 { FIO_RUST_DECOMPRESS_PASS_THROUGH } else { FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR } } /// Drives the CLI's mixed-format decompression loop. /// /// Rust owns input probing, format selection, repeated callback dispatch, and /// decoded-size accumulation. Codec implementations and their private C /// resource layout stay behind the callback projection above. A successful /// pass-through is reported separately because the C caller historically /// returns before final decompression accounting in that case. #[no_mangle] pub unsafe extern "C" fn FIO_rust_decompressFrames( read_ctx: *mut ReadPoolCtx_t, src_file_name: *const c_char, pass_through: c_int, decoded_size: *mut u64, callbacks: *const FIO_rust_decompress_callbacks_t, ) -> c_int { assert!(!read_ctx.is_null()); assert!(!src_file_name.is_null()); assert!(!decoded_size.is_null()); assert!(!callbacks.is_null()); assert!(pass_through == 0 || pass_through == 1); let callbacks = unsafe { &*callbacks }; unsafe { *decoded_size = 0 }; let mut read_something = false; loop { unsafe { AIO_ReadPool_fillBuffer(read_ctx, 4) }; let loaded = unsafe { read_buffer_loaded(read_ctx) }; if loaded == 0 { return if read_something { FIO_RUST_DECOMPRESS_OK } else { FIO_RUST_DECOMPRESS_EMPTY_INPUT }; } read_something = true; if loaded < 4 { return if pass_through != 0 { unsafe { run_pass_through_callback(callbacks) } } else { FIO_RUST_DECOMPRESS_SHORT_INPUT }; } let source = unsafe { read_buffer_ptr(read_ctx) }; let buffer = unsafe { std::slice::from_raw_parts(source, loaded) }; let format = classify_decompression_format(buffer, |bytes| unsafe { is_zstd_frame_for_dispatch(bytes) }); let (callback, missing_status, mode) = match format { DecompressionFormat::Zstd => ( callbacks.decode_zstd, FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED, 0, ), DecompressionFormat::Gzip => ( callbacks.decode_gzip, FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED, 0, ), DecompressionFormat::Xz => ( callbacks.decode_lzma, FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, 0, ), DecompressionFormat::Lzma => ( callbacks.decode_lzma, FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, 1, ), DecompressionFormat::Lz4 => { (callbacks.decode_lz4, FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, 0) } DecompressionFormat::ShortHeader => unreachable!(), DecompressionFormat::Unsupported => { return if pass_through != 0 { unsafe { run_pass_through_callback(callbacks) } } else { FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT }; } }; let Some(callback) = callback else { return missing_status; }; let mut frame_size = 0_u64; let mut error_code = 0_usize; let status = unsafe { callback( callbacks.opaque, src_file_name, *decoded_size, &mut frame_size, &mut error_code, mode, ) }; if status != 0 { return FIO_RUST_DECOMPRESS_FRAME_ERROR; } unsafe { *decoded_size = (*decoded_size).wrapping_add(frame_size); } } } /// Maps the mixed-format loop's result to the CLI result and performs the /// successful-file finalization. C keeps the diagnostic and accounting /// callbacks because they touch `DISPLAY*` state and the private `FIO_ctx_t` /// layout; Rust owns the result policy and decides which callback is legal. #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum DecompressFinishAction { Finish, PassThrough, ReportStatus, Invalid, } fn classify_decompress_finish_status(status: c_int) -> DecompressFinishAction { match status { FIO_RUST_DECOMPRESS_OK => DecompressFinishAction::Finish, FIO_RUST_DECOMPRESS_PASS_THROUGH => DecompressFinishAction::PassThrough, FIO_RUST_DECOMPRESS_EMPTY_INPUT | FIO_RUST_DECOMPRESS_SHORT_INPUT | FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED | FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED | FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED | FIO_RUST_DECOMPRESS_FRAME_ERROR | FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT | FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR | FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED => DecompressFinishAction::ReportStatus, _ => DecompressFinishAction::Invalid, } } #[no_mangle] pub unsafe extern "C" fn FIO_rust_finishDecompressFrames( status: c_int, src_file_name: *const c_char, decoded_size: u64, callbacks: *const FIO_rust_decompress_callbacks_t, ) -> c_int { assert!(!src_file_name.is_null()); assert!(!callbacks.is_null()); let callbacks = unsafe { &*callbacks }; match classify_decompress_finish_status(status) { DecompressFinishAction::Finish => { if let Some(finish) = callbacks.finish { unsafe { finish(callbacks.opaque, src_file_name, decoded_size) }; } 0 } DecompressFinishAction::PassThrough => 0, DecompressFinishAction::ReportStatus => { if let Some(report_status) = callbacks.report_status { unsafe { report_status(callbacks.opaque, status, src_file_name) }; } 1 } DecompressFinishAction::Invalid => { unreachable!("unknown Rust decompression status: {status}"); } } } #[inline] unsafe fn read_buffer_ptr(ctx: *mut ReadPoolCtx_t) -> *const u8 { let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; unsafe { read_at::<*const u8>(context, inner.layout.read_src_buffer) } } #[inline] unsafe fn read_buffer_loaded(ctx: *mut ReadPoolCtx_t) -> usize { let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) } } impl PoolInner { unsafe fn all_jobs_available(&self) -> bool { let state = self.jobs.lock().unwrap_or_else(|error| error.into_inner()); state.available.len() == self.total_jobs && state.completed.is_empty() } } #[cfg(test)] mod tests { use super::*; #[test] fn decompression_pass_through_policy_preserves_explicit_and_default_modes() { for pass_through in [0, 1] { assert_eq!( decompress_pass_through_policy(pass_through, 0, 0), pass_through ); assert_eq!( decompress_pass_through_policy(pass_through, 1, 1), pass_through ); } assert_eq!(decompress_pass_through_policy(-1, 0, 0), 0); assert_eq!(decompress_pass_through_policy(-1, 0, 1), 0); assert_eq!(decompress_pass_through_policy(-1, 1, 0), 0); assert_eq!(decompress_pass_through_policy(-1, 1, 1), 1); } #[test] fn decompression_status_action_preserves_cli_mapping_and_abi() { for (status, action) in [ (FIO_RUST_DECOMPRESS_OK, FIO_RUST_DECOMPRESS_ACTION_NOOP), ( FIO_RUST_DECOMPRESS_PASS_THROUGH, FIO_RUST_DECOMPRESS_ACTION_NOOP, ), ( FIO_RUST_DECOMPRESS_EMPTY_INPUT, FIO_RUST_DECOMPRESS_ACTION_EMPTY_INPUT, ), ( FIO_RUST_DECOMPRESS_SHORT_INPUT, FIO_RUST_DECOMPRESS_ACTION_SHORT_INPUT, ), ( FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED, FIO_RUST_DECOMPRESS_ACTION_GZIP_UNSUPPORTED, ), ( FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, FIO_RUST_DECOMPRESS_ACTION_LZMA_UNSUPPORTED, ), ( FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, FIO_RUST_DECOMPRESS_ACTION_LZ4_UNSUPPORTED, ), ( FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT, FIO_RUST_DECOMPRESS_ACTION_UNSUPPORTED_FORMAT, ), ( FIO_RUST_DECOMPRESS_FRAME_ERROR, FIO_RUST_DECOMPRESS_ACTION_NOOP, ), ( FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR, FIO_RUST_DECOMPRESS_ACTION_NOOP, ), ( FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED, FIO_RUST_DECOMPRESS_ACTION_NOOP, ), (-1, FIO_RUST_DECOMPRESS_ACTION_INVALID), (11, FIO_RUST_DECOMPRESS_ACTION_INVALID), (c_int::MAX, FIO_RUST_DECOMPRESS_ACTION_INVALID), ] { assert_eq!( decompress_status_action(status), action, "unexpected action for decompression status {status}" ); assert_eq!( FIO_rust_decompressStatusAction(status), action, "C ABI disagrees for decompression status {status}" ); } } #[test] fn gzip_compression_status_diagnostic_preserves_cli_mapping() { for (status, diagnostic) in [ (FIO_RUST_GZIP_OK, FIO_RUST_GZIP_DIAGNOSTIC_OK), ( FIO_RUST_GZIP_INIT_ERROR, FIO_RUST_GZIP_DIAGNOSTIC_INIT_ERROR, ), ( FIO_RUST_GZIP_DEFLATE_ERROR, FIO_RUST_GZIP_DIAGNOSTIC_DEFLATE_ERROR, ), ( FIO_RUST_GZIP_FINISH_ERROR, FIO_RUST_GZIP_DIAGNOSTIC_FINISH_ERROR, ), (FIO_RUST_GZIP_END_ERROR, FIO_RUST_GZIP_DIAGNOSTIC_END_ERROR), ( FIO_RUST_GZIP_INVALID_PROJECTION, FIO_RUST_GZIP_DIAGNOSTIC_INVALID_PROJECTION, ), ] { assert_eq!(FIO_rust_gzipCompressionDiagnostic(status), diagnostic); } for status in [-1, 99] { assert_eq!( FIO_rust_gzipCompressionDiagnostic(status), FIO_RUST_GZIP_DIAGNOSTIC_UNKNOWN ); } } #[test] fn lzma_compression_status_diagnostic_preserves_cli_mapping() { for (status, diagnostic) in [ (FIO_RUST_LZMA_OK, FIO_RUST_LZMA_DIAGNOSTIC_OK), ( FIO_RUST_LZMA_INIT_PRESET_ERROR, FIO_RUST_LZMA_DIAGNOSTIC_INIT_PRESET_ERROR, ), ( FIO_RUST_LZMA_INIT_ALONE_ERROR, FIO_RUST_LZMA_DIAGNOSTIC_INIT_ALONE_ERROR, ), ( FIO_RUST_LZMA_INIT_XZ_ERROR, FIO_RUST_LZMA_DIAGNOSTIC_INIT_XZ_ERROR, ), ( FIO_RUST_LZMA_CODE_ERROR, FIO_RUST_LZMA_DIAGNOSTIC_CODE_ERROR, ), ( FIO_RUST_LZMA_INVALID_PROJECTION, FIO_RUST_LZMA_DIAGNOSTIC_INVALID_PROJECTION, ), ] { assert_eq!(FIO_rust_lzmaCompressionDiagnostic(status), diagnostic); } for status in [-1, 99] { assert_eq!( FIO_rust_lzmaCompressionDiagnostic(status), FIO_RUST_LZMA_DIAGNOSTIC_UNKNOWN ); } } #[test] fn lz4_compression_status_diagnostic_preserves_cli_mapping() { for (status, diagnostic) in [ (FIO_RUST_LZ4_OK, FIO_RUST_LZ4_DIAGNOSTIC_OK), ( FIO_RUST_LZ4_CREATE_ERROR, FIO_RUST_LZ4_DIAGNOSTIC_CREATE_ERROR, ), ( FIO_RUST_LZ4_HEADER_ERROR, FIO_RUST_LZ4_DIAGNOSTIC_HEADER_ERROR, ), ( FIO_RUST_LZ4_UPDATE_ERROR, FIO_RUST_LZ4_DIAGNOSTIC_UPDATE_ERROR, ), (FIO_RUST_LZ4_END_ERROR, FIO_RUST_LZ4_DIAGNOSTIC_END_ERROR), ( FIO_RUST_LZ4_INVALID_PROJECTION, FIO_RUST_LZ4_DIAGNOSTIC_INVALID_PROJECTION, ), ] { assert_eq!(FIO_rust_lz4CompressionDiagnostic(status), diagnostic); } for status in [-1, 99] { assert_eq!( FIO_rust_lz4CompressionDiagnostic(status), FIO_RUST_LZ4_DIAGNOSTIC_UNKNOWN ); } } #[test] fn zstd_compression_status_diagnostic_preserves_cli_mapping() { for (status, diagnostic) in [ (FIO_RUST_ZSTD_OK, FIO_RUST_ZSTD_DIAGNOSTIC_OK), ( FIO_RUST_ZSTD_COMPRESS_ERROR, FIO_RUST_ZSTD_DIAGNOSTIC_COMPRESS_ERROR, ), ( FIO_RUST_ZSTD_INCOMPLETE_INPUT, FIO_RUST_ZSTD_DIAGNOSTIC_INCOMPLETE_INPUT, ), ( FIO_RUST_ZSTD_INVALID_PROJECTION, FIO_RUST_ZSTD_DIAGNOSTIC_INVALID_PROJECTION, ), ] { assert_eq!(FIO_rust_zstdCompressionDiagnostic(status), diagnostic); } for status in [-1, 99] { assert_eq!( FIO_rust_zstdCompressionDiagnostic(status), FIO_RUST_ZSTD_DIAGNOSTIC_UNKNOWN ); } } #[test] fn aggregate_compression_status_diagnostic_preserves_format_mapping() { for (status, diagnostic) in [ (FIO_RUST_COMPRESS_OK, FIO_RUST_COMPRESS_DIAGNOSTIC_OK), ( FIO_RUST_COMPRESS_GZIP_UNSUPPORTED, FIO_RUST_COMPRESS_DIAGNOSTIC_GZIP_UNSUPPORTED, ), ( FIO_RUST_COMPRESS_LZMA_UNSUPPORTED, FIO_RUST_COMPRESS_DIAGNOSTIC_LZMA_UNSUPPORTED, ), ( FIO_RUST_COMPRESS_LZ4_UNSUPPORTED, FIO_RUST_COMPRESS_DIAGNOSTIC_LZ4_UNSUPPORTED, ), ( FIO_RUST_COMPRESS_ZSTD_UNSUPPORTED, FIO_RUST_COMPRESS_DIAGNOSTIC_ZSTD_UNSUPPORTED, ), ] { assert_eq!(FIO_rust_compressFilenameDiagnostic(status), diagnostic); } for status in [-1, 99] { assert_eq!( FIO_rust_compressFilenameDiagnostic(status), FIO_RUST_COMPRESS_DIAGNOSTIC_UNKNOWN ); } } const SOURCE_POLICY_STAT: u8 = 1; const SOURCE_POLICY_EXCLUDED: u8 = 2; const SOURCE_POLICY_OPEN: u8 = 3; const SOURCE_POLICY_ASYNC: u8 = 4; const SOURCE_POLICY_ATTACH: u8 = 5; const SOURCE_POLICY_COMPRESS: u8 = 6; const SOURCE_POLICY_CLOSE: u8 = 7; const SOURCE_POLICY_REMOVE: u8 = 8; #[derive(Default)] struct SourcePolicyState { events: Vec, async_modes: Vec, stat_status: c_int, excluded: c_int, open_status: c_int, file_size: u64, compression_status: c_int, compression_level: c_int, } unsafe extern "C" fn source_policy_stat( opaque: *mut c_void, _src_file_name: *const c_char, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_STAT); state.stat_status } unsafe extern "C" fn source_policy_excluded( opaque: *mut c_void, _src_file_name: *const c_char, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_EXCLUDED); state.excluded } unsafe extern "C" fn source_policy_open( opaque: *mut c_void, _src_file_name: *const c_char, file_size: *mut u64, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_OPEN); unsafe { *file_size = state.file_size }; state.open_status } unsafe extern "C" fn source_policy_async(opaque: *mut c_void, async_mode: c_int) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_ASYNC); state.async_modes.push(async_mode); } unsafe extern "C" fn source_policy_attach(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_ATTACH); } unsafe extern "C" fn source_policy_compress( opaque: *mut c_void, _dst_file_name: *const c_char, _src_file_name: *const c_char, compression_level: c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_COMPRESS); state.compression_level = compression_level; state.compression_status } unsafe extern "C" fn source_policy_close(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_CLOSE); } unsafe extern "C" fn source_policy_remove(opaque: *mut c_void, _src_file_name: *const c_char) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(SOURCE_POLICY_REMOVE); } fn source_policy_projection( state: &mut SourcePolicyState, source_is_stdin: c_int, exclude_compressed_files: c_int, remove_src_file: c_int, ) -> FIO_rust_compress_src_projection_t { FIO_rust_compress_src_projection_t { opaque: (state as *mut SourcePolicyState).cast(), dst_file_name: c"destination".as_ptr(), src_file_name: c"source".as_ptr(), compression_level: 7, source_is_stdin, exclude_compressed_files, remove_src_file, stat_source: Some(source_policy_stat), source_is_excluded: Some(source_policy_excluded), open_source: Some(source_policy_open), set_async: Some(source_policy_async), attach_source: Some(source_policy_attach), compress: Some(source_policy_compress), close_source: Some(source_policy_close), remove_source: Some(source_policy_remove), } } #[test] fn source_policy_keeps_named_checks_before_exclusion_and_open() { for status in [ FIO_RUST_COMPRESS_SRC_DIRECTORY, FIO_RUST_COMPRESS_SRC_DICT_COLLISION, ] { let mut state = SourcePolicyState { stat_status: status, excluded: 1, ..SourcePolicyState::default() }; let projection = source_policy_projection(&mut state, 0, 1, 1); assert_eq!(unsafe { FIO_rust_compressFilenameSrcFile(&projection) }, 1); assert_eq!(state.events, vec![SOURCE_POLICY_STAT]); } } #[test] fn source_policy_short_circuits_excluded_files_before_opening() { let mut state = SourcePolicyState { stat_status: FIO_RUST_COMPRESS_SRC_STAT_OK, excluded: 1, ..SourcePolicyState::default() }; let projection = source_policy_projection(&mut state, 0, 1, 1); assert_eq!(unsafe { FIO_rust_compressFilenameSrcFile(&projection) }, 0); assert_eq!( state.events, vec![SOURCE_POLICY_STAT, SOURCE_POLICY_EXCLUDED] ); } #[test] fn source_policy_selects_async_after_open_and_removes_only_after_success() { let mut state = SourcePolicyState { stat_status: FIO_RUST_COMPRESS_SRC_STAT_OK, file_size: FIO_RUST_COMPRESS_SRC_ASYNC_THRESHOLD - 1, compression_status: 0, ..SourcePolicyState::default() }; let projection = source_policy_projection(&mut state, 0, 0, 1); assert_eq!(unsafe { FIO_rust_compressFilenameSrcFile(&projection) }, 0); assert_eq!(state.async_modes, vec![0]); assert_eq!(state.compression_level, 7); assert_eq!( state.events, vec![ SOURCE_POLICY_STAT, SOURCE_POLICY_OPEN, SOURCE_POLICY_ASYNC, SOURCE_POLICY_ATTACH, SOURCE_POLICY_COMPRESS, SOURCE_POLICY_CLOSE, SOURCE_POLICY_REMOVE, ] ); let mut failed = SourcePolicyState { stat_status: FIO_RUST_COMPRESS_SRC_STAT_OK, file_size: FIO_RUST_COMPRESS_SRC_ASYNC_THRESHOLD, compression_status: 1, ..SourcePolicyState::default() }; let failed_projection = source_policy_projection(&mut failed, 0, 0, 1); assert_eq!( unsafe { FIO_rust_compressFilenameSrcFile(&failed_projection) }, 1 ); assert_eq!(failed.async_modes, vec![1]); assert_eq!( failed.events, vec![ SOURCE_POLICY_STAT, SOURCE_POLICY_OPEN, SOURCE_POLICY_ASYNC, SOURCE_POLICY_ATTACH, SOURCE_POLICY_COMPRESS, SOURCE_POLICY_CLOSE, ] ); } #[test] fn source_policy_skips_named_checks_and_removal_for_stdin() { let mut state = SourcePolicyState { file_size: FIO_RUST_COMPRESS_SRC_UNKNOWN_SIZE, ..SourcePolicyState::default() }; let projection = source_policy_projection(&mut state, 1, 0, 1); assert_eq!(unsafe { FIO_rust_compressFilenameSrcFile(&projection) }, 0); assert_eq!(state.async_modes, vec![1]); assert_eq!( state.events, vec![ SOURCE_POLICY_OPEN, SOURCE_POLICY_ASYNC, SOURCE_POLICY_ATTACH, SOURCE_POLICY_COMPRESS, SOURCE_POLICY_CLOSE, ] ); } const COMPRESS_DST_POLICY_OPEN: u8 = 1; const COMPRESS_DST_POLICY_ATTACH: u8 = 2; const COMPRESS_DST_POLICY_ADD_HANDLER: u8 = 3; const COMPRESS_DST_POLICY_COMPRESS: u8 = 4; const COMPRESS_DST_POLICY_CLEAR_HANDLER: u8 = 5; const COMPRESS_DST_POLICY_SET_FD_STAT: u8 = 6; const COMPRESS_DST_POLICY_CLOSE: u8 = 7; const COMPRESS_DST_POLICY_UTIME: u8 = 8; const COMPRESS_DST_POLICY_REMOVE: u8 = 9; #[derive(Default)] struct CompressDestinationPolicyState { events: Vec, transfer_stats: Vec, destination_fds: Vec, compression_levels: Vec, open_status: c_int, close_status: c_int, compression_status: c_int, } unsafe extern "C" fn compress_destination_policy_open( opaque: *mut c_void, _src_file_name: *const c_char, _dst_file_name: *const c_char, transfer_stat: c_int, destination_fd: *mut c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_OPEN); state.transfer_stats.push(transfer_stat); unsafe { *destination_fd = 41 }; state.open_status } unsafe extern "C" fn compress_destination_policy_handler(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_ATTACH); } unsafe extern "C" fn compress_destination_policy_add_handler(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_ADD_HANDLER); } unsafe extern "C" fn compress_destination_policy_clear_handler(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_CLEAR_HANDLER); } unsafe extern "C" fn compress_destination_policy_compress( opaque: *mut c_void, _dst_file_name: *const c_char, _src_file_name: *const c_char, compression_level: c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_COMPRESS); state.compression_levels.push(compression_level); state.compression_status } unsafe extern "C" fn compress_destination_policy_set_fd_stat( opaque: *mut c_void, destination_fd: c_int, _dst_file_name: *const c_char, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_SET_FD_STAT); state.destination_fds.push(destination_fd); } unsafe extern "C" fn compress_destination_policy_close(opaque: *mut c_void) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_CLOSE); state.close_status } unsafe extern "C" fn compress_destination_policy_utime(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_UTIME); } unsafe extern "C" fn compress_destination_policy_remove( opaque: *mut c_void, _dst_file_name: *const c_char, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(COMPRESS_DST_POLICY_REMOVE); } fn compress_destination_policy_projection( state: &mut CompressDestinationPolicyState, destination_already_open: c_int, source_is_stdin: c_int, destination_is_stdout: c_int, source_is_regular: c_int, ) -> FIO_rust_compress_dst_projection_t { FIO_rust_compress_dst_projection_t { opaque: (state as *mut CompressDestinationPolicyState).cast(), dst_file_name: c"destination".as_ptr(), src_file_name: c"source".as_ptr(), compression_level: 7, destination_already_open, source_is_stdin, destination_is_stdout, source_is_regular, open_destination: Some(compress_destination_policy_open), attach_destination: Some(compress_destination_policy_handler), add_handler: Some(compress_destination_policy_add_handler), compress: Some(compress_destination_policy_compress), clear_handler: Some(compress_destination_policy_clear_handler), set_fd_stat: Some(compress_destination_policy_set_fd_stat), close_destination: Some(compress_destination_policy_close), utime_destination: Some(compress_destination_policy_utime), remove_destination: Some(compress_destination_policy_remove), } } #[test] fn compression_destination_policy_preserves_metadata_and_cleanup_order() { let mut state = CompressDestinationPolicyState::default(); let projection = compress_destination_policy_projection(&mut state, 0, 0, 0, 1); assert_eq!(unsafe { FIO_rust_compressFilenameDstFile(&projection) }, 0); assert_eq!(state.transfer_stats, vec![1]); assert_eq!(state.destination_fds, vec![41]); assert_eq!(state.compression_levels, vec![7]); assert_eq!( state.events, vec![ COMPRESS_DST_POLICY_OPEN, COMPRESS_DST_POLICY_ATTACH, COMPRESS_DST_POLICY_ADD_HANDLER, COMPRESS_DST_POLICY_COMPRESS, COMPRESS_DST_POLICY_CLEAR_HANDLER, COMPRESS_DST_POLICY_SET_FD_STAT, COMPRESS_DST_POLICY_CLOSE, COMPRESS_DST_POLICY_UTIME, ] ); } #[test] fn compression_destination_policy_propagates_close_error_and_removes_output() { let mut state = CompressDestinationPolicyState { close_status: 1, ..CompressDestinationPolicyState::default() }; let projection = compress_destination_policy_projection(&mut state, 0, 0, 0, 1); assert_eq!(unsafe { FIO_rust_compressFilenameDstFile(&projection) }, 1); assert_eq!( state.events, vec![ COMPRESS_DST_POLICY_OPEN, COMPRESS_DST_POLICY_ATTACH, COMPRESS_DST_POLICY_ADD_HANDLER, COMPRESS_DST_POLICY_COMPRESS, COMPRESS_DST_POLICY_CLEAR_HANDLER, COMPRESS_DST_POLICY_SET_FD_STAT, COMPRESS_DST_POLICY_CLOSE, COMPRESS_DST_POLICY_UTIME, COMPRESS_DST_POLICY_REMOVE, ] ); } #[test] fn compression_destination_policy_skips_lifecycle_for_shared_destination() { let mut state = CompressDestinationPolicyState { compression_status: 1, ..CompressDestinationPolicyState::default() }; let projection = compress_destination_policy_projection(&mut state, 1, 0, 0, 1); assert_eq!(unsafe { FIO_rust_compressFilenameDstFile(&projection) }, 1); assert_eq!(state.events, vec![COMPRESS_DST_POLICY_COMPRESS]); assert!(state.transfer_stats.is_empty()); assert!(state.destination_fds.is_empty()); } #[test] fn compression_destination_policy_keeps_stdout_cleanup_guard() { let mut state = CompressDestinationPolicyState { compression_status: 1, ..CompressDestinationPolicyState::default() }; let projection = compress_destination_policy_projection(&mut state, 0, 0, 1, 1); assert_eq!(unsafe { FIO_rust_compressFilenameDstFile(&projection) }, 1); assert_eq!(state.transfer_stats, vec![0]); assert_eq!( state.events, vec![ COMPRESS_DST_POLICY_OPEN, COMPRESS_DST_POLICY_ATTACH, COMPRESS_DST_POLICY_ADD_HANDLER, COMPRESS_DST_POLICY_COMPRESS, COMPRESS_DST_POLICY_CLEAR_HANDLER, COMPRESS_DST_POLICY_CLOSE, ] ); } const DECOMPRESS_POLICY_OPEN_SOURCE: u8 = 1; const DECOMPRESS_POLICY_ASYNC: u8 = 2; const DECOMPRESS_POLICY_ATTACH_SOURCE: u8 = 3; const DECOMPRESS_POLICY_OPEN_DESTINATION: u8 = 4; const DECOMPRESS_POLICY_ATTACH_DESTINATION: u8 = 5; const DECOMPRESS_POLICY_ADD_HANDLER: u8 = 6; const DECOMPRESS_POLICY_DECOMPRESS: u8 = 7; const DECOMPRESS_POLICY_CLEAR_HANDLER: u8 = 8; const DECOMPRESS_POLICY_SET_FD_STAT: u8 = 9; const DECOMPRESS_POLICY_CLOSE_DESTINATION: u8 = 10; const DECOMPRESS_POLICY_UTIME_DESTINATION: u8 = 11; const DECOMPRESS_POLICY_REMOVE_DESTINATION: u8 = 12; const DECOMPRESS_POLICY_DETACH_SOURCE: u8 = 13; const DECOMPRESS_POLICY_CLOSE_SOURCE: u8 = 14; const DECOMPRESS_POLICY_REMOVE_SOURCE: u8 = 15; #[derive(Default)] struct DecompressPolicyState { events: Vec, async_modes: Vec, transfers: Vec, destination_fds: Vec, source_file_size: u64, source_is_regular: c_int, source_is_directory: c_int, source_directory_checks: usize, source_open_status: c_int, source_close_status: c_int, destination_open_status: c_int, destination_close_status: c_int, decompress_status: c_int, remove_source_status: c_int, } unsafe extern "C" fn decompress_policy_open_source( opaque: *mut c_void, _src_file_name: *const c_char, file_size: *mut u64, source_is_regular: *mut c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_OPEN_SOURCE); unsafe { *file_size = state.source_file_size; *source_is_regular = state.source_is_regular; } state.source_open_status } unsafe extern "C" fn decompress_policy_source_is_directory( opaque: *mut c_void, _src_file_name: *const c_char, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.source_directory_checks += 1; state.source_is_directory } unsafe extern "C" fn decompress_policy_async(opaque: *mut c_void, async_mode: c_int) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_ASYNC); state.async_modes.push(async_mode); } unsafe extern "C" fn decompress_policy_attach_source(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_ATTACH_SOURCE); } unsafe extern "C" fn decompress_policy_open_destination( opaque: *mut c_void, _src_file_name: *const c_char, _dst_file_name: *const c_char, transfer_stat: c_int, dst_fd: *mut c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_OPEN_DESTINATION); state.transfers.push(transfer_stat); unsafe { *dst_fd = 37 }; state.destination_open_status } unsafe extern "C" fn decompress_policy_attach_destination(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_ATTACH_DESTINATION); } unsafe extern "C" fn decompress_policy_add_handler(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_ADD_HANDLER); } unsafe extern "C" fn decompress_policy_decompress( opaque: *mut c_void, _dst_file_name: *const c_char, _src_file_name: *const c_char, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_DECOMPRESS); state.decompress_status } unsafe extern "C" fn decompress_policy_clear_handler(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_CLEAR_HANDLER); } unsafe extern "C" fn decompress_policy_set_fd_stat( opaque: *mut c_void, dst_fd: c_int, _dst_file_name: *const c_char, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_SET_FD_STAT); state.destination_fds.push(dst_fd); } unsafe extern "C" fn decompress_policy_close_destination(opaque: *mut c_void) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_CLOSE_DESTINATION); state.destination_close_status } unsafe extern "C" fn decompress_policy_utime_destination(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_UTIME_DESTINATION); } unsafe extern "C" fn decompress_policy_remove_destination( opaque: *mut c_void, _dst_file_name: *const c_char, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_REMOVE_DESTINATION); } unsafe extern "C" fn decompress_policy_detach_source(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_DETACH_SOURCE); } unsafe extern "C" fn decompress_policy_close_source(opaque: *mut c_void) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_CLOSE_SOURCE); state.source_close_status } unsafe extern "C" fn decompress_policy_remove_source( opaque: *mut c_void, _src_file_name: *const c_char, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_REMOVE_SOURCE); state.remove_source_status } fn decompress_policy_projection( state: &mut DecompressPolicyState, destination_already_open: c_int, test_mode: c_int, source_is_stdin: c_int, destination_is_stdout: c_int, remove_source: c_int, ) -> FIO_rust_decompress_file_projection_t { FIO_rust_decompress_file_projection_t { opaque: (state as *mut DecompressPolicyState).cast(), dst_file_name: c"destination".as_ptr(), src_file_name: c"source".as_ptr(), destination_already_open, test_mode, source_is_stdin, destination_is_stdout, remove_source, open_source: Some(decompress_policy_open_source), set_async: Some(decompress_policy_async), attach_source: Some(decompress_policy_attach_source), detach_source: Some(decompress_policy_detach_source), close_source: Some(decompress_policy_close_source), open_destination: Some(decompress_policy_open_destination), attach_destination: Some(decompress_policy_attach_destination), add_handler: Some(decompress_policy_add_handler), decompress: Some(decompress_policy_decompress), clear_handler: Some(decompress_policy_clear_handler), set_fd_stat: Some(decompress_policy_set_fd_stat), close_destination: Some(decompress_policy_close_destination), utime_destination: Some(decompress_policy_utime_destination), remove_destination: Some(decompress_policy_remove_destination), remove_source_file: Some(decompress_policy_remove_source), source_is_directory: Some(decompress_policy_source_is_directory), } } #[test] fn decompress_file_policy_rejects_directory_before_opening_source() { let mut state = DecompressPolicyState { source_is_directory: 1, ..DecompressPolicyState::default() }; let projection = decompress_policy_projection(&mut state, 0, 0, 0, 0, 0); assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 1); assert_eq!(state.source_directory_checks, 1); assert!(state.events.is_empty()); } #[test] fn decompress_file_policy_probes_stdin_marker_before_opening_source() { let mut state = DecompressPolicyState::default(); let projection = decompress_policy_projection(&mut state, 0, 0, 1, 0, 0); assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 0); assert_eq!(state.source_directory_checks, 1); assert_eq!(state.events[0], DECOMPRESS_POLICY_OPEN_SOURCE); } #[test] fn decompress_file_policy_orders_cleanup_and_successful_source_removal() { let mut state = DecompressPolicyState { source_file_size: FIO_RUST_DECOMPRESS_SRC_ASYNC_THRESHOLD - 1, source_is_regular: 1, ..DecompressPolicyState::default() }; let projection = decompress_policy_projection(&mut state, 0, 0, 0, 0, 1); assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 0); assert_eq!(state.async_modes, vec![0]); assert_eq!(state.transfers, vec![1]); assert_eq!(state.destination_fds, vec![37]); assert_eq!( state.events, vec![ DECOMPRESS_POLICY_OPEN_SOURCE, DECOMPRESS_POLICY_ASYNC, DECOMPRESS_POLICY_ATTACH_SOURCE, DECOMPRESS_POLICY_OPEN_DESTINATION, DECOMPRESS_POLICY_ATTACH_DESTINATION, DECOMPRESS_POLICY_ADD_HANDLER, DECOMPRESS_POLICY_DECOMPRESS, DECOMPRESS_POLICY_CLEAR_HANDLER, DECOMPRESS_POLICY_SET_FD_STAT, DECOMPRESS_POLICY_CLOSE_DESTINATION, DECOMPRESS_POLICY_UTIME_DESTINATION, DECOMPRESS_POLICY_DETACH_SOURCE, DECOMPRESS_POLICY_CLOSE_SOURCE, DECOMPRESS_POLICY_CLEAR_HANDLER, DECOMPRESS_POLICY_REMOVE_SOURCE, ] ); } #[test] fn decompress_file_policy_removes_failed_destination_and_skips_source_removal() { let mut state = DecompressPolicyState { source_file_size: FIO_RUST_DECOMPRESS_SRC_ASYNC_THRESHOLD, decompress_status: 1, destination_close_status: 1, ..DecompressPolicyState::default() }; let projection = decompress_policy_projection(&mut state, 0, 0, 0, 0, 1); assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 1); assert_eq!(state.async_modes, vec![1]); assert_eq!(state.transfers, vec![0]); assert!(!state.events.contains(&DECOMPRESS_POLICY_REMOVE_SOURCE)); assert_eq!( state.events, vec![ DECOMPRESS_POLICY_OPEN_SOURCE, DECOMPRESS_POLICY_ASYNC, DECOMPRESS_POLICY_ATTACH_SOURCE, DECOMPRESS_POLICY_OPEN_DESTINATION, DECOMPRESS_POLICY_ATTACH_DESTINATION, DECOMPRESS_POLICY_ADD_HANDLER, DECOMPRESS_POLICY_DECOMPRESS, DECOMPRESS_POLICY_CLEAR_HANDLER, DECOMPRESS_POLICY_CLOSE_DESTINATION, DECOMPRESS_POLICY_REMOVE_DESTINATION, DECOMPRESS_POLICY_DETACH_SOURCE, DECOMPRESS_POLICY_CLOSE_SOURCE, ] ); } #[test] fn decompress_file_policy_does_not_remove_source_after_close_error() { let mut state = DecompressPolicyState { source_file_size: FIO_RUST_DECOMPRESS_SRC_UNKNOWN_SIZE, source_close_status: 1, ..DecompressPolicyState::default() }; let projection = decompress_policy_projection(&mut state, 1, 0, 1, 1, 1); assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 1); assert_eq!(state.async_modes, vec![1]); assert_eq!( state.events, vec![ DECOMPRESS_POLICY_OPEN_SOURCE, DECOMPRESS_POLICY_ASYNC, DECOMPRESS_POLICY_ATTACH_SOURCE, DECOMPRESS_POLICY_DECOMPRESS, DECOMPRESS_POLICY_DETACH_SOURCE, DECOMPRESS_POLICY_CLOSE_SOURCE, ] ); } fn run_zstd_adapt(policy: c_int, projection: &FIO_rust_zstd_adapt_projection_t) -> c_int { unsafe { FIO_rust_zstd_adapt(policy, projection) } } #[test] fn zstd_adapt_slows_when_output_backlog_grows() { let projection = FIO_rust_zstd_adapt_projection_t { newly_produced: 10, newly_flushed: 8, ..FIO_rust_zstd_adapt_projection_t::default() }; assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG, &projection), FIO_RUST_ZSTD_ADAPT_SLOWER ); } #[test] fn zstd_adapt_slows_when_output_is_blocked() { let projection = FIO_rust_zstd_adapt_projection_t { consumed: 100, previous_consumed: 100, ..FIO_rust_zstd_adapt_projection_t::default() }; assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED, &projection), FIO_RUST_ZSTD_ADAPT_SLOWER ); } #[test] fn zstd_adapt_slows_when_input_never_blocks() { let projection = FIO_rust_zstd_adapt_projection_t::default(); assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION, &projection), FIO_RUST_ZSTD_ADAPT_SLOWER ); } #[test] fn zstd_adapt_speeds_up_when_blocked_input_lags_both_sides() { let projection = FIO_rust_zstd_adapt_projection_t { input_blocked: 2, input_presented: 8, newly_ingested: 34, newly_consumed: 32, newly_produced: 32, newly_flushed: 34, ..FIO_rust_zstd_adapt_projection_t::default() }; assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT, &projection), FIO_RUST_ZSTD_ADAPT_FASTER ); } #[test] fn zstd_adapt_preserves_noop_boundaries() { let backlog_waiting = FIO_rust_zstd_adapt_projection_t { newly_produced: 10, newly_flushed: 8, flush_waiting: 1, ..FIO_rust_zstd_adapt_projection_t::default() }; let insufficiently_blocked = FIO_rust_zstd_adapt_projection_t { input_blocked: 1, input_presented: 8, newly_ingested: 34, newly_consumed: 32, newly_produced: 32, newly_flushed: 34, ..FIO_rust_zstd_adapt_projection_t::default() }; assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG, &backlog_waiting), FIO_RUST_ZSTD_ADAPT_NO_CHANGE ); assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT, &insufficiently_blocked), FIO_RUST_ZSTD_ADAPT_NO_CHANGE ); } #[test] fn zstd_adapt_clamps_levels_and_avoids_zero() { let slower_max = FIO_rust_zstd_adapt_projection_t { compression_level: 21, max_adapt_level: 20, max_c_level: 22, ..FIO_rust_zstd_adapt_projection_t::default() }; let faster_min = FIO_rust_zstd_adapt_projection_t { compression_level: 2, min_adapt_level: 3, ..FIO_rust_zstd_adapt_projection_t::default() }; let slower_zero = FIO_rust_zstd_adapt_projection_t { compression_level: 0, max_adapt_level: 0, max_c_level: 22, ..FIO_rust_zstd_adapt_projection_t::default() }; let faster_zero = FIO_rust_zstd_adapt_projection_t { compression_level: 1, min_adapt_level: 0, ..FIO_rust_zstd_adapt_projection_t::default() }; assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER, &slower_max), 20 ); assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER, &faster_min), 3 ); assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER, &slower_zero), 1 ); assert_eq!( run_zstd_adapt(FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER, &faster_zero), -1 ); } fn test_prefs(async_io: c_int) -> FIO_prefs_t { let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; prefs.asyncIO = async_io; prefs.testMode = 1; prefs.sparseFileSupport = 1; prefs } #[derive(Default)] struct CompressionCallbackState { zstd_calls: usize, gzip_calls: usize, lzma_calls: usize, lz4_calls: usize, lzma_plain: c_int, lz4_checksum: c_int, } unsafe extern "C" fn record_compress_zstd( _f_ctx: *mut c_void, _prefs: *mut c_void, ress: *mut c_void, _src_file_name: *const c_char, _file_size: u64, _compression_level: c_int, read_size: *mut u64, ) -> u64 { let state = unsafe { &mut *ress.cast::() }; state.zstd_calls += 1; unsafe { *read_size = 11 }; 7 } unsafe extern "C" fn record_compress_gzip( ress: *mut c_void, _src_file_name: *const c_char, _file_size: u64, _compression_level: c_int, read_size: *mut u64, ) -> u64 { let state = unsafe { &mut *ress.cast::() }; state.gzip_calls += 1; unsafe { *read_size = 12 }; 8 } unsafe extern "C" fn record_compress_lzma( ress: *mut c_void, _src_file_name: *const c_char, _file_size: u64, _compression_level: c_int, read_size: *mut u64, plain_lzma: c_int, ) -> u64 { let state = unsafe { &mut *ress.cast::() }; state.lzma_calls += 1; state.lzma_plain = plain_lzma; unsafe { *read_size = 13 }; 9 } unsafe extern "C" fn record_compress_lz4( ress: *mut c_void, _src_file_name: *const c_char, _file_size: u64, _compression_level: c_int, checksum: c_int, read_size: *mut u64, ) -> u64 { let state = unsafe { &mut *ress.cast::() }; state.lz4_calls += 1; state.lz4_checksum = checksum; unsafe { *read_size = 14 }; 10 } fn compression_test_callbacks() -> FIO_rust_compress_callbacks_t { FIO_rust_compress_callbacks_t { opaque: ptr::null_mut(), compress_zstd: Some(record_compress_zstd), compress_gzip: Some(record_compress_gzip), compress_lzma: Some(record_compress_lzma), compress_lz4: Some(record_compress_lz4), display_input: None, display_status: None, } } #[derive(Default)] struct CompressionDisplayState { input_sizes: Vec, status_sizes: Vec<(u64, u64)>, } unsafe extern "C" fn record_compress_input( opaque: *mut c_void, _src_file_name: *const c_char, file_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.input_sizes.push(file_size); } unsafe extern "C" fn record_compress_status( opaque: *mut c_void, _f_ctx: *mut c_void, _dst_file_name: *const c_char, _src_file_name: *const c_char, read_size: u64, compressed_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.status_sizes.push((read_size, compressed_size)); } #[test] fn compression_selection_dispatches_each_format_and_forwards_options() { let source = c"source"; let destination = c"destination"; let cases = [ (99, 1, 0_usize, 0, 11_u64, 7_u64), (FIO_ZSTD_COMPRESSION, 1, 0, 0, 11, 7), (FIO_GZIP_COMPRESSION, 1, 1, 0, 12, 8), (FIO_XZ_COMPRESSION, 1, 2, 0, 13, 9), (FIO_LZMA_COMPRESSION, 1, 2, 1, 13, 9), (FIO_LZ4_COMPRESSION, 2, 3, 0, 14, 10), ]; for ( compression_type, checksum, expected_codec, expected_plain, expected_read, expected_output, ) in cases { let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; prefs.compressionType = compression_type; prefs.checksumFlag = checksum; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 1, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 0, totalBytesInput: 100, totalBytesOutput: 200, }; let callbacks = compression_test_callbacks(); let mut state = CompressionCallbackState::default(); assert_eq!( unsafe { FIO_rust_compressFilenameInternal( (&mut context as *mut FIO_rust_compression_context_t).cast(), &mut prefs, (&mut state as *mut CompressionCallbackState).cast(), destination.as_ptr(), source.as_ptr(), 123, 5, &callbacks, ) }, FIO_RUST_COMPRESS_OK ); assert_eq!( [ state.zstd_calls, state.gzip_calls, state.lzma_calls, state.lz4_calls, ][expected_codec], 1 ); assert_eq!( state.zstd_calls + state.gzip_calls + state.lzma_calls + state.lz4_calls, 1 ); assert_eq!(state.lzma_plain, expected_plain); assert_eq!( state.lz4_checksum, if expected_codec == 3 { checksum } else { 0 } ); assert_eq!(context.totalBytesInput, 100 + expected_read as usize); assert_eq!(context.totalBytesOutput, 200 + expected_output as usize); } } #[test] fn compression_accounting_calls_display_hooks_after_codec_success() { let source = c"source"; let destination = c"destination"; let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; prefs.compressionType = FIO_ZSTD_COMPRESSION; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 1, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 0, totalBytesInput: 0, totalBytesOutput: 0, }; let mut codec_state = CompressionCallbackState::default(); let mut display_state = CompressionDisplayState::default(); let mut callbacks = compression_test_callbacks(); callbacks.opaque = (&mut display_state as *mut CompressionDisplayState).cast(); callbacks.display_input = Some(record_compress_input); callbacks.display_status = Some(record_compress_status); assert_eq!( unsafe { FIO_rust_compressFilenameInternal( (&mut context as *mut FIO_rust_compression_context_t).cast(), &mut prefs, (&mut codec_state as *mut CompressionCallbackState).cast(), destination.as_ptr(), source.as_ptr(), 123, 5, &callbacks, ) }, FIO_RUST_COMPRESS_OK ); assert_eq!(display_state.input_sizes, vec![123]); assert_eq!(display_state.status_sizes, vec![(11, 7)]); assert_eq!(context.totalBytesInput, 11); assert_eq!(context.totalBytesOutput, 7); } #[test] fn compression_missing_optional_codec_leaves_accounting_unchanged() { let source = c"source"; let destination = c"destination"; let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; prefs.compressionType = FIO_GZIP_COMPRESSION; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 1, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 0, totalBytesInput: 31, totalBytesOutput: 47, }; let mut callbacks = compression_test_callbacks(); callbacks.compress_gzip = None; assert_eq!( unsafe { FIO_rust_compressFilenameInternal( (&mut context as *mut FIO_rust_compression_context_t).cast(), &mut prefs, ptr::dangling_mut::(), destination.as_ptr(), source.as_ptr(), 123, 5, &callbacks, ) }, FIO_RUST_COMPRESS_GZIP_UNSUPPORTED ); assert_eq!(context.totalBytesInput, 31); assert_eq!(context.totalBytesOutput, 47); } #[derive(Default)] struct MultipleCompressionState { sources: Vec<*const c_char>, destinations: Vec<*const c_char>, statuses: Vec, } #[derive(Default)] struct MultipleSummaryState { events: Vec<&'static str>, compression: Option<(c_int, u64, u64)>, decompression: Option<(c_int, u64)>, } unsafe extern "C" fn record_multiple_summary_progress(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("progress"); } unsafe extern "C" fn record_multiple_compression_summary( opaque: *mut c_void, nb_files_processed: c_int, total_bytes_input: u64, total_bytes_output: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("compression"); state.compression = Some(( nb_files_processed, total_bytes_input, total_bytes_output, )); } unsafe extern "C" fn record_multiple_decompression_summary( opaque: *mut c_void, nb_files_processed: c_int, total_bytes_output: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("decompression"); state.decompression = Some((nb_files_processed, total_bytes_output)); } fn multiple_summary_callbacks( state: &mut MultipleSummaryState, ) -> FIO_rust_multiple_summary_callbacks_t { FIO_rust_multiple_summary_callbacks_t { opaque: (state as *mut MultipleSummaryState).cast(), display_progress: Some(record_multiple_summary_progress), display_compression: Some(record_multiple_compression_summary), display_decompression: Some(record_multiple_decompression_summary), } } #[test] fn multiple_file_summary_dispatches_compression_after_progress() { let mut state = MultipleSummaryState::default(); let callbacks = multiple_summary_callbacks(&mut state); unsafe { FIO_rust_displayMultipleFileSummary( 3, 2, 4096, 1024, FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION, &callbacks, ) }; assert_eq!(state.events, ["progress", "compression"]); assert_eq!(state.compression, Some((2, 4096, 1024))); assert_eq!(state.decompression, None); } #[test] fn multiple_file_summary_dispatches_decompression_and_skips_empty_sessions() { let mut state = MultipleSummaryState::default(); let callbacks = multiple_summary_callbacks(&mut state); unsafe { FIO_rust_displayMultipleFileSummary( 4, 3, 0, 777, FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION, &callbacks, ) }; assert_eq!(state.events, ["progress", "decompression"]); assert_eq!(state.decompression, Some((3, 777))); state.events.clear(); state.decompression = None; unsafe { FIO_rust_displayMultipleFileSummary( 1, 3, 10, 20, FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION, &callbacks, ) }; unsafe { FIO_rust_displayMultipleFileSummary( 4, 0, 10, 20, FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION, &callbacks, ) }; assert!(state.events.is_empty()); assert_eq!(state.decompression, None); } unsafe extern "C" fn record_multiple_compression_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.sources.push(source); state.destinations.push(destination); status } unsafe extern "C" fn record_multiple_separate_compression_file( opaque: *mut c_void, 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.sources.push(source); status } #[derive(Default)] struct SeparateCompressionRoutingState { routes: Vec<&'static str>, sources: Vec<*const c_char>, statuses: Vec, } fn record_separate_compression_route( opaque: *mut c_void, source: *const c_char, route: &'static str, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; let status = state .statuses .get(state.sources.len()) .copied() .unwrap_or(0); state.routes.push(route); state.sources.push(source); status } unsafe extern "C" fn record_mirrored_compression_file( opaque: *mut c_void, source: *const c_char, ) -> c_int { record_separate_compression_route(opaque, source, "mirror") } unsafe extern "C" fn record_flat_compression_file( opaque: *mut c_void, source: *const c_char, ) -> c_int { record_separate_compression_route(opaque, source, "flat") } #[derive(Default)] struct MultipleDecompressionState { sources: Vec<*const c_char>, destinations: Vec<*const c_char>, statuses: Vec, } unsafe extern "C" fn record_multiple_decompression_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.sources.push(source); state.destinations.push(destination); status } unsafe extern "C" fn record_multiple_separate_decompression_file( opaque: *mut c_void, 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.sources.push(source); 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()]; 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 = SeparateCompressionRoutingState::default(); let projection = FIO_rust_compress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), input_file_names: source_names.as_ptr(), opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(), mirror_output: 1, compress_mirrored_file: Some(record_mirrored_compression_file), compress_flat_file: Some(record_flat_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) }, 0 ); assert_eq!(state.routes, vec!["mirror", "mirror"]); assert_eq!(state.sources, source_names); assert_eq!(context.currFileIdx, 2); assert_eq!(context.nbFilesProcessed, 2); } #[test] fn compression_multiple_separate_destinations_selects_flat_callback_only() { let source_names = [c"one".as_ptr(), c"two".as_ptr()]; 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 = SeparateCompressionRoutingState::default(); let projection = FIO_rust_compress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), input_file_names: source_names.as_ptr(), opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(), mirror_output: 0, compress_mirrored_file: Some(record_mirrored_compression_file), compress_flat_file: Some(record_flat_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) }, 0 ); assert_eq!(state.routes, vec!["flat", "flat"]); assert_eq!(state.sources, source_names); assert_eq!(context.currFileIdx, 2); assert_eq!(context.nbFilesProcessed, 2); } #[test] fn compression_multiple_separate_destinations_accumulates_selected_callback_errors_in_order() { let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()]; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 4, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = SeparateCompressionRoutingState { statuses: vec![1, 4, 2], ..SeparateCompressionRoutingState::default() }; let projection = FIO_rust_compress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), input_file_names: source_names.as_ptr(), opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(), mirror_output: 1, compress_mirrored_file: Some(record_mirrored_compression_file), compress_flat_file: Some(record_flat_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) }, 1 | 4 | 2 ); assert_eq!(state.routes, vec!["mirror", "mirror", "mirror"]); assert_eq!(state.sources, source_names); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 4); } #[test] fn compression_multiple_separate_destinations_preserves_order_and_counters() { let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()]; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 1, nbFilesProcessed: 5, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleCompressionState { statuses: vec![0, 0], ..MultipleCompressionState::default() }; let projection = FIO_rust_compress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), input_file_names: source_names.as_ptr(), opaque: (&mut state as *mut MultipleCompressionState).cast(), mirror_output: 0, compress_mirrored_file: Some(record_multiple_separate_compression_file), compress_flat_file: Some(record_multiple_separate_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) }, 0 ); assert_eq!(state.sources, vec![source_names[1], source_names[2]]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 7); } #[test] fn compression_multiple_separate_destinations_accumulates_errors_without_short_circuiting() { let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()]; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 4, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleCompressionState { statuses: vec![1, 4, 2], ..MultipleCompressionState::default() }; let projection = FIO_rust_compress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), input_file_names: source_names.as_ptr(), opaque: (&mut state as *mut MultipleCompressionState).cast(), mirror_output: 0, compress_mirrored_file: Some(record_multiple_separate_compression_file), compress_flat_file: Some(record_multiple_separate_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) }, 1 | 4 | 2 ); assert_eq!(state.sources, source_names); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 4); } #[test] fn compression_multiple_shared_destination_preserves_order_and_counters() { let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()]; let destination = c"archive.zst"; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 1, nbFilesProcessed: 5, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleCompressionState { statuses: vec![0, 0], ..MultipleCompressionState::default() }; let projection = 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 MultipleCompressionState).cast(), compress_file: Some(record_multiple_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleFilenames(&projection) }, 0 ); assert_eq!(state.sources, vec![source_names[1], source_names[2]]); assert_eq!(state.destinations, vec![destination.as_ptr(); 2]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 7); } #[test] fn compression_multiple_shared_destination_accumulates_errors_without_short_circuiting() { let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()]; let destination = c"archive.zst"; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 4, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleCompressionState { statuses: vec![1, 4, 2], ..MultipleCompressionState::default() }; let projection = 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 MultipleCompressionState).cast(), compress_file: Some(record_multiple_compression_file), }; assert_eq!( unsafe { FIO_rust_compressMultipleFilenames(&projection) }, 1 | 4 | 2 ); assert_eq!(state.sources, source_names); assert_eq!(state.destinations, vec![destination.as_ptr(); 3]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 4); } #[test] fn decompression_multiple_shared_destination_preserves_order_and_counters() { let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()]; let destination = c"archive"; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 1, nbFilesProcessed: 5, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleDecompressionState { statuses: vec![0, 0], ..MultipleDecompressionState::default() }; let projection = 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 MultipleDecompressionState).cast(), decompress_file: Some(record_multiple_decompression_file), }; assert_eq!( unsafe { FIO_rust_decompressMultipleFilenames(&projection) }, 0 ); assert_eq!(state.sources, vec![source_names[1], source_names[2]]); assert_eq!(state.destinations, vec![destination.as_ptr(); 2]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 7); } #[test] fn decompression_multiple_shared_destination_accumulates_errors_without_short_circuiting() { let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()]; let destination = c"archive"; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 4, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleDecompressionState { statuses: vec![1, 4, 2], ..MultipleDecompressionState::default() }; let projection = 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 MultipleDecompressionState).cast(), decompress_file: Some(record_multiple_decompression_file), }; assert_eq!( unsafe { FIO_rust_decompressMultipleFilenames(&projection) }, 1 | 4 | 2 ); assert_eq!(state.sources, source_names); assert_eq!(state.destinations, vec![destination.as_ptr(); 3]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 4); } #[test] fn decompression_multiple_separate_destinations_preserves_order_and_counters() { let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()]; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 1, nbFilesProcessed: 5, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleDecompressionState { statuses: vec![0, 0], ..MultipleDecompressionState::default() }; let projection = FIO_rust_decompress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), src_names_table: source_names.as_ptr(), opaque: (&mut state as *mut MultipleDecompressionState).cast(), decompress_file: Some(record_multiple_separate_decompression_file), }; assert_eq!( unsafe { FIO_rust_decompressMultipleSeparateFilenames(&projection) }, 0 ); assert_eq!(state.sources, vec![source_names[1], source_names[2]]); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 7); } #[test] fn decompression_multiple_separate_destinations_accumulates_errors_without_short_circuiting() { let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()]; let mut context = FIO_rust_compression_context_t { nbFilesTotal: 3, hasStdinInput: 0, hasStdoutOutput: 0, currFileIdx: 0, nbFilesProcessed: 4, totalBytesInput: 0, totalBytesOutput: 0, }; let mut state = MultipleDecompressionState { statuses: vec![1, 4, 2], ..MultipleDecompressionState::default() }; let projection = FIO_rust_decompress_multiple_separate_projection_t { f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(), src_names_table: source_names.as_ptr(), opaque: (&mut state as *mut MultipleDecompressionState).cast(), decompress_file: Some(record_multiple_separate_decompression_file), }; assert_eq!( unsafe { FIO_rust_decompressMultipleSeparateFilenames(&projection) }, 1 | 4 | 2 ); assert_eq!(state.sources, source_names); assert_eq!(context.currFileIdx, 3); assert_eq!(context.nbFilesProcessed, 4); } struct ZstdCodecCallbackTestState { pending_flush: usize, codec_result: usize, observed_input: Option<(usize, usize, usize)>, observed_output: Option<(usize, usize, usize)>, observed_directive: c_int, } unsafe extern "C" fn zstd_test_to_flush_now(opaque: *mut c_void) -> usize { let state = unsafe { &mut *opaque.cast::() }; state.pending_flush } unsafe extern "C" fn zstd_test_compress_stream2( opaque: *mut c_void, output: *mut ZSTD_outBuffer, input: *mut ZSTD_inBuffer, directive: c_int, ) -> usize { let state = unsafe { &mut *opaque.cast::() }; let input = unsafe { &mut *input }; let output = unsafe { &mut *output }; state.observed_input = Some((input.src as usize, input.size, input.pos)); state.observed_output = Some((output.dst as usize, output.size, output.pos)); state.observed_directive = directive; input.pos = input.size; output.pos = 3; state.codec_result } #[test] fn zstd_codec_callback_builds_public_views_and_preserves_results() { let input = b"abcdef"; let mut output = [0_u8; 8]; let mut state = ZstdCodecCallbackTestState { pending_flush: 19, codec_result: 0, observed_input: None, observed_output: None, observed_directive: -1, }; let mut input_pos_after = 0; let mut output_produced = 0; let mut to_flush_now = 0; let mut zstd_result = 0; let cctx = (&mut state as *mut ZstdCodecCallbackTestState).cast::(); assert_eq!( unsafe { fio_zstd_compress_stream_with( cctx, 2, input.as_ptr(), input.len(), 1, output.as_mut_ptr(), output.len(), &mut input_pos_after, &mut output_produced, &mut to_flush_now, &mut zstd_result, zstd_test_to_flush_now, zstd_test_compress_stream2, ) }, 0 ); assert_eq!( state.observed_input, Some((input.as_ptr() as usize, input.len(), 1)) ); assert_eq!( state.observed_output, Some((output.as_mut_ptr() as usize, output.len(), 0)) ); assert_eq!(state.observed_directive, 2); assert_eq!(input_pos_after, input.len()); assert_eq!(output_produced, 3); assert_eq!(to_flush_now, 19); assert_eq!(zstd_result, 0); let mut error_state = ZstdCodecCallbackTestState { pending_flush: 19, codec_result: usize::MAX, observed_input: None, observed_output: None, observed_directive: -1, }; let error_cctx = (&mut error_state as *mut ZstdCodecCallbackTestState).cast::(); assert_eq!( unsafe { fio_zstd_compress_stream_with( error_cctx, 0, input.as_ptr(), input.len(), 0, output.as_mut_ptr(), output.len(), &mut input_pos_after, &mut output_produced, &mut to_flush_now, &mut zstd_result, zstd_test_to_flush_now, zstd_test_compress_stream2, ) }, 1 ); assert_eq!(zstd_result, usize::MAX); } struct ZstdProjectionState { input: [u8; 5], input_pos: usize, output_buffer: [u8; 4], consumed: Vec, enqueue_sizes: Vec, output_chunks: Vec>, directives: Vec, iterations: Vec<(usize, usize, usize, c_int)>, acquire_calls: usize, release_calls: usize, sparse_end_calls: usize, compress_calls: usize, codec_error: Option, } impl Default for ZstdProjectionState { fn default() -> Self { Self { input: *b"abcde", input_pos: 0, output_buffer: [0; 4], consumed: Vec::new(), enqueue_sizes: Vec::new(), output_chunks: Vec::new(), directives: Vec::new(), iterations: Vec::new(), acquire_calls: 0, release_calls: 0, sparse_end_calls: 0, compress_calls: 0, codec_error: None, } } } #[derive(Default)] struct AdaptiveCallbackState { progression: FIO_rust_zstd_progression_t, events: Vec, } unsafe extern "C" fn adaptive_test_progression( opaque: *mut c_void, progression: *mut FIO_rust_zstd_progression_t, ) { let state = unsafe { &*opaque.cast::() }; unsafe { *progression = state.progression; } } unsafe extern "C" fn adaptive_test_set_parameter(opaque: *mut c_void, level: c_int) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(100 + level); } unsafe extern "C" fn adaptive_test_diagnostic( opaque: *mut c_void, diagnostic: c_int, _projection: *const FIO_rust_zstd_adapt_projection_t, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(diagnostic); } fn adaptive_test_projection( state: &mut AdaptiveCallbackState, ) -> FIO_rust_zstd_compress_projection_t { let opaque = (state as *mut AdaptiveCallbackState).cast::(); FIO_rust_zstd_compress_projection_t { read_opaque: ptr::null_mut(), write_opaque: ptr::null_mut(), codec_opaque: ptr::null_mut(), policy_opaque: opaque, read_buffer_size: 1, read_fill: None, read_consume: None, write_acquire: None, write_enqueue: None, write_release: None, sparse_write_end: None, compress_stream: None, iteration: None, compress_display: None, adaptive_mode: 1, nb_workers: 1, min_adapt_level: 1, max_adapt_level: 5, max_c_level: 22, adaptive_progression: Some(adaptive_test_progression), adaptive_set_parameter: Some(adaptive_test_set_parameter), adaptive_diagnostic: Some(adaptive_test_diagnostic), } } #[test] fn zstd_adaptive_iteration_preserves_callback_order_and_resets_state() { let mut callbacks = AdaptiveCallbackState { progression: FIO_rust_zstd_progression_t { produced: 10, flushed: 8, current_job_id: 3, ..FIO_rust_zstd_progression_t::default() }, ..AdaptiveCallbackState::default() }; let projection = adaptive_test_projection(&mut callbacks); let mut state = ZstdAdaptiveState::default(); assert!(state.last_refresh.is_none()); let mut compression_level = 3; unsafe { zstd_adaptive_iteration(&mut state, &projection, &mut compression_level, 0, 1, 1); } assert_eq!( callbacks.events, vec![ FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED, FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG, FIO_RUST_ZSTD_ADAPT_DIAG_CHECK, FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION, FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL, 104, ] ); assert!(state.last_refresh.is_some()); assert_eq!(compression_level, 4); assert_eq!(state.previous_update, callbacks.progression); assert_eq!(state.last_job_id, 3); assert_eq!(state.speed_change, FIO_RUST_ZSTD_ADAPT_NO_CHANGE); assert_eq!(state.input_blocked, 0); assert_eq!(state.input_presented, 0); assert_eq!(state.flush_waiting, 0); } unsafe extern "C" fn zstd_test_read_fill( opaque: *mut c_void, requested: usize, buffer: *mut *const u8, loaded: *mut usize, ) -> usize { let state = unsafe { &mut *opaque.cast::() }; let available = state.input.len() - state.input_pos; let amount = available.min(requested); unsafe { *buffer = state.input.as_ptr().add(state.input_pos); *loaded = amount; } amount } unsafe extern "C" fn zstd_test_read_consume(opaque: *mut c_void, amount: usize) { let state = unsafe { &mut *opaque.cast::() }; assert!(amount <= state.input.len() - state.input_pos); state.input_pos += amount; state.consumed.push(amount); } unsafe extern "C" fn zstd_test_write_acquire( opaque: *mut c_void, job: *mut *mut c_void, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.acquire_calls += 1; unsafe { *job = opaque; *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn zstd_test_write_enqueue( opaque: *mut c_void, job: *mut *mut c_void, used: usize, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(unsafe { *job }, opaque); assert!(used <= state.output_buffer.len()); state.enqueue_sizes.push(used); state .output_chunks .push(state.output_buffer[..used].to_vec()); unsafe { *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn zstd_test_write_release(opaque: *mut c_void, job: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(job, opaque); state.release_calls += 1; } unsafe extern "C" fn zstd_test_sparse_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.sparse_end_calls += 1; } unsafe extern "C" fn zstd_test_compress( opaque: *mut c_void, _src_file_name: *const c_char, directive: c_int, _input: *const u8, input_size: usize, input_pos: usize, output: *mut u8, output_size: usize, input_pos_after: *mut usize, output_produced: *mut usize, to_flush_now: *mut usize, zstd_result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.compress_calls += 1; state.directives.push(directive); let remaining = input_size - input_pos; let consumed = remaining.min(2); let needs_final_flush = directive == FIO_RUST_ZSTD_E_END && consumed == remaining; let result = if needs_final_flush && remaining != 0 { 1 } else { 0 }; unsafe { *input_pos_after = input_pos + consumed; *output_produced = 1; *to_flush_now = 1; *zstd_result = result; assert!(output_size != 0); *output = b'x'; } if let Some(error) = state.codec_error { unsafe { *input_pos_after = input_pos; *output_produced = 0; *zstd_result = error; } return 1; } 0 } unsafe extern "C" fn zstd_test_iteration( opaque: *mut c_void, _src_file_name: *const c_char, compression_level: *mut c_int, old_input_pos: usize, new_input_pos: usize, to_flush_now: usize, ) { let state = unsafe { &mut *opaque.cast::() }; state .iterations .push((old_input_pos, new_input_pos, to_flush_now, unsafe { *compression_level })); } fn zstd_test_projection( state: &mut ZstdProjectionState, ) -> FIO_rust_zstd_compress_projection_t { let opaque = (state as *mut ZstdProjectionState).cast::(); FIO_rust_zstd_compress_projection_t { read_opaque: opaque, write_opaque: opaque, codec_opaque: opaque, policy_opaque: opaque, read_buffer_size: 3, read_fill: Some(zstd_test_read_fill), read_consume: Some(zstd_test_read_consume), write_acquire: Some(zstd_test_write_acquire), write_enqueue: Some(zstd_test_write_enqueue), write_release: Some(zstd_test_write_release), sparse_write_end: Some(zstd_test_sparse_end), compress_stream: Some(zstd_test_compress), iteration: Some(zstd_test_iteration), compress_display: None, adaptive_mode: 0, nb_workers: 0, min_adapt_level: 0, max_adapt_level: 0, max_c_level: 0, adaptive_progression: None, adaptive_set_parameter: None, adaptive_diagnostic: None, } } #[test] fn zstd_projection_preserves_stream_order_and_accounting() { let mut state = ZstdProjectionState::default(); let projection = zstd_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut zstd_result = 0; assert_eq!( unsafe { FIO_rust_compressZstdFrame( &projection, c"zstd-test".as_ptr(), 5, 3, &mut read_size, &mut compressed_size, &mut zstd_result, ) }, FIO_RUST_ZSTD_OK ); assert_eq!(read_size, 5); assert_eq!(compressed_size, 4); assert_eq!(zstd_result, 0); assert_eq!(state.consumed, vec![2, 1, 2, 0]); assert_eq!(state.enqueue_sizes, vec![1, 1, 1, 1]); assert_eq!(state.output_chunks, vec![vec![b'x']; 4]); assert_eq!(state.compress_calls, 4); assert_eq!( state.directives, vec![ FIO_RUST_ZSTD_E_CONTINUE, FIO_RUST_ZSTD_E_CONTINUE, FIO_RUST_ZSTD_E_END, FIO_RUST_ZSTD_E_END, ] ); assert_eq!(state.iterations.len(), 4); assert_eq!(state.acquire_calls, 1); assert_eq!(state.release_calls, 1); assert_eq!(state.sparse_end_calls, 1); } #[test] fn zstd_projection_does_not_consume_input_after_codec_error() { let mut state = ZstdProjectionState { codec_error: Some(17), ..ZstdProjectionState::default() }; let projection = zstd_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut zstd_result = 0; assert_eq!( unsafe { FIO_rust_compressZstdFrame( &projection, c"zstd-test".as_ptr(), 5, 3, &mut read_size, &mut compressed_size, &mut zstd_result, ) }, FIO_RUST_ZSTD_COMPRESS_ERROR ); assert_eq!(zstd_result, 17); assert_eq!(read_size, 3); assert_eq!(compressed_size, 0); assert_eq!(state.compress_calls, 1); assert!(state.consumed.is_empty()); assert_eq!(state.release_calls, 0); assert_eq!(state.sparse_end_calls, 0); } #[test] fn zstd_projection_reports_incomplete_input_without_releasing_output() { let mut state = ZstdProjectionState::default(); let projection = zstd_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut zstd_result = 0; assert_eq!( unsafe { FIO_rust_compressZstdFrame( &projection, c"zstd-test".as_ptr(), 7, 3, &mut read_size, &mut compressed_size, &mut zstd_result, ) }, FIO_RUST_ZSTD_INCOMPLETE_INPUT ); assert_eq!(read_size, 5); assert_eq!(compressed_size, 4); assert_eq!(zstd_result, 0); assert_eq!(state.release_calls, 0); assert_eq!(state.sparse_end_calls, 0); } struct GzipProjectionState { input: [u8; 5], input_pos: usize, output_buffer: [u8; 4], read_fill_calls: usize, consumed: Vec, enqueue_sizes: Vec, output_chunks: Vec>, acquire_calls: usize, release_calls: usize, sparse_end_calls: usize, init_levels: Vec, deflate_flushes: Vec, finish_calls: usize, end_calls: usize, progress: Vec<(u64, u64, u64)>, init_result: c_int, } impl Default for GzipProjectionState { fn default() -> Self { Self { input: *b"abcde", input_pos: 0, output_buffer: [0; 4], read_fill_calls: 0, consumed: Vec::new(), enqueue_sizes: Vec::new(), output_chunks: Vec::new(), acquire_calls: 0, release_calls: 0, sparse_end_calls: 0, init_levels: Vec::new(), deflate_flushes: Vec::new(), finish_calls: 0, end_calls: 0, progress: Vec::new(), init_result: FIO_RUST_GZIP_Z_OK, } } } unsafe extern "C" fn gzip_test_read_fill( opaque: *mut c_void, requested: usize, buffer: *mut *const u8, loaded: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.read_fill_calls += 1; let available = state.input.len() - state.input_pos; let amount = available.min(requested); unsafe { *buffer = state.input.as_ptr().add(state.input_pos); *loaded = amount; } } unsafe extern "C" fn gzip_test_read_consume(opaque: *mut c_void, amount: usize) { let state = unsafe { &mut *opaque.cast::() }; assert!(amount <= state.input.len() - state.input_pos); state.input_pos += amount; state.consumed.push(amount); } unsafe extern "C" fn gzip_test_write_acquire( opaque: *mut c_void, job: *mut *mut c_void, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.acquire_calls += 1; unsafe { *job = opaque; *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn gzip_test_write_enqueue( opaque: *mut c_void, job: *mut *mut c_void, used: usize, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(unsafe { *job }, opaque); assert!(used <= state.output_buffer.len()); state.enqueue_sizes.push(used); state .output_chunks .push(state.output_buffer[..used].to_vec()); unsafe { *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn gzip_test_write_release(opaque: *mut c_void, job: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(job, opaque); state.release_calls += 1; } unsafe extern "C" fn gzip_test_sparse_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.sparse_end_calls += 1; } unsafe extern "C" fn gzip_test_zlib_init(opaque: *mut c_void, level: c_int) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.init_levels.push(level); state.init_result } unsafe extern "C" fn gzip_test_zlib_deflate( opaque: *mut c_void, _input: *const u8, input_size: usize, output: *mut u8, output_size: usize, flush: c_int, consumed: *mut usize, produced: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.deflate_flushes.push(flush); unsafe { *consumed = 0; *produced = 0; } if flush == FIO_RUST_GZIP_Z_NO_FLUSH { assert!(input_size != 0); assert!(output_size != 0); unsafe { *consumed = input_size.min(2); *produced = 1; *output = b'x'; } return FIO_RUST_GZIP_Z_OK; } state.finish_calls += 1; if state.finish_calls == 1 { return FIO_RUST_GZIP_Z_BUF_ERROR; } assert!(output_size >= 2); unsafe { *produced = 2; *output.add(0) = b'y'; *output.add(1) = b'z'; } FIO_RUST_GZIP_Z_STREAM_END } unsafe extern "C" fn gzip_test_zlib_end(opaque: *mut c_void) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.end_calls += 1; FIO_RUST_GZIP_Z_OK } unsafe extern "C" fn gzip_test_progress( opaque: *mut c_void, _src_file_size: u64, in_file_size: u64, out_file_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state .progress .push((_src_file_size, in_file_size, out_file_size)); } fn gzip_test_projection( state: &mut GzipProjectionState, ) -> FIO_rust_gzip_compress_projection_t { let opaque = (state as *mut GzipProjectionState).cast::(); FIO_rust_gzip_compress_projection_t { read_opaque: opaque, write_opaque: opaque, zlib_opaque: opaque, progress_opaque: opaque, read_buffer_size: 3, read_fill: Some(gzip_test_read_fill), read_consume: Some(gzip_test_read_consume), write_acquire: Some(gzip_test_write_acquire), write_enqueue: Some(gzip_test_write_enqueue), write_release: Some(gzip_test_write_release), sparse_write_end: Some(gzip_test_sparse_end), zlib_init: Some(gzip_test_zlib_init), zlib_deflate: Some(gzip_test_zlib_deflate), zlib_end: Some(gzip_test_zlib_end), progress: Some(gzip_test_progress), } } #[test] fn gzip_projection_preserves_pool_order_and_accounting() { let mut state = GzipProjectionState::default(); let projection = gzip_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut zlib_result = 0; assert_eq!( unsafe { FIO_rust_compressGzipFrame( &projection, c"gzip-test".as_ptr(), 5, 99, &mut read_size, &mut compressed_size, &mut zlib_result, ) }, FIO_RUST_GZIP_OK ); assert_eq!(read_size, 5); assert_eq!(compressed_size, 5); assert_eq!(zlib_result, FIO_RUST_GZIP_Z_OK); assert_eq!(state.init_levels, vec![FIO_RUST_GZIP_BEST_COMPRESSION]); assert_eq!(state.consumed, vec![2, 1, 2]); assert_eq!(state.enqueue_sizes, vec![1, 1, 1, 2]); assert_eq!( state.output_chunks, vec![vec![b'x'], vec![b'x'], vec![b'x'], vec![b'y', b'z']] ); assert_eq!(state.deflate_flushes, vec![0, 0, 0, 4, 4]); assert_eq!(state.finish_calls, 2); assert_eq!(state.acquire_calls, 1); assert_eq!(state.release_calls, 1); assert_eq!(state.sparse_end_calls, 1); assert_eq!(state.end_calls, 1); assert_eq!(state.progress, vec![(5, 3, 1), (5, 3, 2), (5, 5, 3)]); } #[test] fn gzip_projection_propagates_init_error_before_pool_acquisition() { let mut state = GzipProjectionState { init_result: -7, ..GzipProjectionState::default() }; let projection = gzip_test_projection(&mut state); let mut read_size = 41; let mut compressed_size = 43; let mut zlib_result = 0; assert_eq!( unsafe { FIO_rust_compressGzipFrame( &projection, c"gzip-test".as_ptr(), 5, 1, &mut read_size, &mut compressed_size, &mut zlib_result, ) }, FIO_RUST_GZIP_INIT_ERROR ); assert_eq!(zlib_result, -7); assert_eq!(read_size, 0); assert_eq!(compressed_size, 0); assert_eq!(state.init_levels, vec![1]); assert_eq!(state.acquire_calls, 0); assert_eq!(state.release_calls, 0); assert_eq!(state.sparse_end_calls, 0); } struct LzmaProjectionState { input: [u8; 5], input_pos: usize, output_buffer: [u8; 4], read_fill_calls: usize, consumed: Vec, enqueue_sizes: Vec, output_chunks: Vec>, acquire_calls: usize, release_calls: usize, sparse_end_calls: usize, init_args: Vec<(c_int, c_int)>, init_status: c_int, init_result: c_int, actions: Vec, finish_calls: usize, code_result: Option, end_calls: usize, progress: Vec<(u64, u64, u64)>, } impl Default for LzmaProjectionState { fn default() -> Self { Self { input: *b"abcde", input_pos: 0, output_buffer: [0; 4], read_fill_calls: 0, consumed: Vec::new(), enqueue_sizes: Vec::new(), output_chunks: Vec::new(), acquire_calls: 0, release_calls: 0, sparse_end_calls: 0, init_args: Vec::new(), init_status: FIO_RUST_LZMA_OK, init_result: FIO_RUST_LZMA_OK_CODE, actions: Vec::new(), finish_calls: 0, code_result: None, end_calls: 0, progress: Vec::new(), } } } unsafe extern "C" fn lzma_test_read_fill( opaque: *mut c_void, requested: usize, buffer: *mut *const u8, loaded: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.read_fill_calls += 1; let available = state.input.len() - state.input_pos; let amount = available.min(requested); unsafe { *buffer = state.input.as_ptr().add(state.input_pos); *loaded = amount; } } unsafe extern "C" fn lzma_test_read_consume(opaque: *mut c_void, amount: usize) { let state = unsafe { &mut *opaque.cast::() }; assert!(amount <= state.input.len() - state.input_pos); state.input_pos += amount; state.consumed.push(amount); } unsafe extern "C" fn lzma_test_write_acquire( opaque: *mut c_void, job: *mut *mut c_void, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.acquire_calls += 1; unsafe { *job = opaque; *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn lzma_test_write_enqueue( opaque: *mut c_void, job: *mut *mut c_void, used: usize, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(unsafe { *job }, opaque); assert!(used <= state.output_buffer.len()); state.enqueue_sizes.push(used); state .output_chunks .push(state.output_buffer[..used].to_vec()); unsafe { *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn lzma_test_write_release(opaque: *mut c_void, job: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(job, opaque); state.release_calls += 1; } unsafe extern "C" fn lzma_test_sparse_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.sparse_end_calls += 1; } unsafe extern "C" fn lzma_test_init( opaque: *mut c_void, level: c_int, plain_lzma: c_int, result: *mut c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.init_args.push((level, plain_lzma)); unsafe { *result = state.init_result }; state.init_status } unsafe extern "C" fn lzma_test_code( opaque: *mut c_void, _input: *const u8, input_size: usize, output: *mut u8, output_size: usize, action: c_int, consumed: *mut usize, produced: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.actions.push(action); unsafe { *consumed = 0; *produced = 0; } if action == FIO_RUST_LZMA_RUN { assert!(input_size != 0); assert!(output_size != 0); unsafe { *consumed = input_size.min(2); *produced = 1; *output = b'r'; } return state.code_result.unwrap_or(FIO_RUST_LZMA_OK_CODE); } assert_eq!(action, FIO_RUST_LZMA_FINISH); assert_eq!(input_size, 0); assert!(output_size != 0); state.finish_calls += 1; if state.finish_calls == 1 { unsafe { *produced = 1; *output = b'f'; } return FIO_RUST_LZMA_OK_CODE; } assert!(output_size >= 2); unsafe { *produced = 2; *output.add(0) = b'g'; *output.add(1) = b'h'; } FIO_RUST_LZMA_STREAM_END } unsafe extern "C" fn lzma_test_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.end_calls += 1; } unsafe extern "C" fn lzma_test_progress( opaque: *mut c_void, src_file_size: u64, in_file_size: u64, out_file_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state .progress .push((src_file_size, in_file_size, out_file_size)); } fn lzma_test_projection( state: &mut LzmaProjectionState, ) -> FIO_rust_lzma_compress_projection_t { let opaque = (state as *mut LzmaProjectionState).cast::(); FIO_rust_lzma_compress_projection_t { read_opaque: opaque, write_opaque: opaque, lzma_opaque: opaque, progress_opaque: opaque, read_buffer_size: 3, read_fill: Some(lzma_test_read_fill), read_consume: Some(lzma_test_read_consume), write_acquire: Some(lzma_test_write_acquire), write_enqueue: Some(lzma_test_write_enqueue), write_release: Some(lzma_test_write_release), sparse_write_end: Some(lzma_test_sparse_end), lzma_init: Some(lzma_test_init), lzma_code: Some(lzma_test_code), lzma_end: Some(lzma_test_end), progress: Some(lzma_test_progress), } } #[test] fn lzma_projection_preserves_run_finish_order_and_accounting() { let mut state = LzmaProjectionState::default(); let projection = lzma_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut lzma_result = 0; assert_eq!( unsafe { FIO_rust_compressLzmaFrame( &projection, c"lzma-test".as_ptr(), 5, 99, 1, &mut read_size, &mut compressed_size, &mut lzma_result, ) }, FIO_RUST_LZMA_OK ); assert_eq!(read_size, 5); assert_eq!(compressed_size, 6); assert_eq!(lzma_result, FIO_RUST_LZMA_OK_CODE); assert_eq!(state.init_args, vec![(9, 1)]); assert_eq!(state.read_fill_calls, 4); assert_eq!(state.consumed, vec![2, 1, 2, 0, 0]); assert_eq!( state.actions, vec![ FIO_RUST_LZMA_RUN, FIO_RUST_LZMA_RUN, FIO_RUST_LZMA_RUN, FIO_RUST_LZMA_FINISH, FIO_RUST_LZMA_FINISH, ] ); assert_eq!(state.enqueue_sizes, vec![1, 1, 1, 1, 2]); assert_eq!( state.output_chunks, vec![ vec![b'r'], vec![b'r'], vec![b'r'], vec![b'f'], vec![b'g', b'h'] ] ); assert_eq!(state.acquire_calls, 1); assert_eq!(state.release_calls, 1); assert_eq!(state.sparse_end_calls, 1); assert_eq!(state.finish_calls, 2); assert_eq!(state.end_calls, 1); assert_eq!( state.progress, vec![(5, 3, 1), (5, 3, 2), (5, 5, 3), (5, 5, 4), (5, 5, 6)] ); } #[test] fn lzma_projection_clamps_level_and_stops_before_pool_on_init_error() { let mut state = LzmaProjectionState { init_status: FIO_RUST_LZMA_INIT_XZ_ERROR, init_result: 11, ..LzmaProjectionState::default() }; let projection = lzma_test_projection(&mut state); let mut read_size = 41; let mut compressed_size = 43; let mut lzma_result = 0; assert_eq!( unsafe { FIO_rust_compressLzmaFrame( &projection, c"lzma-test".as_ptr(), 5, -7, 0, &mut read_size, &mut compressed_size, &mut lzma_result, ) }, FIO_RUST_LZMA_INIT_XZ_ERROR ); assert_eq!(lzma_result, 11); assert_eq!(read_size, 0); assert_eq!(compressed_size, 0); assert_eq!(state.init_args, vec![(0, 0)]); assert_eq!(state.acquire_calls, 0); assert_eq!(state.end_calls, 0); assert_eq!(state.sparse_end_calls, 0); } #[test] fn lzma_projection_propagates_code_error_after_consuming_input() { let mut state = LzmaProjectionState { code_result: Some(17), ..LzmaProjectionState::default() }; let projection = lzma_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut lzma_result = 0; assert_eq!( unsafe { FIO_rust_compressLzmaFrame( &projection, c"lzma-test".as_ptr(), 5, 3, 1, &mut read_size, &mut compressed_size, &mut lzma_result, ) }, FIO_RUST_LZMA_CODE_ERROR ); assert_eq!(lzma_result, 17); assert_eq!(read_size, 0); assert_eq!(compressed_size, 0); assert_eq!(state.consumed, vec![2]); assert_eq!(state.enqueue_sizes, Vec::::new()); assert_eq!(state.release_calls, 0); assert_eq!(state.end_calls, 0); assert_eq!(state.sparse_end_calls, 0); } struct Lz4ProjectionState { input: [u8; 7], input_len: usize, input_pos: usize, output_buffer: [u8; 8], events: Vec<&'static str>, consumed: Vec, enqueue_sizes: Vec, progress: Vec<(u64, u64, u64)>, prepare_args: Vec<(u64, c_int, c_int, usize, usize)>, acquire_calls: usize, release_calls: usize, sparse_end_calls: usize, create_status: c_int, create_result: usize, begin_status: c_int, begin_result: usize, update_status: c_int, update_result: usize, end_status: c_int, end_result: usize, } impl Default for Lz4ProjectionState { fn default() -> Self { Self { input: *b"abcdefg", input_len: 7, input_pos: 0, output_buffer: [0; 8], events: Vec::new(), consumed: Vec::new(), enqueue_sizes: Vec::new(), progress: Vec::new(), prepare_args: Vec::new(), acquire_calls: 0, release_calls: 0, sparse_end_calls: 0, create_status: 0, create_result: 0, begin_status: 0, begin_result: 1, update_status: 0, update_result: 2, end_status: 0, end_result: 3, } } } unsafe extern "C" fn lz4_test_create( opaque: *mut c_void, _version: c_uint, result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push("create"); unsafe { *result = state.create_result }; state.create_status } unsafe extern "C" fn lz4_test_prepare( opaque: *mut c_void, src_file_size: u64, compression_level: c_int, checksum_flag: c_int, block_size: usize, buffer_size: usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("prepare"); state.prepare_args.push(( src_file_size, compression_level, checksum_flag, block_size, buffer_size, )); } unsafe extern "C" fn lz4_test_begin( opaque: *mut c_void, _output: *mut u8, _output_size: usize, result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push("begin"); unsafe { *result = state.begin_result }; state.begin_status } unsafe extern "C" fn lz4_test_update( opaque: *mut c_void, output: *mut u8, output_size: usize, _input: *const u8, input_size: usize, result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push("update"); assert!(input_size != 0); if state.update_status == 0 { assert!(output_size >= state.update_result); unsafe { *output = b'u'; *result = state.update_result; } } else { unsafe { *result = state.update_result }; } state.update_status } unsafe extern "C" fn lz4_test_end( opaque: *mut c_void, _output: *mut u8, _output_size: usize, result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.events.push("end"); unsafe { *result = state.end_result }; state.end_status } unsafe extern "C" fn lz4_test_free(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("free"); } unsafe extern "C" fn lz4_test_read_fill( opaque: *mut c_void, requested: usize, buffer: *mut *const u8, loaded: *mut usize, ) -> usize { let state = unsafe { &mut *opaque.cast::() }; state.events.push("fill"); let available = state.input_len - state.input_pos; let amount = available.min(requested); unsafe { *buffer = state.input.as_ptr().add(state.input_pos); *loaded = amount; } amount } unsafe extern "C" fn lz4_test_read_consume(opaque: *mut c_void, amount: usize) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("consume"); assert!(amount <= state.input_len - state.input_pos); state.input_pos += amount; state.consumed.push(amount); } unsafe extern "C" fn lz4_test_write_acquire( opaque: *mut c_void, job: *mut *mut c_void, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("acquire"); state.acquire_calls += 1; unsafe { *job = opaque; *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn lz4_test_write_enqueue( opaque: *mut c_void, job: *mut *mut c_void, used: usize, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("enqueue"); assert_eq!(unsafe { *job }, opaque); assert!(used <= state.output_buffer.len()); state.enqueue_sizes.push(used); unsafe { *buffer = state.output_buffer.as_mut_ptr(); *buffer_size = state.output_buffer.len(); } } unsafe extern "C" fn lz4_test_write_release(opaque: *mut c_void, job: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("release"); assert_eq!(job, opaque); state.release_calls += 1; } unsafe extern "C" fn lz4_test_sparse_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("sparse"); state.sparse_end_calls += 1; } unsafe extern "C" fn lz4_test_progress( opaque: *mut c_void, src_file_size: u64, in_file_size: u64, out_file_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.events.push("progress"); state .progress .push((src_file_size, in_file_size, out_file_size)); } fn lz4_test_projection(state: &mut Lz4ProjectionState) -> FIO_rust_lz4_compress_projection_t { let opaque = (state as *mut Lz4ProjectionState).cast::(); FIO_rust_lz4_compress_projection_t { read_opaque: opaque, write_opaque: opaque, codec_opaque: opaque, progress_opaque: opaque, version: 100, block_size: 3, create: Some(lz4_test_create), prepare: Some(lz4_test_prepare), begin: Some(lz4_test_begin), update: Some(lz4_test_update), end: Some(lz4_test_end), free_context: Some(lz4_test_free), read_fill: Some(lz4_test_read_fill), read_consume: Some(lz4_test_read_consume), write_acquire: Some(lz4_test_write_acquire), write_enqueue: Some(lz4_test_write_enqueue), write_release: Some(lz4_test_write_release), sparse_write_end: Some(lz4_test_sparse_end), progress: Some(lz4_test_progress), } } #[test] fn lz4_projection_preserves_header_update_end_order_and_accounting() { let mut state = Lz4ProjectionState::default(); let projection = lz4_test_projection(&mut state); let mut read_size = 0; let mut compressed_size = 0; let mut lz4_result = 0; assert_eq!( unsafe { FIO_rust_compressLz4Frame( &projection, c"lz4-test".as_ptr(), 7, 12, 1, &mut read_size, &mut compressed_size, &mut lz4_result, ) }, FIO_RUST_LZ4_OK ); assert_eq!(read_size, 7); assert_eq!(compressed_size, 10); assert_eq!(lz4_result, 0); assert_eq!(state.prepare_args, vec![(7, 12, 1, 3, 8)]); assert_eq!(state.consumed, vec![3, 3, 1]); assert_eq!(state.enqueue_sizes, vec![1, 2, 2, 2, 3]); assert_eq!(state.progress, vec![(7, 3, 3), (7, 6, 5), (7, 7, 7)]); assert_eq!(state.acquire_calls, 1); assert_eq!(state.release_calls, 1); assert_eq!(state.sparse_end_calls, 1); assert_eq!( state.events, vec![ "acquire", "create", "prepare", "begin", "enqueue", "fill", "update", "progress", "enqueue", "consume", "fill", "update", "progress", "enqueue", "consume", "fill", "update", "progress", "enqueue", "consume", "fill", "end", "enqueue", "free", "release", "sparse", ] ); } #[test] fn lz4_projection_reports_creation_header_update_and_end_errors() { let cases = [ ( Lz4ProjectionState { create_status: 1, create_result: 17, ..Lz4ProjectionState::default() }, FIO_RUST_LZ4_CREATE_ERROR, 17, vec!["acquire", "create"], ), ( Lz4ProjectionState { begin_status: 1, begin_result: 23, ..Lz4ProjectionState::default() }, FIO_RUST_LZ4_HEADER_ERROR, 23, vec!["acquire", "create", "prepare", "begin"], ), ( Lz4ProjectionState { update_status: 1, update_result: 29, ..Lz4ProjectionState::default() }, FIO_RUST_LZ4_UPDATE_ERROR, 29, vec![ "acquire", "create", "prepare", "begin", "enqueue", "fill", "update", ], ), ( Lz4ProjectionState { input_len: 0, end_status: 1, end_result: 31, ..Lz4ProjectionState::default() }, FIO_RUST_LZ4_END_ERROR, 31, vec![ "acquire", "create", "prepare", "begin", "enqueue", "fill", "end", ], ), ]; for (mut state, expected_status, expected_result, expected_events) in cases { let projection = lz4_test_projection(&mut state); let mut read_size = 41; let mut compressed_size = 43; let mut lz4_result = 0; assert_eq!( unsafe { FIO_rust_compressLz4Frame( &projection, c"lz4-test".as_ptr(), 7, 1, 0, &mut read_size, &mut compressed_size, &mut lz4_result, ) }, expected_status ); assert_eq!(lz4_result, expected_result); assert_eq!(read_size, 0); assert_eq!(compressed_size, 0); assert_eq!(state.events, expected_events); assert_eq!(state.release_calls, 0); assert_eq!(state.sparse_end_calls, 0); } } #[derive(Default)] struct OptionalDecompressIoState { input: Vec, input_pos: usize, output: [u8; 4], fill_requests: Vec, consumed: Vec, enqueue_sizes: Vec, output_chunks: Vec>, acquire_calls: usize, release_calls: usize, sparse_end_calls: usize, } impl OptionalDecompressIoState { fn new(input: &[u8]) -> Self { Self { input: input.to_vec(), ..Self::default() } } } unsafe extern "C" fn optional_decompress_read_fill( opaque: *mut c_void, requested: usize, buffer: *mut *const u8, loaded: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.fill_requests.push(requested); let available = state.input.len() - state.input_pos; let amount = if requested == 0 { available } else { available.min(requested) }; unsafe { *buffer = state.input.as_ptr().add(state.input_pos); *loaded = amount; } } unsafe extern "C" fn optional_decompress_read_consume(opaque: *mut c_void, amount: usize) { let state = unsafe { &mut *opaque.cast::() }; assert!(amount <= state.input.len() - state.input_pos); state.input_pos += amount; state.consumed.push(amount); } unsafe extern "C" fn optional_decompress_write_acquire( opaque: *mut c_void, job: *mut *mut c_void, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; state.acquire_calls += 1; unsafe { *job = opaque; *buffer = state.output.as_mut_ptr(); *buffer_size = state.output.len(); } } unsafe extern "C" fn optional_decompress_write_enqueue( opaque: *mut c_void, job: *mut *mut c_void, used: usize, buffer: *mut *mut u8, buffer_size: *mut usize, ) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(unsafe { *job }, opaque); assert!(used <= state.output.len()); state.enqueue_sizes.push(used); state.output_chunks.push(state.output[..used].to_vec()); unsafe { *buffer = state.output.as_mut_ptr(); *buffer_size = state.output.len(); } } unsafe extern "C" fn optional_decompress_write_release(opaque: *mut c_void, job: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; assert_eq!(job, opaque); state.release_calls += 1; } unsafe extern "C" fn optional_decompress_sparse_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.sparse_end_calls += 1; } fn optional_decompress_io( state: &mut OptionalDecompressIoState, read_buffer_size: usize, ) -> FIO_rust_decompress_io_projection_t { let opaque = (state as *mut OptionalDecompressIoState).cast::(); FIO_rust_decompress_io_projection_t { read_opaque: opaque, write_opaque: opaque, read_buffer_size, read_fill: Some(optional_decompress_read_fill), read_consume: Some(optional_decompress_read_consume), write_acquire: Some(optional_decompress_write_acquire), write_enqueue: Some(optional_decompress_write_enqueue), write_release: Some(optional_decompress_write_release), sparse_write_end: Some(optional_decompress_sparse_end), } } struct GzipDecompressState { io: OptionalDecompressIoState, init_result: c_int, inflate_calls: usize, stop_after_calls: usize, finish_result: c_int, end_result: c_int, flushes: Vec, end_calls: usize, } impl GzipDecompressState { fn new(input: &[u8]) -> Self { Self { io: OptionalDecompressIoState::new(input), init_result: FIO_RUST_GZIP_DECOMPRESS_Z_OK, inflate_calls: 0, stop_after_calls: 0, finish_result: FIO_RUST_GZIP_DECOMPRESS_Z_STREAM_END, end_result: FIO_RUST_GZIP_DECOMPRESS_Z_OK, flushes: Vec::new(), end_calls: 0, } } } unsafe extern "C" fn gzip_decompress_test_init(opaque: *mut c_void) -> c_int { unsafe { (*opaque.cast::()).init_result } } unsafe extern "C" fn gzip_decompress_test_inflate( opaque: *mut c_void, _input: *const u8, input_size: usize, output: *mut u8, output_size: usize, flush: c_int, consumed: *mut usize, produced: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.inflate_calls += 1; state.flushes.push(flush); unsafe { *consumed = 0; *produced = 0; } if flush == FIO_RUST_GZIP_DECOMPRESS_Z_NO_FLUSH { assert!(input_size != 0); assert!(output_size != 0); if state.stop_after_calls != 0 && state.inflate_calls > state.stop_after_calls { unsafe { *produced = 1; *output = b'y'; } return FIO_RUST_GZIP_DECOMPRESS_Z_STREAM_END; } unsafe { *consumed = input_size.min(2); *produced = 1; *output = b'x'; } return FIO_RUST_GZIP_DECOMPRESS_Z_OK; } assert_eq!(flush, FIO_RUST_GZIP_DECOMPRESS_Z_FINISH); assert_eq!(input_size, 0); assert!(output_size != 0); unsafe { *produced = 1; *output = b'f'; } state.finish_result } unsafe extern "C" fn gzip_decompress_test_end(opaque: *mut c_void) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.end_calls += 1; state.end_result } fn gzip_decompress_test_projection( state: &mut GzipDecompressState, ) -> FIO_rust_gzip_decompress_projection_t { let codec_opaque = (state as *mut GzipDecompressState).cast::(); let io = optional_decompress_io(&mut state.io, 3); FIO_rust_gzip_decompress_projection_t { io, zlib_opaque: codec_opaque, zlib_init: Some(gzip_decompress_test_init), zlib_inflate: Some(gzip_decompress_test_inflate), zlib_end: Some(gzip_decompress_test_end), } } #[test] fn gzip_decompress_projection_preserves_trailing_input_and_accounting() { let mut state = GzipDecompressState::new(b"frame-tail"); state.stop_after_calls = 1; let projection = gzip_decompress_test_projection(&mut state); let mut frame_size = 0; let mut zlib_result = 0; assert_eq!( unsafe { FIO_rust_decompressGzipFrame(&projection, &mut frame_size, &mut zlib_result) }, FIO_RUST_GZIP_DECOMPRESS_OK ); assert_eq!(frame_size, 2); assert_eq!(zlib_result, FIO_RUST_GZIP_DECOMPRESS_Z_OK); assert_eq!(state.io.input_pos, 2); assert_eq!(state.io.consumed, vec![2, 0]); assert_eq!(state.io.enqueue_sizes, vec![1, 1]); assert_eq!(state.io.output_chunks, vec![vec![b'x'], vec![b'y']]); assert_eq!(state.flushes, vec![FIO_RUST_GZIP_DECOMPRESS_Z_NO_FLUSH; 2]); assert_eq!(state.end_calls, 1); assert_eq!(state.io.acquire_calls, 1); assert_eq!(state.io.release_calls, 1); assert_eq!(state.io.sparse_end_calls, 1); } #[test] fn gzip_decompress_projection_maps_eof_to_premature_error() { let mut state = GzipDecompressState::new(b"abc"); state.finish_result = FIO_RUST_GZIP_DECOMPRESS_Z_BUF_ERROR; let projection = gzip_decompress_test_projection(&mut state); let mut frame_size = 41; let mut zlib_result = 0; assert_eq!( unsafe { FIO_rust_decompressGzipFrame(&projection, &mut frame_size, &mut zlib_result) }, FIO_RUST_GZIP_DECOMPRESS_BUF_ERROR ); assert_eq!(frame_size, 0); assert_eq!(zlib_result, FIO_RUST_GZIP_DECOMPRESS_Z_BUF_ERROR); assert_eq!(state.io.input_pos, 3); assert_eq!(state.io.consumed, vec![2, 1, 0]); assert_eq!(state.end_calls, 1); assert_eq!(state.io.release_calls, 1); assert_eq!(state.io.sparse_end_calls, 1); } struct LzmaDecompressState { io: OptionalDecompressIoState, init_result: c_int, init_modes: Vec, actions: Vec, finish_result: c_int, code_error: Option, end_calls: usize, } impl LzmaDecompressState { fn new(input: &[u8]) -> Self { Self { io: OptionalDecompressIoState::new(input), init_result: FIO_RUST_LZMA_DECOMPRESS_OK_CODE, init_modes: Vec::new(), actions: Vec::new(), finish_result: FIO_RUST_LZMA_DECOMPRESS_STREAM_END, code_error: None, end_calls: 0, } } } unsafe extern "C" fn lzma_decompress_test_init( opaque: *mut c_void, plain_lzma: c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.init_modes.push(plain_lzma); state.init_result } unsafe extern "C" fn lzma_decompress_test_code( opaque: *mut c_void, _input: *const u8, input_size: usize, output: *mut u8, output_size: usize, action: c_int, consumed: *mut usize, produced: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.actions.push(action); unsafe { *consumed = 0; *produced = 0; } if action == FIO_RUST_LZMA_DECOMPRESS_RUN { assert!(input_size != 0); assert!(output_size != 0); unsafe { *consumed = input_size.min(2); *produced = 1; *output = b'r'; } return state.code_error.unwrap_or(FIO_RUST_LZMA_DECOMPRESS_OK_CODE); } assert_eq!(action, FIO_RUST_LZMA_DECOMPRESS_FINISH); assert_eq!(input_size, 0); assert!(output_size != 0); unsafe { *produced = 1; *output = b'f'; } state.finish_result } unsafe extern "C" fn lzma_decompress_test_end(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.end_calls += 1; } fn lzma_decompress_test_projection( state: &mut LzmaDecompressState, ) -> FIO_rust_lzma_decompress_projection_t { let codec_opaque = (state as *mut LzmaDecompressState).cast::(); let io = optional_decompress_io(&mut state.io, 3); FIO_rust_lzma_decompress_projection_t { io, lzma_opaque: codec_opaque, lzma_init: Some(lzma_decompress_test_init), lzma_code: Some(lzma_decompress_test_code), lzma_end: Some(lzma_decompress_test_end), } } #[test] fn lzma_decompress_projection_preserves_mode_and_short_read_accounting() { let mut state = LzmaDecompressState::new(b"abcde"); let projection = lzma_decompress_test_projection(&mut state); let mut frame_size = 0; let mut lzma_result = 0; assert_eq!( unsafe { FIO_rust_decompressLzmaFrame(&projection, 1, &mut frame_size, &mut lzma_result) }, FIO_RUST_LZMA_DECOMPRESS_OK ); assert_eq!(frame_size, 4); assert_eq!(lzma_result, FIO_RUST_LZMA_DECOMPRESS_OK_CODE); assert_eq!(state.init_modes, vec![1]); assert_eq!( state.actions, vec![ FIO_RUST_LZMA_DECOMPRESS_RUN, FIO_RUST_LZMA_DECOMPRESS_RUN, FIO_RUST_LZMA_DECOMPRESS_RUN, FIO_RUST_LZMA_DECOMPRESS_FINISH, ] ); assert_eq!(state.io.input_pos, 5); assert_eq!(state.io.consumed, vec![2, 2, 1, 0]); assert_eq!(state.io.enqueue_sizes, vec![1, 1, 1, 1]); assert_eq!(state.end_calls, 1); assert_eq!(state.io.release_calls, 1); assert_eq!(state.io.sparse_end_calls, 1); } #[test] fn lzma_decompress_projection_propagates_code_error_after_consumption() { let mut state = LzmaDecompressState::new(b"abc"); state.code_error = Some(17); let projection = lzma_decompress_test_projection(&mut state); let mut frame_size = 41; let mut lzma_result = 0; assert_eq!( unsafe { FIO_rust_decompressLzmaFrame(&projection, 0, &mut frame_size, &mut lzma_result) }, FIO_RUST_LZMA_DECOMPRESS_CODE_ERROR ); assert_eq!(frame_size, 0); assert_eq!(lzma_result, 17); assert_eq!(state.io.consumed, vec![2]); assert_eq!(state.end_calls, 1); assert_eq!(state.io.release_calls, 1); assert_eq!(state.io.sparse_end_calls, 1); } struct Lz4DecompressState { io: OptionalDecompressIoState, create_status: c_int, create_result: usize, code_calls: usize, code_error: Option, next_after_code: usize, unfinished: bool, free_calls: usize, progress: Vec, } impl Lz4DecompressState { fn new(input: &[u8]) -> Self { Self { io: OptionalDecompressIoState::new(input), create_status: 0, create_result: 0, code_calls: 0, code_error: None, next_after_code: 0, unfinished: false, free_calls: 0, progress: Vec::new(), } } } unsafe extern "C" fn lz4_decompress_test_create( opaque: *mut c_void, _version: c_uint, result: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; unsafe { *result = state.create_result }; state.create_status } unsafe extern "C" fn lz4_decompress_test_code( opaque: *mut c_void, output: *mut u8, output_size: *mut usize, _input: *const u8, input_size: *mut usize, next_to_load: *mut usize, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.code_calls += 1; if let Some(error) = state.code_error { unsafe { *input_size = 0; *next_to_load = error; } return error as c_int; } let consumed = if state.code_calls == 1 { (*input_size).min(2) } else { *input_size }; let produced = if state.unfinished && state.code_calls > 1 { 1 } else { *output_size }; assert!(produced != 0); unsafe { *input_size = consumed; std::ptr::write_bytes(output, b'u', produced); *next_to_load = if state.code_calls == 1 || state.unfinished { state.next_after_code } else { 0 }; } 0 } unsafe extern "C" fn lz4_decompress_test_free(opaque: *mut c_void) { let state = unsafe { &mut *opaque.cast::() }; state.free_calls += 1; } unsafe extern "C" fn lz4_decompress_test_progress(opaque: *mut c_void, decoded: u64) { let state = unsafe { &mut *opaque.cast::() }; state.progress.push(decoded); } fn lz4_decompress_test_projection( state: &mut Lz4DecompressState, ) -> FIO_rust_lz4_decompress_projection_t { let io_opaque = (&mut state.io as *mut OptionalDecompressIoState).cast::(); let codec_opaque = (state as *mut Lz4DecompressState).cast::(); FIO_rust_lz4_decompress_projection_t { io: FIO_rust_decompress_io_projection_t { read_opaque: io_opaque, write_opaque: io_opaque, read_buffer_size: 0, read_fill: Some(optional_decompress_read_fill), read_consume: Some(optional_decompress_read_consume), write_acquire: Some(optional_decompress_write_acquire), write_enqueue: Some(optional_decompress_write_enqueue), write_release: Some(optional_decompress_write_release), sparse_write_end: Some(optional_decompress_sparse_end), }, codec_opaque, progress_opaque: codec_opaque, version: 100, create: Some(lz4_decompress_test_create), code: Some(lz4_decompress_test_code), free_context: Some(lz4_decompress_test_free), progress: Some(lz4_decompress_test_progress), } } #[test] fn lz4_decompress_projection_retries_full_output_and_accounts_input() { let mut state = Lz4DecompressState::new(b"abcdTAIL"); state.next_after_code = 2; let projection = lz4_decompress_test_projection(&mut state); let mut frame_size = 0; let mut lz4_result = 0; assert_eq!( unsafe { FIO_rust_decompressLz4Frame(&projection, &mut frame_size, &mut lz4_result) }, FIO_RUST_LZ4_DECOMPRESS_OK ); assert_eq!(frame_size, 8); assert_eq!(lz4_result, 0); assert_eq!(state.code_calls, 2); assert_eq!(state.io.input_pos, 4); assert_eq!(state.io.consumed, vec![4]); assert_eq!(state.io.enqueue_sizes, vec![4, 4]); assert_eq!(state.progress, vec![4, 8]); assert_eq!(state.free_calls, 1); assert_eq!(state.io.release_calls, 1); assert_eq!(state.io.sparse_end_calls, 1); } #[test] fn lz4_decompress_projection_reports_errors_and_unfinished_input() { let mut error_state = Lz4DecompressState::new(b"abcd"); error_state.code_error = Some(23); let error_projection = lz4_decompress_test_projection(&mut error_state); let mut frame_size = 41; let mut lz4_result = 0; assert_eq!( unsafe { FIO_rust_decompressLz4Frame(&error_projection, &mut frame_size, &mut lz4_result) }, FIO_RUST_LZ4_DECOMPRESS_CODE_ERROR ); assert_eq!(frame_size, 0); assert_eq!(lz4_result, 23); assert_eq!(error_state.io.input_pos, 0); assert_eq!(error_state.free_calls, 1); assert_eq!(error_state.io.release_calls, 1); assert_eq!(error_state.io.sparse_end_calls, 1); let mut unfinished_state = Lz4DecompressState::new(b"abcd"); unfinished_state.next_after_code = 4; unfinished_state.unfinished = true; let unfinished_projection = lz4_decompress_test_projection(&mut unfinished_state); let mut frame_size = 41; let mut lz4_result = 0; assert_eq!( unsafe { FIO_rust_decompressLz4Frame( &unfinished_projection, &mut frame_size, &mut lz4_result, ) }, FIO_RUST_LZ4_DECOMPRESS_UNFINISHED ); assert_eq!(frame_size, 0); assert_eq!(unfinished_state.io.input_pos, 4); assert_eq!(unfinished_state.free_calls, 1); assert_eq!(unfinished_state.io.release_calls, 1); assert_eq!(unfinished_state.io.sparse_end_calls, 1); } #[test] fn classifies_all_cli_decompression_headers() { let is_mock_zstd = |buffer: &[u8]| buffer.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]); assert_eq!( classify_decompression_format(&[0x28, 0xB5, 0x2F, 0xFD], is_mock_zstd), DecompressionFormat::Zstd ); assert_eq!( classify_decompression_format(&[31, 139, 8, 0], is_mock_zstd), DecompressionFormat::Gzip ); assert_eq!( classify_decompression_format(&[0xFD, 0x37, 0x7A, 0x58], is_mock_zstd), DecompressionFormat::Xz ); assert_eq!( classify_decompression_format(&[0x5D, 0x00, 0x00, 0x80], is_mock_zstd), DecompressionFormat::Lzma ); assert_eq!( classify_decompression_format(&[0x04, 0x22, 0x4D, 0x18], is_mock_zstd), DecompressionFormat::Lz4 ); assert_eq!( classify_decompression_format(&[0x00, 0x01, 0x02, 0x03], is_mock_zstd), DecompressionFormat::Unsupported ); assert_eq!( classify_decompression_format(&[0x00, 0x01, 0x02], is_mock_zstd), DecompressionFormat::ShortHeader ); } #[cfg(unix)] struct DispatchTestState { read_ctx: *mut ReadPoolCtx_t, calls: Vec<(u64, c_int)>, } #[cfg(unix)] unsafe extern "C" fn record_dispatch_callback( opaque: *mut c_void, _src_file_name: *const c_char, already_decoded: u64, frame_size: *mut u64, error_code: *mut usize, mode: c_int, ) -> c_int { let state = unsafe { &mut *opaque.cast::() }; state.calls.push((already_decoded, mode)); let loaded = unsafe { read_buffer_loaded(state.read_ctx) }; unsafe { AIO_ReadPool_consumeBytes(state.read_ctx, loaded); *frame_size = 17; *error_code = 0; } 0 } #[cfg(unix)] #[test] fn dispatches_a_format_and_accumulates_callback_output() { let input_file = unsafe { libc::tmpfile() }; assert!(!input_file.is_null()); let header = [31_u8, 139, 8, 0]; assert_eq!( unsafe { libc::fwrite(header.as_ptr().cast(), 1, header.len(), input_file) }, header.len() ); assert_eq!(unsafe { libc::fflush(input_file) }, 0); assert_eq!(unsafe { libc::fseek(input_file, 0, libc::SEEK_SET) }, 0); let prefs = test_prefs(0); let read_ctx = unsafe { AIO_ReadPool_create(&prefs, 4) }; unsafe { AIO_ReadPool_setFile(read_ctx, input_file) }; let mut state = DispatchTestState { read_ctx, calls: Vec::new(), }; let callbacks = FIO_rust_decompress_callbacks_t { opaque: (&mut state as *mut DispatchTestState).cast(), decode_zstd: None, decode_gzip: Some(record_dispatch_callback), decode_lzma: None, decode_lz4: None, pass_through: None, report_status: None, finish: None, }; let mut decoded_size = 0; let status = unsafe { FIO_rust_decompressFrames( read_ctx, c"dispatch-test".as_ptr(), 0, &mut decoded_size, &callbacks, ) }; assert_eq!(status, FIO_RUST_DECOMPRESS_OK); assert_eq!(decoded_size, 17); assert_eq!(state.calls, vec![(0, 0)]); unsafe { AIO_ReadPool_free(read_ctx) }; } struct DecompressResultState { reported_statuses: Vec, finished_sizes: Vec, } unsafe extern "C" fn record_decompress_status( opaque: *mut c_void, status: c_int, _src_file_name: *const c_char, ) { let state = unsafe { &mut *opaque.cast::() }; state.reported_statuses.push(status); } unsafe extern "C" fn record_decompress_finish( opaque: *mut c_void, _src_file_name: *const c_char, decoded_size: u64, ) { let state = unsafe { &mut *opaque.cast::() }; state.finished_sizes.push(decoded_size); } #[test] fn classifies_decompression_finish_statuses() { for (status, expected_action) in [ (FIO_RUST_DECOMPRESS_OK, DecompressFinishAction::Finish), ( FIO_RUST_DECOMPRESS_PASS_THROUGH, DecompressFinishAction::PassThrough, ), ( FIO_RUST_DECOMPRESS_EMPTY_INPUT, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_SHORT_INPUT, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_FRAME_ERROR, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR, DecompressFinishAction::ReportStatus, ), ( FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED, DecompressFinishAction::ReportStatus, ), ] { assert_eq!(classify_decompress_finish_status(status), expected_action); } for status in [-1, 99] { assert_eq!( classify_decompress_finish_status(status), DecompressFinishAction::Invalid ); } } #[test] fn maps_decompression_status_and_finishes_only_success() { let mut state = DecompressResultState { reported_statuses: Vec::new(), finished_sizes: Vec::new(), }; let callbacks = FIO_rust_decompress_callbacks_t { opaque: (&mut state as *mut DecompressResultState).cast(), decode_zstd: None, decode_gzip: None, decode_lzma: None, decode_lz4: None, pass_through: None, report_status: Some(record_decompress_status), finish: Some(record_decompress_finish), }; let statuses = [ (FIO_RUST_DECOMPRESS_OK, 0), (FIO_RUST_DECOMPRESS_PASS_THROUGH, 0), (FIO_RUST_DECOMPRESS_EMPTY_INPUT, 1), (FIO_RUST_DECOMPRESS_SHORT_INPUT, 1), (FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED, 1), (FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, 1), (FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, 1), (FIO_RUST_DECOMPRESS_FRAME_ERROR, 1), (FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT, 1), (FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR, 1), (FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED, 1), ]; for (status, expected_result) in statuses { assert_eq!( unsafe { FIO_rust_finishDecompressFrames( status, c"decompress-result-test".as_ptr(), 123, &callbacks, ) }, expected_result ); } assert_eq!(state.finished_sizes, vec![123]); assert_eq!( state.reported_statuses, vec![ FIO_RUST_DECOMPRESS_EMPTY_INPUT, FIO_RUST_DECOMPRESS_SHORT_INPUT, FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED, FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED, FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, FIO_RUST_DECOMPRESS_FRAME_ERROR, FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT, FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR, FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED, ] ); } #[repr(C)] struct CBase { thread_pool: *mut c_void, thread_pool_active: c_int, total_io_jobs: c_int, prefs: *const FIO_prefs_t, pool_function: PoolFunction, file: *mut libc::FILE, io_jobs_mutex: M, available_jobs: [*mut c_void; MAX_IO_JOBS], available_jobs_count: c_int, job_buffer_size: usize, } #[repr(C)] struct CRead { base: CBase, reached_eof: c_int, next_read_offset: u64, waiting_on_offset: u64, current_job_held: *mut c_void, coalesce_buffer: *mut u8, src_buffer: *mut u8, src_buffer_loaded: usize, completed_jobs: [*mut c_void; MAX_IO_JOBS], completed_jobs_count: c_int, job_completed_cond: C, } #[repr(C)] struct CWrite { base: CBase, stored_skips: c_uint, } #[repr(C)] struct CJob { ctx: *mut c_void, file: *mut libc::FILE, buffer: *mut c_void, buffer_size: usize, used_buffer_size: usize, offset: u64, } fn assert_base_offsets(layout: AbiLayout) { assert_eq!( std::mem::offset_of!(CBase, thread_pool), layout.thread_pool ); assert_eq!( std::mem::offset_of!(CBase, thread_pool_active), layout.thread_pool_active ); assert_eq!( std::mem::offset_of!(CBase, total_io_jobs), layout.total_io_jobs ); assert_eq!(std::mem::offset_of!(CBase, prefs), layout.prefs); assert_eq!( std::mem::offset_of!(CBase, pool_function), layout.pool_function ); assert_eq!(std::mem::offset_of!(CBase, file), layout.file); assert_eq!( std::mem::offset_of!(CBase, io_jobs_mutex), layout.io_jobs_mutex ); assert_eq!( std::mem::offset_of!(CBase, available_jobs), layout.available_jobs ); assert_eq!( std::mem::offset_of!(CBase, available_jobs_count), layout.available_jobs_count ); assert_eq!( std::mem::offset_of!(CBase, job_buffer_size), layout.job_buffer_size ); } fn assert_read_offsets(layout: AbiLayout) { assert_eq!( std::mem::offset_of!(CRead, reached_eof), layout.read_reached_eof ); assert_eq!( std::mem::offset_of!(CRead, next_read_offset), layout.read_next_offset ); assert_eq!( std::mem::offset_of!(CRead, waiting_on_offset), layout.read_waiting_offset ); assert_eq!( std::mem::offset_of!(CRead, current_job_held), layout.read_current_job ); assert_eq!( std::mem::offset_of!(CRead, coalesce_buffer), layout.read_coalesce_buffer ); assert_eq!( std::mem::offset_of!(CRead, src_buffer), layout.read_src_buffer ); assert_eq!( std::mem::offset_of!(CRead, src_buffer_loaded), layout.read_src_buffer_loaded ); assert_eq!( std::mem::offset_of!(CRead, completed_jobs), layout.read_completed_jobs ); assert_eq!( std::mem::offset_of!(CRead, completed_jobs_count), layout.read_completed_jobs_count ); assert_eq!( std::mem::offset_of!(CRead, job_completed_cond), layout.read_job_completed_cond ); } #[test] fn calculated_context_layout_matches_the_c_structs() { let non_threaded = abi_layout(false); assert_base_offsets::(non_threaded); assert_read_offsets::(non_threaded); assert_eq!(non_threaded.read_reached_eof, size_of::>()); assert_eq!(non_threaded.read_size, size_of::>()); assert_eq!(non_threaded.write_size, size_of::>()); #[cfg(unix)] { let threaded = abi_layout(true); #[cfg(feature = "debug-pthread")] { type CThreadedBase = CBase<*mut libc::pthread_mutex_t>; type CThreadedRead = CRead<*mut libc::pthread_mutex_t, *mut libc::pthread_cond_t>; type CThreadedWrite = CWrite<*mut libc::pthread_mutex_t>; assert_base_offsets::<*mut libc::pthread_mutex_t>(threaded); assert_read_offsets::<*mut libc::pthread_mutex_t, *mut libc::pthread_cond_t>( threaded, ); assert_eq!(threaded.read_reached_eof, size_of::()); assert_eq!(threaded.read_size, size_of::()); assert_eq!(threaded.write_size, size_of::()); } #[cfg(not(feature = "debug-pthread"))] { type CThreadedBase = CBase; type CThreadedRead = CRead; type CThreadedWrite = CWrite; assert_base_offsets::(threaded); assert_read_offsets::(threaded); assert_eq!(threaded.read_reached_eof, size_of::()); assert_eq!(threaded.read_size, size_of::()); assert_eq!(threaded.write_size, size_of::()); } } assert_eq!(size_of::(), size_of::()); assert_eq!( std::mem::offset_of!(IOJob_t, ctx), std::mem::offset_of!(CJob, ctx) ); assert_eq!( std::mem::offset_of!(IOJob_t, file), std::mem::offset_of!(CJob, file) ); assert_eq!( std::mem::offset_of!(IOJob_t, buffer), std::mem::offset_of!(CJob, buffer) ); assert_eq!( std::mem::offset_of!(IOJob_t, bufferSize), std::mem::offset_of!(CJob, buffer_size) ); assert_eq!( std::mem::offset_of!(IOJob_t, usedBufferSize), std::mem::offset_of!(CJob, used_buffer_size) ); assert_eq!( std::mem::offset_of!(IOJob_t, offset), std::mem::offset_of!(CJob, offset) ); } #[test] fn non_threaded_pool_starts_with_two_available_jobs() { let prefs = test_prefs(0); let ctx = unsafe { AIO_WritePool_create(&prefs, 32) }; let base = ctx.cast::(); let inner = unsafe { base_inner(base) }; assert!(inner.queue.is_none()); assert_eq!( unsafe { read_at::(base, inner.layout.total_io_jobs) }, 2 ); assert_eq!( unsafe { read_at::(base, inner.layout.available_jobs_count) }, 2 ); let job = unsafe { AIO_WritePool_acquireJob(ctx) }; assert_eq!( unsafe { read_at::(base, inner.layout.available_jobs_count) }, 1 ); unsafe { AIO_WritePool_releaseIoJob(job) }; assert_eq!( unsafe { read_at::(base, inner.layout.available_jobs_count) }, 2 ); unsafe { AIO_WritePool_free(ctx) }; } #[test] fn async_pool_toggle_keeps_jobs_owned_by_the_pool() { let prefs = test_prefs(1); let ctx = unsafe { AIO_WritePool_create(&prefs, 32) }; let base = ctx.cast::(); let inner = unsafe { base_inner(base) }; assert!(inner.queue.is_some()); assert_eq!( unsafe { read_at::(base, inner.layout.available_jobs_count) }, MAX_IO_JOBS as c_int ); let mut job = unsafe { AIO_WritePool_acquireJob(ctx) }; unsafe { (*job).usedBufferSize = 0 }; unsafe { AIO_WritePool_enqueueAndReacquireWriteJob(&mut job) }; assert!(!job.is_null()); unsafe { AIO_WritePool_releaseIoJob(job) }; unsafe { AIO_WritePool_setAsync(ctx, 0) }; assert_eq!( unsafe { read_at::(base, inner.layout.thread_pool_active) }, 0 ); unsafe { AIO_WritePool_setAsync(ctx, 1) }; assert_eq!( unsafe { read_at::(base, inner.layout.thread_pool_active) }, 1 ); unsafe { AIO_WritePool_free(ctx) }; } #[test] fn async_pool_shutdown_drains_queued_jobs_before_freeing_buffers() { let prefs = test_prefs(1); let ctx = unsafe { AIO_WritePool_create(&prefs, 32) }; let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; for _ in 0..IO_QUEUE_SIZE { let job = unsafe { AIO_WritePool_acquireJob(ctx) }; unsafe { (*job).usedBufferSize = 0; inner.enqueue_job(job); } } // Freeing the context must join the worker before releasing any job // buffers. The queue is intentionally still populated here. unsafe { AIO_WritePool_free(ctx) }; } #[cfg(unix)] #[test] fn read_pool_preserves_file_order_in_threaded_and_non_threaded_modes() { let input = b"async file I/O keeps this order"; for async_io in [0, 1] { let file = unsafe { libc::tmpfile() }; assert!(!file.is_null()); assert_eq!( unsafe { libc::fwrite(input.as_ptr().cast(), 1, input.len(), file) }, input.len() ); assert_eq!(unsafe { libc::fflush(file) }, 0); assert_eq!(unsafe { libc::fseek(file, 0, libc::SEEK_SET) }, 0); let mut prefs = test_prefs(async_io); prefs.testMode = 0; let ctx = unsafe { AIO_ReadPool_create(&prefs, 4) }; unsafe { AIO_ReadPool_setFile(ctx, file) }; let mut output = Vec::new(); loop { unsafe { AIO_ReadPool_fillBuffer(ctx, 4) }; let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let loaded = unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }; if loaded == 0 { break; } let source = unsafe { read_at::<*const u8>(context, inner.layout.read_src_buffer) }; output.extend_from_slice(unsafe { std::slice::from_raw_parts(source, loaded) }); unsafe { AIO_ReadPool_consumeBytes(ctx, loaded) }; } assert_eq!(output, input); assert_eq!(unsafe { AIO_ReadPool_closeFile(ctx) }, 0); unsafe { AIO_ReadPool_free(ctx) }; } } #[test] fn read_buffer_consumption_preserves_unread_bytes_without_a_file() { let prefs = test_prefs(0); let ctx = unsafe { AIO_ReadPool_create(&prefs, 32) }; let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; let coalesce = unsafe { read_at::<*mut u8>(context, inner.layout.read_coalesce_buffer) }; unsafe { ptr::copy_nonoverlapping(b"abc".as_ptr(), coalesce, 3) }; unsafe { write_at(context, inner.layout.read_src_buffer, coalesce); write_at(context, inner.layout.read_src_buffer_loaded, 3_usize); } unsafe { AIO_ReadPool_consumeBytes(ctx, 1) }; assert_eq!( unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }, 2 ); assert_eq!( unsafe { *read_at::<*mut u8>(context, inner.layout.read_src_buffer) }, b'b' ); assert_eq!(unsafe { AIO_ReadPool_fillBuffer(ctx, 3) }, 0); assert_eq!( unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }, 2 ); unsafe { AIO_ReadPool_free(ctx) }; } #[test] fn read_set_file_none_resets_visible_stream_state() { let prefs = test_prefs(0); let ctx = unsafe { AIO_ReadPool_create(&prefs, 32) }; let context = ctx.cast::(); let inner = unsafe { base_inner(context) }; unsafe { write_at(context, inner.layout.read_reached_eof, 1 as c_int); write_at(context, inner.layout.read_next_offset, 123_u64); write_at(context, inner.layout.read_waiting_offset, 77_u64); write_at(context, inner.layout.read_src_buffer_loaded, 11_usize); } unsafe { AIO_ReadPool_setFile(ctx, ptr::null_mut()) }; assert_eq!( unsafe { read_at::(context, inner.layout.read_reached_eof) }, 0 ); assert_eq!( unsafe { read_at::(context, inner.layout.read_next_offset) }, 0 ); assert_eq!( unsafe { read_at::(context, inner.layout.read_waiting_offset) }, 0 ); assert_eq!( unsafe { read_at::(context, inner.layout.read_src_buffer_loaded) }, 0 ); unsafe { AIO_ReadPool_free(ctx) }; } #[cfg(unix)] fn run_pass_through( input: &[u8], read_buffer_size: usize, write_buffer_size: usize, async_io: c_int, ) -> Vec { let input_file = unsafe { libc::tmpfile() }; assert!(!input_file.is_null()); if !input.is_empty() { assert_eq!( unsafe { libc::fwrite(input.as_ptr().cast(), 1, input.len(), input_file) }, input.len() ); } assert_eq!(unsafe { libc::fflush(input_file) }, 0); assert_eq!(unsafe { libc::fseek(input_file, 0, libc::SEEK_SET) }, 0); let output_file = unsafe { libc::tmpfile() }; assert!(!output_file.is_null()); let mut prefs = test_prefs(async_io); prefs.testMode = 0; prefs.sparseFileSupport = 0; let read_ctx = unsafe { AIO_ReadPool_create(&prefs, read_buffer_size) }; let write_ctx = unsafe { AIO_WritePool_create(&prefs, write_buffer_size) }; unsafe { AIO_ReadPool_setFile(read_ctx, input_file); AIO_WritePool_setFile(write_ctx, output_file); } assert_eq!(unsafe { FIO_rust_passThrough(read_ctx, write_ctx) }, 0); assert_eq!(unsafe { libc::fseek(output_file, 0, libc::SEEK_END) }, 0); let output_size = unsafe { libc::ftell(output_file) }; assert!(output_size >= 0); let output_size = usize::try_from(output_size).unwrap(); assert_eq!(output_size, input.len()); assert_eq!(unsafe { libc::fseek(output_file, 0, libc::SEEK_SET) }, 0); let mut output = vec![0_u8; output_size]; if output_size != 0 { assert_eq!( unsafe { libc::fread(output.as_mut_ptr().cast(), 1, output_size, output_file) }, output_size ); } unsafe { AIO_WritePool_free(write_ctx); AIO_ReadPool_free(read_ctx); } output } #[cfg(unix)] #[test] fn pass_through_copies_across_bounded_job_buffers() { let input = b"pass-through data spans several blocks"; for async_io in [0, 1] { assert_eq!(run_pass_through(input, 5, 3, async_io).as_slice(), input); } } #[cfg(unix)] #[test] fn pass_through_handles_empty_input() { for async_io in [0, 1] { assert!(run_pass_through(&[], 5, 3, async_io).is_empty()); } } #[cfg(all(unix, feature = "compression"))] mod zstd_frame_tests { use super::*; #[derive(Default)] struct PolicyCallbackState { decoding_errors: Vec, premature_ends: usize, } unsafe extern "C" fn record_policy_decoding_error( opaque: *mut c_void, _source_name: *const c_char, error: usize, ) { unsafe { (*opaque.cast::()).decoding_errors.push(error) }; } unsafe extern "C" fn record_policy_premature_end( opaque: *mut c_void, _source_name: *const c_char, ) { unsafe { (*opaque.cast::()).premature_ends += 1 }; } #[test] fn zstd_frame_policy_preserves_result_classes_and_callback_order() { let mut context = PolicyCallbackState::default(); let callback_context = (&mut context as *mut PolicyCallbackState).cast(); assert_eq!( unsafe { zstd_frame_policy_result( callback_context, ptr::null(), FIO_RUST_ZSTD_FRAME_OK, 17, 0, record_policy_decoding_error, record_policy_premature_end, ) }, 17 ); assert!(context.decoding_errors.is_empty()); assert_eq!(context.premature_ends, 0); assert_eq!( unsafe { zstd_frame_policy_result( callback_context, ptr::null(), FIO_RUST_ZSTD_FRAME_DECODING_ERROR, 0, 23, record_policy_decoding_error, record_policy_premature_end, ) }, FIO_ERROR_FRAME_DECODING ); assert_eq!(context.decoding_errors, [23]); assert_eq!(context.premature_ends, 0); assert_eq!( unsafe { zstd_frame_policy_result( callback_context, ptr::null(), FIO_RUST_ZSTD_FRAME_PREMATURE_END, 0, 0, record_policy_decoding_error, record_policy_premature_end, ) }, FIO_ERROR_FRAME_DECODING ); assert_eq!(context.decoding_errors, [23]); assert_eq!(context.premature_ends, 1); } #[repr(C)] #[derive(Default)] struct ProgressState { calls: usize, last_decoded: u64, decoded_values: Vec, } unsafe extern "C" fn record_progress( opaque: *mut c_void, source_name: *const c_char, decoded_size: u64, ) { assert!(!opaque.is_null()); assert!(!source_name.is_null()); let state = unsafe { &mut *opaque.cast::() }; state.calls += 1; state.last_decoded = decoded_size; state.decoded_values.push(decoded_size); } const MOCK_FRAME_HEADER_SIZE: usize = 8; const MOCK_DSTREAM_IN_SIZE: usize = 8; const MOCK_FRAME_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD]; struct MockDecoder { header: [u8; MOCK_FRAME_HEADER_SIZE], header_len: usize, remaining: usize, } impl MockDecoder { fn new() -> Self { Self { header: [0; MOCK_FRAME_HEADER_SIZE], header_len: 0, remaining: 0, } } fn reset(&mut self) { self.header = [0; MOCK_FRAME_HEADER_SIZE]; self.header_len = 0; self.remaining = 0; } } fn encode_frame(input: &[u8]) -> Vec { assert!(u32::try_from(input.len()).is_ok()); let mut frame = Vec::with_capacity(MOCK_FRAME_HEADER_SIZE + input.len()); frame.extend_from_slice(&MOCK_FRAME_MAGIC); frame.extend_from_slice(&(input.len() as u32).to_le_bytes()); frame.extend_from_slice(input); frame } unsafe extern "C" fn mock_is_frame(buffer: *const c_void, size: usize) -> c_uint { if size < MOCK_FRAME_MAGIC.len() || buffer.is_null() { return 0; } let magic = unsafe { std::slice::from_raw_parts(buffer.cast::(), MOCK_FRAME_MAGIC.len()) }; u32::from(magic == MOCK_FRAME_MAGIC) } unsafe extern "C" fn mock_reset(dctx: *mut c_void, _reset: c_int) -> usize { unsafe { (*dctx.cast::()).reset() }; 0 } unsafe extern "C" fn mock_decompress( dctx: *mut c_void, output: *mut crate::zstd_decompress::ZSTD_outBuffer, input: *mut crate::zstd_decompress::ZSTD_inBuffer, ) -> usize { let decoder = unsafe { &mut *dctx.cast::() }; let input = unsafe { &mut *input }; let output = unsafe { &mut *output }; let call_start = input.pos; while decoder.header_len < MOCK_FRAME_HEADER_SIZE && input.pos < input.size { decoder.header[decoder.header_len] = unsafe { input.src.cast::().add(input.pos).read() }; decoder.header_len += 1; input.pos += 1; } if decoder.header_len < MOCK_FRAME_HEADER_SIZE { return (MOCK_FRAME_HEADER_SIZE - decoder.header_len).max(1); } if decoder.header[..MOCK_FRAME_MAGIC.len()] != MOCK_FRAME_MAGIC || decoder.header[4..].iter().all(|&byte| byte == 0xFF) { input.pos = call_start; return crate::errors::ERROR(crate::errors::ZstdErrorCode::Generic); } if decoder.remaining == 0 { let length = u32::from_le_bytes([ decoder.header[4], decoder.header[5], decoder.header[6], decoder.header[7], ]); decoder.remaining = length as usize; if decoder.remaining == 0 { return 0; } } let available = input.size - input.pos; let writable = output.size - output.pos; let copied = decoder.remaining.min(available).min(writable); if copied != 0 { unsafe { ptr::copy_nonoverlapping( input.src.cast::().add(input.pos), output.dst.cast::().add(output.pos), copied, ); } input.pos += copied; output.pos += copied; decoder.remaining -= copied; } if decoder.remaining == 0 { 0 } else { decoder.remaining.min(MOCK_DSTREAM_IN_SIZE) } } extern "C" fn mock_dstream_in_size() -> usize { MOCK_DSTREAM_IN_SIZE } struct FrameHarness { _prefs: Box, _decoder: Box, read_ctx: *mut ReadPoolCtx_t, write_ctx: *mut WritePoolCtx_t, dctx: *mut c_void, output_file: *mut libc::FILE, } impl FrameHarness { fn new(input: &[u8], read_buffer_size: usize, async_io: c_int) -> Self { let mut prefs = Box::new(test_prefs(async_io)); prefs.testMode = 0; prefs.sparseFileSupport = 0; let input_file = unsafe { libc::tmpfile() }; assert!(!input_file.is_null()); assert_eq!( unsafe { libc::fwrite(input.as_ptr().cast(), 1, input.len(), input_file) }, input.len() ); assert_eq!(unsafe { libc::fflush(input_file) }, 0); assert_eq!(unsafe { libc::fseek(input_file, 0, libc::SEEK_SET) }, 0); let output_file = unsafe { libc::tmpfile() }; assert!(!output_file.is_null()); let read_ctx = unsafe { AIO_ReadPool_create(&*prefs, read_buffer_size) }; let write_ctx = unsafe { AIO_WritePool_create(&*prefs, 128 * 1024) }; assert!(!read_ctx.is_null()); assert!(!write_ctx.is_null()); unsafe { AIO_ReadPool_setFile(read_ctx, input_file); AIO_WritePool_setFile(write_ctx, output_file); } let mut decoder = Box::new(MockDecoder::new()); let dctx = (&mut *decoder as *mut MockDecoder).cast::(); Self { _prefs: prefs, _decoder: decoder, read_ctx, write_ctx, dctx, output_file, } } fn decompress( &mut self, already_decoded: u64, progress_state: Option<&mut ProgressState>, ) -> (c_int, u64, usize) { let mut frame_size = 0; let mut zstd_error = 0; let progress_context = progress_state .map(|state| state as *mut ProgressState as *mut c_void) .unwrap_or(ptr::null_mut()); let progress_callback = if progress_context.is_null() { None } else { Some(record_progress as unsafe extern "C" fn(*mut c_void, *const c_char, u64)) }; let status = unsafe { decompress_zstd_frame_with( progress_context, self.dctx, self.read_ctx, self.write_ctx, c"frame-test.zst".as_ptr(), already_decoded, &mut frame_size, &mut zstd_error, progress_callback, mock_reset, mock_decompress, mock_dstream_in_size, ) }; (status, frame_size, zstd_error) } fn decompress_frames( &mut self, already_decoded: u64, progress_state: Option<&mut ProgressState>, ) -> (c_int, u64, usize) { let mut decoded_size = 0; let mut zstd_error = 0; let progress_context = progress_state .map(|state| state as *mut ProgressState as *mut c_void) .unwrap_or(ptr::null_mut()); let progress_callback = if progress_context.is_null() { None } else { Some(record_progress as unsafe extern "C" fn(*mut c_void, *const c_char, u64)) }; let status = unsafe { decompress_zstd_frames_with( progress_context, self.dctx, self.read_ctx, self.write_ctx, c"frame-test.zst".as_ptr(), already_decoded, &mut decoded_size, &mut zstd_error, progress_callback, mock_reset, mock_decompress, mock_dstream_in_size, mock_is_frame, ) }; (status, decoded_size, zstd_error) } fn unread_bytes(&self) -> Vec { let loaded = unsafe { read_buffer_loaded(self.read_ctx) }; let source = unsafe { read_buffer_ptr(self.read_ctx) }; unsafe { std::slice::from_raw_parts(source, loaded) }.to_vec() } fn output_bytes(&self) -> Vec { assert_eq!( unsafe { libc::fseek(self.output_file, 0, libc::SEEK_END) }, 0 ); let size = unsafe { libc::ftell(self.output_file) }; assert!(size >= 0); let size = usize::try_from(size).unwrap(); assert_eq!( unsafe { libc::fseek(self.output_file, 0, libc::SEEK_SET) }, 0 ); let mut output = vec![0_u8; size]; if size != 0 { assert_eq!( unsafe { libc::fread(output.as_mut_ptr().cast(), 1, size, self.output_file) }, size ); } output } } impl Drop for FrameHarness { fn drop(&mut self) { unsafe { AIO_WritePool_free(self.write_ctx); AIO_ReadPool_free(self.read_ctx); } } } #[test] fn decompresses_frame_streams_output_and_reports_progress() { let input: Vec = (0..300_000) .map(|index| (index as u32).wrapping_mul(37).rotate_left(5) as u8) .collect(); let frame = encode_frame(&input); for async_io in [0, 1] { let mut harness = FrameHarness::new(&frame, 97, async_io); let mut progress = ProgressState::default(); let (status, frame_size, zstd_error) = harness.decompress(0, Some(&mut progress)); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(frame_size, input.len() as u64); assert_eq!(zstd_error, 0); assert!(progress.calls > 1); assert!(progress.last_decoded < input.len() as u64); assert_eq!(harness.output_bytes(), input); } } #[test] fn progress_uses_the_pre_increment_frame_size() { let input = b"one streaming output block"; let frame = encode_frame(input); let mut harness = FrameHarness::new(&frame, frame.len() + 1, 0); let mut progress = ProgressState::default(); let (status, frame_size, zstd_error) = harness.decompress(37, Some(&mut progress)); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(frame_size, input.len() as u64); assert_eq!(zstd_error, 0); assert_eq!(progress.calls, 1); assert_eq!(progress.last_decoded, 37); } #[test] fn decompresses_one_frame_and_stops_at_eof() { let input = b"one frame followed by eof".repeat(4_000); let frame = encode_frame(&input); for async_io in [0, 1] { let mut harness = FrameHarness::new(&frame, 113, async_io); let (status, decoded_size, zstd_error) = harness.decompress_frames(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(decoded_size, input.len() as u64); assert_eq!(zstd_error, 0); assert!(harness.unread_bytes().is_empty()); assert_eq!(harness.output_bytes(), input); } } #[test] fn decompresses_concatenated_frames_and_accumulates_output() { let first = b"first concatenated frame".repeat(1_000); let second = b"second concatenated frame".repeat(1_000); let mut stream = encode_frame(&first); stream.extend_from_slice(&encode_frame(&second)); let mut harness = FrameHarness::new(&stream, 113, 1); let (status, decoded_size, zstd_error) = harness.decompress_frames(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(decoded_size, (first.len() + second.len()) as u64); assert_eq!(zstd_error, 0); assert!(harness.unread_bytes().is_empty()); let mut expected = first; expected.extend_from_slice(&second); assert_eq!(harness.output_bytes(), expected); } #[test] fn stops_before_a_following_non_zstd_format_header() { let input = b"zstd before gzip".repeat(100); let frame = encode_frame(&input); let following_format = [0x1F, 0x8B, 0x08, 0x00, 0xAA]; let mut stream = frame; stream.extend_from_slice(&following_format); let mut harness = FrameHarness::new(&stream, stream.len(), 0); let (status, decoded_size, zstd_error) = harness.decompress_frames(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(decoded_size, input.len() as u64); assert_eq!(zstd_error, 0); assert_eq!(harness.unread_bytes(), following_format); assert_eq!(harness.output_bytes(), input); } #[test] fn stops_before_one_to_three_trailing_bytes() { let input = b"zstd before a short trailing header"; let frame = encode_frame(input); for trailing_len in 1..=3 { let trailing: Vec = (0..trailing_len).map(|byte| 0xC0 + byte).collect(); let mut stream = frame.clone(); stream.extend_from_slice(&trailing); let mut harness = FrameHarness::new(&stream, stream.len(), 0); let (status, decoded_size, zstd_error) = harness.decompress_frames(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(decoded_size, input.len() as u64); assert_eq!(zstd_error, 0); assert_eq!(harness.unread_bytes(), trailing); assert_eq!(harness.output_bytes(), input); } } #[test] fn second_frame_error_preserves_its_input() { let first = b"first frame is valid".repeat(100); let mut invalid_second = MOCK_FRAME_MAGIC.to_vec(); invalid_second.extend_from_slice(&u32::MAX.to_le_bytes()); invalid_second.extend_from_slice(b"invalid second frame"); let mut stream = encode_frame(&first); stream.extend_from_slice(&invalid_second); let mut harness = FrameHarness::new(&stream, stream.len(), 0); let (status, decoded_size, zstd_error) = harness.decompress_frames(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_DECODING_ERROR); assert_eq!(decoded_size, first.len() as u64); assert_ne!(zstd_error, 0); assert_eq!(harness.unread_bytes(), invalid_second); assert_eq!(harness.output_bytes(), first); } #[test] fn consecutive_frame_progress_uses_the_nonzero_base() { let first = b"first progress frame"; let second = b"second progress frame"; let mut stream = encode_frame(first); stream.extend_from_slice(&encode_frame(second)); let mut harness = FrameHarness::new(&stream, stream.len(), 0); let mut progress = ProgressState::default(); let (status, decoded_size, zstd_error) = harness.decompress_frames(41, Some(&mut progress)); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(decoded_size, (first.len() + second.len()) as u64); assert_eq!(zstd_error, 0); assert_eq!(progress.decoded_values, vec![41, 41 + first.len() as u64]); assert_eq!( harness.output_bytes(), [first.as_slice(), second.as_slice()].concat() ); } #[test] fn decompresses_concatenated_frames_without_crossing_boundary() { let first = b"first frame ".repeat(20_000); let second = b"second frame ".repeat(20_000); let first_frame = encode_frame(&first); let second_frame = encode_frame(&second); let mut stream = first_frame.clone(); stream.extend_from_slice(&second_frame); let mut harness = FrameHarness::new(&stream, stream.len(), 1); let mut progress = ProgressState::default(); let (status, frame_size, zstd_error) = harness.decompress(0, Some(&mut progress)); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(frame_size, first.len() as u64); assert_eq!(zstd_error, 0); assert_eq!(harness.unread_bytes(), second_frame); assert_eq!(harness.output_bytes(), first); let (status, frame_size, zstd_error) = harness.decompress(first.len() as u64, Some(&mut progress)); assert_eq!(status, FIO_RUST_ZSTD_FRAME_OK); assert_eq!(frame_size, second.len() as u64); assert_eq!(zstd_error, 0); assert!(harness.unread_bytes().is_empty()); assert!(progress.last_decoded >= first.len() as u64); assert!(progress.last_decoded < (first.len() + second.len()) as u64); let mut expected = first; expected.extend_from_slice(&second); assert_eq!(harness.output_bytes(), expected); } #[test] fn decoder_error_preserves_the_current_input_buffer() { let input = b"decoder error input"; let mut frame = encode_frame(input); frame[0] ^= 1; let mut harness = FrameHarness::new(&frame, frame.len() + 1, 0); let (status, frame_size, zstd_error) = harness.decompress(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_DECODING_ERROR); assert_eq!(frame_size, 0); assert_ne!(zstd_error, 0); assert_eq!(harness.unread_bytes(), frame); } #[test] fn truncated_frame_reports_premature_end() { let frame = encode_frame(&b"truncated frame".repeat(10_000)); let truncated = &frame[..frame.len() - 1]; let mut harness = FrameHarness::new(truncated, truncated.len() + 1, 0); let (status, _frame_size, zstd_error) = harness.decompress(0, None); assert_eq!(status, FIO_RUST_ZSTD_FRAME_PREMATURE_END); assert_eq!(zstd_error, 0); } } }