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
10960 lines
383 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|