Files
zstd-rs/rust/src/fileio_asyncio.rs
T
ddidderr 02d3579da3 feat(cli): move adaptive refresh timing to Rust
The adaptive compression loop still delegated its refresh clock gate to a
small C callback even though Rust already owned the iteration state and all
adaptive policy decisions. That left timing policy, the last-refresh scalar,
and one projection callback in the C-side orchestration boundary.

Move the one-sixth-second monotonic refresh gate into ZstdAdaptiveState. The
Rust loop now initializes the first refresh timestamp at frame start and
preserves the C callback's strict-greater-than interval check. C retains only
the private progression, parameter-setting, and diagnostic callbacks needed
by the existing file I/O context.

Shrink both sides of the projection together and keep compile-time offset and
size assertions aligned. The adaptive unit fixture now verifies Rust records
the refresh event while continuing to exercise the existing progression and
policy callbacks.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --tests
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1 -C tests invalidDictionaries
- ulimit -v 41943040; make -j1 -C tests test
- git diff --check
2026-07-21 16:39:33 +02:00

10960 lines
383 KiB
Rust

#![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};
use crate::zstd_compress::{ZSTD_inBuffer, ZSTD_outBuffer};
#[cfg(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;
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<unsafe extern "C" fn(*mut c_void, *const c_char, u64)>;
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);
type FIO_zstd_reset_fn = unsafe extern "C" fn(*mut c_void, c_int) -> usize;
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;
type FIO_zstd_in_size_fn = extern "C" fn() -> usize;
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<FIO_rust_zstd_frame_decoding_error_fn>,
display_premature_end: Option<FIO_rust_zstd_frame_premature_end_fn>,
}
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::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, dctx) == 2 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, read_ctx) == 3 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, write_ctx) == 4 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, src_file_name) == 5 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, already_decoded) == 6 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, progress) == 7 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, display_decoding_error) == 8 * size_of::<usize>());
assert!(offset_of!(FIO_rust_zstd_frame_policy_state, display_premature_end) == 9 * size_of::<usize>());
assert!(size_of::<FIO_rust_zstd_frame_policy_state>() == 10 * size_of::<usize>());
assert!(size_of::<FIO_rust_zstd_frame_decoding_error_fn>() == size_of::<usize>());
assert!(size_of::<FIO_rust_zstd_frame_premature_end_fn>() == size_of::<usize>());
};
/// 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<FIO_rust_decompress_frame_fn>,
pub decode_gzip: Option<FIO_rust_decompress_frame_fn>,
pub decode_lzma: Option<FIO_rust_decompress_frame_fn>,
pub decode_lz4: Option<FIO_rust_decompress_frame_fn>,
pub pass_through: Option<FIO_rust_pass_through_fn>,
pub report_status: Option<FIO_rust_decompress_status_fn>,
pub finish: Option<FIO_rust_decompress_finish_fn>,
}
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<FIO_rust_decompress_read_fill_fn>,
pub read_consume: Option<FIO_rust_decompress_read_consume_fn>,
pub write_acquire: Option<FIO_rust_decompress_write_acquire_fn>,
pub write_enqueue: Option<FIO_rust_decompress_write_enqueue_fn>,
pub write_release: Option<FIO_rust_decompress_write_release_fn>,
pub sparse_write_end: Option<FIO_rust_decompress_sparse_write_end_fn>,
}
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<FIO_rust_gzip_decompress_init_fn>,
pub zlib_inflate: Option<FIO_rust_gzip_decompress_inflate_fn>,
pub zlib_end: Option<FIO_rust_gzip_decompress_end_fn>,
}
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<FIO_rust_lzma_decompress_init_fn>,
pub lzma_code: Option<FIO_rust_lzma_decompress_code_fn>,
pub lzma_end: Option<FIO_rust_lzma_decompress_end_fn>,
}
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<FIO_rust_lz4_decompress_create_fn>,
pub code: Option<FIO_rust_lz4_decompress_code_fn>,
pub free_context: Option<FIO_rust_lz4_decompress_free_fn>,
pub progress: Option<FIO_rust_lz4_decompress_progress_fn>,
}
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<FIO_rust_compress_zstd_fn>,
pub compress_gzip: Option<FIO_rust_compress_gzip_fn>,
pub compress_lzma: Option<FIO_rust_compress_lzma_fn>,
pub compress_lz4: Option<FIO_rust_compress_lz4_fn>,
pub display_input: Option<FIO_rust_compress_input_display_fn>,
pub display_status: Option<FIO_rust_compress_status_display_fn>,
}
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<FIO_rust_compress_src_stat_fn>,
pub source_is_excluded: Option<FIO_rust_compress_src_excluded_fn>,
pub open_source: Option<FIO_rust_compress_src_open_fn>,
pub set_async: Option<FIO_rust_compress_src_async_fn>,
pub attach_source: Option<FIO_rust_compress_src_attach_fn>,
pub compress: Option<FIO_rust_compress_src_compress_fn>,
pub close_source: Option<FIO_rust_compress_src_close_fn>,
pub remove_source: Option<FIO_rust_compress_src_remove_fn>,
}
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, src_file_name)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, compression_level)
== 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, source_is_stdin)
== 3 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, exclude_compressed_files)
== 3 * size_of::<usize>() + 2 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, remove_src_file)
== 3 * size_of::<usize>() + 3 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, stat_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, source_is_excluded)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, open_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, set_async)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, attach_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 4 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, compress)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 5 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, close_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 6 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_src_projection_t, remove_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 7 * size_of::<usize>()
);
assert!(
size_of::<FIO_rust_compress_src_projection_t>()
== 3 * size_of::<usize>() + 4 * size_of::<c_int>() + 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<FIO_rust_compress_dst_open_fn>,
pub attach_destination: Option<FIO_rust_compress_dst_handler_fn>,
pub add_handler: Option<FIO_rust_compress_dst_handler_fn>,
pub compress: Option<FIO_rust_compress_dst_file_fn>,
pub clear_handler: Option<FIO_rust_compress_dst_handler_fn>,
pub set_fd_stat: Option<FIO_rust_compress_dst_stat_fn>,
pub close_destination: Option<FIO_rust_compress_dst_close_fn>,
pub utime_destination: Option<FIO_rust_compress_dst_handler_fn>,
pub remove_destination: Option<FIO_rust_compress_dst_remove_fn>,
}
const _: () = {
let callback_offset = (3 * size_of::<usize>() + 5 * size_of::<c_int>())
.div_ceil(size_of::<usize>())
* size_of::<usize>();
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, src_file_name)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, compression_level)
== 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, destination_already_open)
== 3 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, source_is_stdin)
== 3 * size_of::<usize>() + 2 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, destination_is_stdout)
== 3 * size_of::<usize>() + 3 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, source_is_regular)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>()
);
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, add_handler)
== callback_offset + 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, compress)
== callback_offset + 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, clear_handler)
== callback_offset + 4 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, set_fd_stat)
== callback_offset + 5 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, close_destination)
== callback_offset + 6 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, utime_destination)
== callback_offset + 7 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_dst_projection_t, remove_destination)
== callback_offset + usize::BITS as usize
);
assert!(
size_of::<FIO_rust_compress_dst_projection_t>() == callback_offset + 9 * size_of::<usize>()
);
};
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;
/// 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<FIO_rust_compress_multiple_file_fn>,
}
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, output_file_name)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, opaque)
== 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_multiple_projection_t, compress_file)
== 4 * size_of::<usize>()
);
assert!(size_of::<FIO_rust_compress_multiple_file_fn>() == size_of::<usize>());
assert!(size_of::<FIO_rust_compress_multiple_projection_t>() == 5 * size_of::<usize>());
};
/// 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<FIO_rust_compress_multiple_separate_file_fn>,
pub compress_flat_file: Option<FIO_rust_compress_multiple_separate_file_fn>,
}
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_compress_multiple_separate_projection_t, opaque)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(
FIO_rust_compress_multiple_separate_projection_t,
mirror_output
) == 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(
FIO_rust_compress_multiple_separate_projection_t,
compress_mirrored_file
) == 4 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(
FIO_rust_compress_multiple_separate_projection_t,
compress_flat_file
) == 5 * size_of::<usize>()
);
assert!(size_of::<c_int>() <= size_of::<usize>());
assert!(size_of::<FIO_rust_compress_multiple_separate_file_fn>() == size_of::<usize>());
assert!(
size_of::<FIO_rust_compress_multiple_separate_projection_t>() == 6 * size_of::<usize>()
);
};
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<FIO_rust_decompress_multiple_file_fn>,
}
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, out_file_name)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, opaque)
== 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_multiple_projection_t, decompress_file)
== 4 * size_of::<usize>()
);
assert!(size_of::<FIO_rust_decompress_multiple_file_fn>() == size_of::<usize>());
assert!(size_of::<FIO_rust_decompress_multiple_projection_t>() == 5 * size_of::<usize>());
};
/// 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<FIO_rust_decompress_multiple_separate_file_fn>,
}
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_multiple_separate_projection_t, opaque)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(
FIO_rust_decompress_multiple_separate_projection_t,
decompress_file
) == 3 * size_of::<usize>()
);
assert!(size_of::<FIO_rust_decompress_multiple_separate_file_fn>() == size_of::<usize>());
assert!(
size_of::<FIO_rust_decompress_multiple_separate_projection_t>() == 4 * size_of::<usize>()
);
};
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<FIO_rust_decompress_src_open_fn>,
pub set_async: Option<FIO_rust_decompress_src_async_fn>,
pub attach_source: Option<FIO_rust_decompress_src_attach_fn>,
pub detach_source: Option<FIO_rust_decompress_src_attach_fn>,
pub close_source: Option<FIO_rust_decompress_src_close_fn>,
pub open_destination: Option<FIO_rust_decompress_dst_open_fn>,
pub attach_destination: Option<FIO_rust_decompress_dst_attach_fn>,
pub add_handler: Option<FIO_rust_decompress_handler_fn>,
pub decompress: Option<FIO_rust_decompress_file_fn>,
pub clear_handler: Option<FIO_rust_decompress_handler_fn>,
pub set_fd_stat: Option<FIO_rust_decompress_dst_stat_fn>,
pub close_destination: Option<FIO_rust_decompress_dst_close_fn>,
pub utime_destination: Option<FIO_rust_decompress_handler_fn>,
pub remove_destination: Option<FIO_rust_decompress_dst_remove_fn>,
pub remove_source_file: Option<FIO_rust_decompress_src_remove_fn>,
pub source_is_directory: Option<FIO_rust_decompress_src_directory_fn>,
}
const FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET: usize =
(3 * size_of::<usize>() + 5 * size_of::<c_int>() + size_of::<usize>() - 1)
& !(size_of::<usize>() - 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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, src_file_name)
== 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(
FIO_rust_decompress_file_projection_t,
destination_already_open
) == 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, test_mode)
== 3 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, source_is_stdin)
== 3 * size_of::<usize>() + 2 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, destination_is_stdout)
== 3 * size_of::<usize>() + 3 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_source)
== 3 * size_of::<usize>() + 4 * size_of::<c_int>()
);
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, attach_source)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 2 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, detach_source)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 3 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, close_source)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 4 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, open_destination)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 5 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, attach_destination)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 6 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, add_handler)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 7 * size_of::<usize>()
);
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, set_fd_stat)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 10 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, close_destination)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 11 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, utime_destination)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 12 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_destination)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 13 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, remove_source_file)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 14 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_decompress_file_projection_t, source_is_directory)
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 15 * size_of::<usize>()
);
assert!(
size_of::<FIO_rust_decompress_file_projection_t>()
== FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 16 * size_of::<usize>()
);
};
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::<u64>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, nb_active_workers)
== 2 * size_of::<u64>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_produced)
== if size_of::<usize>() == 8 { 24 } else { 20 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_flushed)
== if size_of::<usize>() == 8 { 32 } else { 28 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, flush_waiting)
== if size_of::<usize>() == 8 { 40 } else { 36 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, input_blocked)
== if size_of::<usize>() == 8 { 44 } else { 40 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, input_presented)
== if size_of::<usize>() == 8 { 48 } else { 44 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_ingested)
== if size_of::<usize>() == 8 { 56 } else { 48 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, newly_consumed)
== if size_of::<usize>() == 8 { 64 } else { 56 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, compression_level)
== if size_of::<usize>() == 8 { 72 } else { 64 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, min_adapt_level)
== if size_of::<usize>() == 8 { 76 } else { 68 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, max_adapt_level)
== if size_of::<usize>() == 8 { 80 } else { 72 }
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_adapt_projection_t, max_c_level)
== if size_of::<usize>() == 8 { 84 } else { 76 }
);
assert!(
size_of::<FIO_rust_zstd_adapt_projection_t>()
== if size_of::<usize>() == 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::<u64>());
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, produced) == 2 * size_of::<u64>());
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, flushed) == 3 * size_of::<u64>());
assert!(
std::mem::offset_of!(FIO_rust_zstd_progression_t, current_job_id) == 4 * size_of::<u64>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_progression_t, nb_active_workers)
== 4 * size_of::<u64>() + size_of::<c_uint>()
);
assert!(
size_of::<FIO_rust_zstd_progression_t>() == 4 * size_of::<u64>() + 2 * size_of::<c_uint>()
);
};
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<FIO_rust_zstd_read_fill_fn>,
pub read_consume: Option<FIO_rust_zstd_read_consume_fn>,
pub write_acquire: Option<FIO_rust_zstd_write_acquire_fn>,
pub write_enqueue: Option<FIO_rust_zstd_write_enqueue_fn>,
pub write_release: Option<FIO_rust_zstd_write_release_fn>,
pub sparse_write_end: Option<FIO_rust_zstd_sparse_write_end_fn>,
pub compress_stream: Option<FIO_rust_zstd_compress_stream_fn>,
pub iteration: Option<FIO_rust_zstd_iteration_fn>,
pub compress_display: Option<FIO_rust_zstd_compress_display_fn>,
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<FIO_rust_zstd_adaptive_progression_fn>,
pub adaptive_set_parameter: Option<FIO_rust_zstd_adaptive_set_parameter_fn>,
pub adaptive_diagnostic: Option<FIO_rust_zstd_adaptive_diagnostic_fn>,
}
const _: () = {
let callback_offset = (14 * size_of::<usize>() + 5 * size_of::<c_int>())
.div_ceil(size_of::<usize>())
* size_of::<usize>();
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_mode)
== 14 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, nb_workers)
== 14 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, max_c_level)
== 14 * size_of::<usize>() + 4 * size_of::<c_int>()
);
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::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_diagnostic)
== callback_offset + 2 * size_of::<usize>()
);
assert!(
size_of::<FIO_rust_zstd_compress_projection_t>()
== callback_offset + 3 * size_of::<usize>()
);
};
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<FIO_rust_gzip_read_fill_fn>,
pub read_consume: Option<FIO_rust_gzip_read_consume_fn>,
pub write_acquire: Option<FIO_rust_gzip_write_acquire_fn>,
pub write_enqueue: Option<FIO_rust_gzip_write_enqueue_fn>,
pub write_release: Option<FIO_rust_gzip_write_release_fn>,
pub sparse_write_end: Option<FIO_rust_gzip_sparse_write_end_fn>,
pub zlib_init: Option<FIO_rust_gzip_zlib_init_fn>,
pub zlib_deflate: Option<FIO_rust_gzip_zlib_deflate_fn>,
pub zlib_end: Option<FIO_rust_gzip_zlib_end_fn>,
pub progress: Option<FIO_rust_gzip_progress_fn>,
}
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<FIO_rust_lzma_read_fill_fn>,
pub read_consume: Option<FIO_rust_lzma_read_consume_fn>,
pub write_acquire: Option<FIO_rust_lzma_write_acquire_fn>,
pub write_enqueue: Option<FIO_rust_lzma_write_enqueue_fn>,
pub write_release: Option<FIO_rust_lzma_write_release_fn>,
pub sparse_write_end: Option<FIO_rust_lzma_sparse_write_end_fn>,
pub lzma_init: Option<FIO_rust_lzma_init_fn>,
pub lzma_code: Option<FIO_rust_lzma_code_fn>,
pub lzma_end: Option<FIO_rust_lzma_end_fn>,
pub progress: Option<FIO_rust_lzma_progress_fn>,
}
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<FIO_rust_lz4_create_fn>,
pub prepare: Option<FIO_rust_lz4_prepare_fn>,
pub begin: Option<FIO_rust_lz4_begin_fn>,
pub update: Option<FIO_rust_lz4_update_fn>,
pub end: Option<FIO_rust_lz4_end_fn>,
pub free_context: Option<FIO_rust_lz4_free_fn>,
pub read_fill: Option<FIO_rust_lz4_read_fill_fn>,
pub read_consume: Option<FIO_rust_lz4_read_consume_fn>,
pub write_acquire: Option<FIO_rust_lz4_write_acquire_fn>,
pub write_enqueue: Option<FIO_rust_lz4_write_enqueue_fn>,
pub write_release: Option<FIO_rust_lz4_write_release_fn>,
pub sparse_write_end: Option<FIO_rust_lz4_sparse_write_end_fn>,
pub progress: Option<FIO_rust_lz4_progress_fn>,
}
/// 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,
}
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::<c_int>();
let int_align = align_of::<c_int>();
let word_size = size_of::<usize>();
let word_align = align_of::<usize>();
let function_size = size_of::<PoolFunction>();
let function_align = align_of::<PoolFunction>();
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::<libc::pthread_mutex_t>(),
align_of::<libc::pthread_mutex_t>(),
size_of::<libc::pthread_cond_t>(),
align_of::<libc::pthread_cond_t>(),
)
}
#[cfg(windows)]
{
(
size_of::<WindowsCriticalSection>(),
align_of::<WindowsCriticalSection>(),
size_of::<WindowsConditionVariable>(),
align_of::<WindowsConditionVariable>(),
)
}
#[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::<c_uint>(), write_align);
let read_reached_eof = base_size;
offset = read_reached_eof + int_size;
offset = align_up(offset, align_of::<u64>());
let read_next_offset = offset;
offset += size_of::<u64>();
let read_waiting_offset = offset;
offset += size_of::<u64>();
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::<u64>());
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<T: Copy>(base: *const u8, offset: usize) -> T {
unsafe { base.add(offset).cast::<T>().read() }
}
#[inline]
unsafe fn write_at<T: Copy>(base: *mut u8, offset: usize, value: T) {
unsafe { base.add(offset).cast::<T>().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<QueueState>,
work_available: Condvar,
queue_space: Condvar,
idle: Condvar,
worker: Mutex<Option<JoinHandle<()>>>,
threaded: bool,
kind: PoolKind,
}
unsafe impl Send for AsyncQueue {}
unsafe impl Sync for AsyncQueue {}
impl AsyncQueue {
fn new(threaded: bool, kind: PoolKind) -> Arc<Self> {
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<JobState>,
jobs_changed: Condvar,
queue: Option<Arc<AsyncQueue>>,
}
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::<IOJob_t>()).cast::<IOJob_t>();
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::<c_void>(),
);
}
}
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::<c_void>(),
);
}
}
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::<u64>(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::<c_int>(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::<c_int>(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::<u8>();
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::<libc::FILE>());
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::<u8>();
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::<c_void>());
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::<usize>();
let segment_words = SPARSE_SEGMENT_SIZE / word_size;
let word_count = buffer_size / word_size;
let words = buffer.cast::<usize>();
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::<c_void>() };
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::<u8>().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::<c_uint>(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::<c_int>(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::<u64>(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::<c_int>(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::<c_void>(),
);
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::<c_uint>(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::<c_uint>(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::<libc::FILE>(),
)
};
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::<u8>();
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::<u8>();
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::<c_void>(),
);
}
}
}
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::<u8>();
let inner = unsafe { base_inner(context) };
let loaded = unsafe { read_at::<usize>(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::<u8>();
let inner = unsafe { base_inner(context) };
let buffer_size = unsafe { read_at::<usize>(context, inner.layout.job_buffer_size) };
n = n.min(buffer_size);
let loaded = unsafe { read_at::<usize>(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::<u8>(), 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::<u8>(),
);
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::<u8>();
let inner = unsafe { base_inner(context) };
let loaded = unsafe { read_at::<usize>(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::<u8>();
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::<c_void>(),
);
}
}
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::<u8>();
let write_context = write_ctx.cast::<u8>();
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::<usize>(read_context, read_inner.layout.job_buffer_size) })
.min(unsafe { read_at::<usize>(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::<usize>(read_context, read_inner.layout.read_src_buffer_loaded) } != 0 {
let loaded =
unsafe { read_at::<usize>(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::<u8>(), 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::<c_int>(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::<FIO_rust_compression_context_t>() };
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::<FIO_rust_compression_context_t>();
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
}
/// 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::<FIO_rust_compression_context_t>();
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::<FIO_rust_compression_context_t>();
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
}
/// 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::<FIO_rust_compression_context_t>();
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<Instant>,
}
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;
}
}
type FIO_rust_zstd_to_flush_now_fn = unsafe extern "C" fn(*mut c_void) -> usize;
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.
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::<c_void>(),
size: input_size,
pos: input_pos,
};
let mut output_view = ZSTD_outBuffer {
dst: output.cast::<c_void>(),
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(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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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::<u8>();
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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::<u8>();
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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()`.
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::<c_void>(),
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.
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::<c_void>(), 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);
}
}
}
unsafe extern "C" fn fio_zstd_reset(dctx: *mut c_void, reset: c_int) -> usize {
unsafe {
crate::zstd_decompress::ZSTD_DCtx_reset(
dctx.cast::<crate::zstd_decompress::ZSTD_DCtx>(),
reset,
)
}
}
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::<crate::zstd_decompress::ZSTD_DStream>(),
output,
input,
)
}
}
#[no_mangle]
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]
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,
)
}
}
const FIO_ERROR_FRAME_DECODING: u64 = u64::MAX - 1;
#[inline]
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]
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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::<c_void>();
let mut output = ptr::null_mut::<u8>();
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<F>(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(test)]
{
/* 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(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::<u8>();
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::<u8>();
let inner = unsafe { base_inner(context) };
unsafe { read_at::<usize>(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<u8>,
async_modes: Vec<c_int>,
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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::<SourcePolicyState>() };
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<u8>,
transfer_stats: Vec<c_int>,
destination_fds: Vec<c_int>,
compression_levels: Vec<c_int>,
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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::<CompressDestinationPolicyState>() };
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<u8>,
async_modes: Vec<c_int>,
transfers: Vec<c_int>,
destination_fds: Vec<c_int>,
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<DecompressPolicyState>() };
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::<CompressionCallbackState>() };
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::<CompressionCallbackState>() };
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::<CompressionCallbackState>() };
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::<CompressionCallbackState>() };
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<u64>,
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::<CompressionDisplayState>() };
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::<CompressionDisplayState>() };
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::<c_void>(),
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<c_int>,
}
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::<MultipleCompressionState>() };
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::<MultipleCompressionState>() };
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<c_int>,
}
fn record_separate_compression_route(
opaque: *mut c_void,
source: *const c_char,
route: &'static str,
) -> c_int {
let state = unsafe { &mut *opaque.cast::<SeparateCompressionRoutingState>() };
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<c_int>,
}
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::<MultipleDecompressionState>() };
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::<MultipleDecompressionState>() };
let status = state
.statuses
.get(state.sources.len())
.copied()
.unwrap_or(0);
state.sources.push(source);
status
}
#[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::<ZstdCodecCallbackTestState>() };
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::<ZstdCodecCallbackTestState>() };
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::<c_void>();
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::<c_void>();
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<usize>,
enqueue_sizes: Vec<usize>,
output_chunks: Vec<Vec<u8>>,
directives: Vec<c_int>,
iterations: Vec<(usize, usize, usize, c_int)>,
acquire_calls: usize,
release_calls: usize,
sparse_end_calls: usize,
compress_calls: usize,
codec_error: Option<usize>,
}
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<c_int>,
}
unsafe extern "C" fn adaptive_test_progression(
opaque: *mut c_void,
progression: *mut FIO_rust_zstd_progression_t,
) {
let state = unsafe { &*opaque.cast::<AdaptiveCallbackState>() };
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::<AdaptiveCallbackState>() };
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::<AdaptiveCallbackState>() };
state.events.push(diagnostic);
}
fn adaptive_test_projection(
state: &mut AdaptiveCallbackState,
) -> FIO_rust_zstd_compress_projection_t {
let opaque = (state as *mut AdaptiveCallbackState).cast::<c_void>();
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<ZstdProjectionState>() };
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::<c_void>();
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<usize>,
enqueue_sizes: Vec<usize>,
output_chunks: Vec<Vec<u8>>,
acquire_calls: usize,
release_calls: usize,
sparse_end_calls: usize,
init_levels: Vec<c_int>,
deflate_flushes: Vec<c_int>,
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<GzipProjectionState>() };
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::<c_void>();
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<usize>,
enqueue_sizes: Vec<usize>,
output_chunks: Vec<Vec<u8>>,
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<c_int>,
finish_calls: usize,
code_result: Option<c_int>,
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<LzmaProjectionState>() };
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::<c_void>();
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::<usize>::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<usize>,
enqueue_sizes: Vec<usize>,
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<Lz4ProjectionState>() };
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::<c_void>();
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<u8>,
input_pos: usize,
output: [u8; 4],
fill_requests: Vec<usize>,
consumed: Vec<usize>,
enqueue_sizes: Vec<usize>,
output_chunks: Vec<Vec<u8>>,
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::<OptionalDecompressIoState>() };
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::<OptionalDecompressIoState>() };
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::<OptionalDecompressIoState>() };
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::<OptionalDecompressIoState>() };
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::<OptionalDecompressIoState>() };
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::<OptionalDecompressIoState>() };
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::<c_void>();
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<c_int>,
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::<GzipDecompressState>()).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::<GzipDecompressState>() };
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::<GzipDecompressState>() };
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::<c_void>();
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<c_int>,
actions: Vec<c_int>,
finish_result: c_int,
code_error: Option<c_int>,
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::<LzmaDecompressState>() };
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::<LzmaDecompressState>() };
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::<LzmaDecompressState>() };
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::<c_void>();
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<usize>,
next_after_code: usize,
unfinished: bool,
free_calls: usize,
progress: Vec<u64>,
}
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::<Lz4DecompressState>() };
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::<Lz4DecompressState>() };
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::<Lz4DecompressState>() };
state.free_calls += 1;
}
unsafe extern "C" fn lz4_decompress_test_progress(opaque: *mut c_void, decoded: u64) {
let state = unsafe { &mut *opaque.cast::<Lz4DecompressState>() };
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::<c_void>();
let codec_opaque = (state as *mut Lz4DecompressState).cast::<c_void>();
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::<DispatchTestState>() };
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<c_int>,
finished_sizes: Vec<u64>,
}
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::<DecompressResultState>() };
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::<DecompressResultState>() };
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<M> {
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<M, C> {
base: CBase<M>,
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<M> {
base: CBase<M>,
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<M>(layout: AbiLayout) {
assert_eq!(
std::mem::offset_of!(CBase<M>, thread_pool),
layout.thread_pool
);
assert_eq!(
std::mem::offset_of!(CBase<M>, thread_pool_active),
layout.thread_pool_active
);
assert_eq!(
std::mem::offset_of!(CBase<M>, total_io_jobs),
layout.total_io_jobs
);
assert_eq!(std::mem::offset_of!(CBase<M>, prefs), layout.prefs);
assert_eq!(
std::mem::offset_of!(CBase<M>, pool_function),
layout.pool_function
);
assert_eq!(std::mem::offset_of!(CBase<M>, file), layout.file);
assert_eq!(
std::mem::offset_of!(CBase<M>, io_jobs_mutex),
layout.io_jobs_mutex
);
assert_eq!(
std::mem::offset_of!(CBase<M>, available_jobs),
layout.available_jobs
);
assert_eq!(
std::mem::offset_of!(CBase<M>, available_jobs_count),
layout.available_jobs_count
);
assert_eq!(
std::mem::offset_of!(CBase<M>, job_buffer_size),
layout.job_buffer_size
);
}
fn assert_read_offsets<M, C>(layout: AbiLayout) {
assert_eq!(
std::mem::offset_of!(CRead<M, C>, reached_eof),
layout.read_reached_eof
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, next_read_offset),
layout.read_next_offset
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, waiting_on_offset),
layout.read_waiting_offset
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, current_job_held),
layout.read_current_job
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, coalesce_buffer),
layout.read_coalesce_buffer
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, src_buffer),
layout.read_src_buffer
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, src_buffer_loaded),
layout.read_src_buffer_loaded
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, completed_jobs),
layout.read_completed_jobs
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, completed_jobs_count),
layout.read_completed_jobs_count
);
assert_eq!(
std::mem::offset_of!(CRead<M, C>, 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::<c_int>(non_threaded);
assert_read_offsets::<c_int, c_int>(non_threaded);
assert_eq!(non_threaded.read_reached_eof, size_of::<CBase<c_int>>());
assert_eq!(non_threaded.read_size, size_of::<CRead<c_int, c_int>>());
assert_eq!(non_threaded.write_size, size_of::<CWrite<c_int>>());
#[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::<CThreadedBase>());
assert_eq!(threaded.read_size, size_of::<CThreadedRead>());
assert_eq!(threaded.write_size, size_of::<CThreadedWrite>());
}
#[cfg(not(feature = "debug-pthread"))]
{
type CThreadedBase = CBase<libc::pthread_mutex_t>;
type CThreadedRead = CRead<libc::pthread_mutex_t, libc::pthread_cond_t>;
type CThreadedWrite = CWrite<libc::pthread_mutex_t>;
assert_base_offsets::<libc::pthread_mutex_t>(threaded);
assert_read_offsets::<libc::pthread_mutex_t, libc::pthread_cond_t>(threaded);
assert_eq!(threaded.read_reached_eof, size_of::<CThreadedBase>());
assert_eq!(threaded.read_size, size_of::<CThreadedRead>());
assert_eq!(threaded.write_size, size_of::<CThreadedWrite>());
}
}
assert_eq!(size_of::<IOJob_t>(), size_of::<CJob>());
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::<u8>();
let inner = unsafe { base_inner(base) };
assert!(inner.queue.is_none());
assert_eq!(
unsafe { read_at::<c_int>(base, inner.layout.total_io_jobs) },
2
);
assert_eq!(
unsafe { read_at::<c_int>(base, inner.layout.available_jobs_count) },
2
);
let job = unsafe { AIO_WritePool_acquireJob(ctx) };
assert_eq!(
unsafe { read_at::<c_int>(base, inner.layout.available_jobs_count) },
1
);
unsafe { AIO_WritePool_releaseIoJob(job) };
assert_eq!(
unsafe { read_at::<c_int>(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::<u8>();
let inner = unsafe { base_inner(base) };
assert!(inner.queue.is_some());
assert_eq!(
unsafe { read_at::<c_int>(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::<c_int>(base, inner.layout.thread_pool_active) },
0
);
unsafe { AIO_WritePool_setAsync(ctx, 1) };
assert_eq!(
unsafe { read_at::<c_int>(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::<u8>();
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::<u8>();
let inner = unsafe { base_inner(context) };
let loaded =
unsafe { read_at::<usize>(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::<u8>();
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::<usize>(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::<usize>(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::<u8>();
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::<c_int>(context, inner.layout.read_reached_eof) },
0
);
assert_eq!(
unsafe { read_at::<u64>(context, inner.layout.read_next_offset) },
0
);
assert_eq!(
unsafe { read_at::<u64>(context, inner.layout.read_waiting_offset) },
0
);
assert_eq!(
unsafe { read_at::<usize>(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<u8> {
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<usize>,
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::<PolicyCallbackState>()).decoding_errors.push(error) };
}
unsafe extern "C" fn record_policy_premature_end(
opaque: *mut c_void,
_source_name: *const c_char,
) {
unsafe { (*opaque.cast::<PolicyCallbackState>()).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<u64>,
}
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::<ProgressState>() };
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<u8> {
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::<u8>(), 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::<MockDecoder>()).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::<MockDecoder>() };
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::<u8>().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::<u8>().add(input.pos),
output.dst.cast::<u8>().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<FIO_prefs_t>,
_decoder: Box<MockDecoder>,
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::<c_void>();
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<u8> {
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<u8> {
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<u8> = (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<u8> = (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);
}
}
}