`make all` exercises substantially more than the default zstd binary. It also builds compression-only and decompression-only archives, older contrib tools that compile program C shims directly, and the single-file amalgamations. The Rust migration had changed symbol ownership without updating every one of those feature and link boundaries. The first failure came from `fileio_asyncio`: it is always compiled, but its codec bindings unconditionally referenced both `zstd_compress` and `zstd_decompress`. A compression-only archive therefore required disabled decoder modules, and the decompression-only configuration had the symmetric problem. Gate the concrete codec imports, callback types, helpers, and exports with their Cargo features. Keep the format probe compilable without the legacy decoder predicate when decompression is disabled. Once that boundary compiled, the remaining `make all` paths exposed related integration gaps. Move the C decompression projection declarations out of the compression preprocessor block, while leaving destination callback types shared. Link the Rust CLI helpers archive into zlibWrapper, pzstd, and largeNbDicts, whose util/time/data-generator C files are now declaration shims rather than implementations. The generated single-file C sources also call Rust-owned symbols now. Build and link an appropriately featured Rust archive in their native smoke tests, and include the pool configuration shim in the decoder case. The full amalgamation combines `zstd_lazy.c` and `zstd_opt.c` into one translation unit, so guard their otherwise translation-unit-local dictionary mode enum against duplicate definition. A Rust-backed amalgamation is no longer a standalone Emscripten input. Remove the obsolete emcc/Docker path and report that limitation explicitly; restoring the WebAssembly smoke test requires a Rust WebAssembly archive and a defined cross-language amalgamation contract. Test Plan: - `cargo check --manifest-path rust/Cargo.toml --no-default-features --features compression` -- passed - `cargo check --manifest-path rust/Cargo.toml --no-default-features --features decompression` -- passed - `sh -n build/single_file_libs/build_decoder_test.sh build/single_file_libs/build_library_test.sh` -- passed - `make all` -- passed, including native single-file and seekable-format tests - `git diff --cached --check` -- passed
11535 lines
404 KiB
Rust
11535 lines
404 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};
|
|
|
|
#[cfg(feature = "compression")]
|
|
use crate::zstd_compress::{ZSTD_inBuffer, ZSTD_outBuffer};
|
|
|
|
#[cfg(all(feature = "compression", not(test)))]
|
|
unsafe extern "C" {
|
|
fn ZSTD_toFlushNow(cctx: *mut c_void) -> usize;
|
|
}
|
|
|
|
const MAX_IO_JOBS: usize = 10;
|
|
const IO_QUEUE_SIZE: usize = MAX_IO_JOBS - 2;
|
|
const SPARSE_SEGMENT_SIZE: usize = 32 * 1024;
|
|
const SPARSE_SKIP_CHUNK: u64 = 1 << 30;
|
|
const PASS_THROUGH_MAX_BLOCK_SIZE: usize = 64 * 1024;
|
|
#[cfg(feature = "decompression")]
|
|
const ZSTD_FRAMEHEADERSIZE_MAX: usize = 18;
|
|
|
|
pub const FIO_RUST_ZSTD_FRAME_OK: c_int = 0;
|
|
pub const FIO_RUST_ZSTD_FRAME_DECODING_ERROR: c_int = 1;
|
|
pub const FIO_RUST_ZSTD_FRAME_PREMATURE_END: c_int = 2;
|
|
|
|
pub const FIO_RUST_DECOMPRESS_OK: c_int = 0;
|
|
pub const FIO_RUST_DECOMPRESS_PASS_THROUGH: c_int = 1;
|
|
pub const FIO_RUST_DECOMPRESS_EMPTY_INPUT: c_int = 2;
|
|
pub const FIO_RUST_DECOMPRESS_SHORT_INPUT: c_int = 3;
|
|
pub const FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED: c_int = 4;
|
|
pub const FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED: c_int = 5;
|
|
pub const FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED: c_int = 6;
|
|
pub const FIO_RUST_DECOMPRESS_FRAME_ERROR: c_int = 7;
|
|
pub const FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT: c_int = 8;
|
|
pub const FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR: c_int = 9;
|
|
pub const FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED: c_int = 10;
|
|
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_NOOP: c_int = 0;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_EMPTY_INPUT: c_int = 1;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_SHORT_INPUT: c_int = 2;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_GZIP_UNSUPPORTED: c_int = 3;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_LZMA_UNSUPPORTED: c_int = 4;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_LZ4_UNSUPPORTED: c_int = 5;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_UNSUPPORTED_FORMAT: c_int = 6;
|
|
pub const FIO_RUST_DECOMPRESS_ACTION_INVALID: c_int = 7;
|
|
|
|
/// Resolves the legacy automatic pass-through mode without touching the
|
|
/// destination name or any C-owned file state. Explicit values are returned
|
|
/// unchanged so the C assertion keeps its original validation behavior.
|
|
#[inline]
|
|
fn decompress_pass_through_policy(
|
|
pass_through: c_int,
|
|
overwrite: c_int,
|
|
destination_is_stdout: c_int,
|
|
) -> c_int {
|
|
if pass_through == -1 {
|
|
(overwrite != 0 && destination_is_stdout != 0) as c_int
|
|
} else {
|
|
pass_through
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_rust_decompressPassThroughPolicy(
|
|
pass_through: c_int,
|
|
overwrite: c_int,
|
|
destination_is_stdout: c_int,
|
|
) -> c_int {
|
|
decompress_pass_through_policy(pass_through, overwrite, destination_is_stdout)
|
|
}
|
|
|
|
/// Maps decompression results to the scalar actions consumed by the C CLI.
|
|
/// Rust owns this status policy; C retains the exact diagnostics, source-name
|
|
/// handling, and display operations for the returned action.
|
|
#[inline]
|
|
fn decompress_status_action(status: c_int) -> c_int {
|
|
match status {
|
|
FIO_RUST_DECOMPRESS_EMPTY_INPUT => FIO_RUST_DECOMPRESS_ACTION_EMPTY_INPUT,
|
|
FIO_RUST_DECOMPRESS_SHORT_INPUT => FIO_RUST_DECOMPRESS_ACTION_SHORT_INPUT,
|
|
FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_GZIP_UNSUPPORTED,
|
|
FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_LZMA_UNSUPPORTED,
|
|
FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_LZ4_UNSUPPORTED,
|
|
FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT => FIO_RUST_DECOMPRESS_ACTION_UNSUPPORTED_FORMAT,
|
|
FIO_RUST_DECOMPRESS_OK
|
|
| FIO_RUST_DECOMPRESS_PASS_THROUGH
|
|
| FIO_RUST_DECOMPRESS_FRAME_ERROR
|
|
| FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR
|
|
| FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED => FIO_RUST_DECOMPRESS_ACTION_NOOP,
|
|
_ => FIO_RUST_DECOMPRESS_ACTION_INVALID,
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_rust_decompressStatusAction(status: c_int) -> c_int {
|
|
decompress_status_action(status)
|
|
}
|
|
|
|
type FIO_rust_frame_progress_fn = Option<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);
|
|
#[cfg(feature = "decompression")]
|
|
type FIO_zstd_reset_fn = unsafe extern "C" fn(*mut c_void, c_int) -> usize;
|
|
#[cfg(feature = "decompression")]
|
|
type FIO_zstd_decompress_fn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut crate::zstd_decompress::ZSTD_outBuffer,
|
|
*mut crate::zstd_decompress::ZSTD_inBuffer,
|
|
) -> usize;
|
|
#[cfg(feature = "decompression")]
|
|
type FIO_zstd_in_size_fn = extern "C" fn() -> usize;
|
|
#[cfg(feature = "decompression")]
|
|
type FIO_zstd_is_frame_fn = unsafe extern "C" fn(*const c_void, usize) -> c_uint;
|
|
|
|
/// Rust owns the default zstd-frame result policy. C supplies only the exact
|
|
/// display callbacks and the private asynchronous-pool pointers needed by the
|
|
/// frame loop.
|
|
#[repr(C)]
|
|
pub struct FIO_rust_zstd_frame_policy_state {
|
|
callback_context: *mut c_void,
|
|
f_ctx: *mut c_void,
|
|
dctx: *mut c_void,
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
write_ctx: *mut WritePoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
already_decoded: u64,
|
|
progress: FIO_rust_frame_progress_fn,
|
|
display_decoding_error: Option<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;
|
|
pub type FIO_rust_multiple_destination_warning_fn =
|
|
unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
pub type FIO_rust_multiple_destination_open_fn =
|
|
unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
pub type FIO_rust_multiple_destination_attach_fn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void);
|
|
pub type FIO_rust_multiple_destination_close_fn =
|
|
unsafe extern "C" fn(*mut c_void) -> c_int;
|
|
|
|
/// Rust owns only the shared-destination file iteration and aggregate error
|
|
/// handling. C retains the private preferences/resources and supplies one
|
|
/// callback for each source file.
|
|
#[repr(C)]
|
|
pub struct FIO_rust_compress_multiple_projection_t {
|
|
pub f_ctx: *mut c_void,
|
|
pub input_file_names: *const *const c_char,
|
|
pub output_file_name: *const c_char,
|
|
pub opaque: *mut c_void,
|
|
pub compress_file: Option<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 the shared-destination lifecycle around the file iterator. C
|
|
/// retains warning, filesystem, and write-pool operations behind callbacks.
|
|
#[repr(C)]
|
|
pub struct FIO_rust_compress_multiple_destination_projection_t {
|
|
pub files: *const FIO_rust_compress_multiple_projection_t,
|
|
pub warning: Option<FIO_rust_multiple_destination_warning_fn>,
|
|
pub open_destination: Option<FIO_rust_multiple_destination_open_fn>,
|
|
pub attach_destination: Option<FIO_rust_multiple_destination_attach_fn>,
|
|
pub close_destination: Option<FIO_rust_multiple_destination_close_fn>,
|
|
}
|
|
const _: () = {
|
|
assert!(
|
|
std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, files) == 0
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, warning)
|
|
== size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_compress_multiple_destination_projection_t,
|
|
open_destination
|
|
) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_compress_multiple_destination_projection_t,
|
|
attach_destination
|
|
) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_compress_multiple_destination_projection_t,
|
|
close_destination
|
|
) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<FIO_rust_multiple_destination_warning_fn>() == size_of::<usize>());
|
|
assert!(size_of::<FIO_rust_multiple_destination_open_fn>() == size_of::<usize>());
|
|
assert!(size_of::<FIO_rust_multiple_destination_attach_fn>() == size_of::<usize>());
|
|
assert!(size_of::<FIO_rust_multiple_destination_close_fn>() == size_of::<usize>());
|
|
assert!(
|
|
size_of::<FIO_rust_compress_multiple_destination_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 the shared-destination lifecycle around the decompression file
|
|
/// iterator. Test mode skips the destination callbacks just as the original
|
|
/// C path did.
|
|
#[repr(C)]
|
|
pub struct FIO_rust_decompress_multiple_destination_projection_t {
|
|
pub files: *const FIO_rust_decompress_multiple_projection_t,
|
|
pub destination_enabled: c_int,
|
|
pub warning: Option<FIO_rust_multiple_destination_warning_fn>,
|
|
pub open_destination: Option<FIO_rust_multiple_destination_open_fn>,
|
|
pub attach_destination: Option<FIO_rust_multiple_destination_attach_fn>,
|
|
pub close_destination: Option<FIO_rust_multiple_destination_close_fn>,
|
|
}
|
|
const _: () = {
|
|
assert!(
|
|
std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, files) == 0
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_decompress_multiple_destination_projection_t,
|
|
destination_enabled
|
|
) == size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, warning)
|
|
== 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_decompress_multiple_destination_projection_t,
|
|
open_destination
|
|
) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_decompress_multiple_destination_projection_t,
|
|
attach_destination
|
|
) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
std::mem::offset_of!(
|
|
FIO_rust_decompress_multiple_destination_projection_t,
|
|
close_destination
|
|
) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<c_int>() <= size_of::<usize>());
|
|
assert!(size_of::<FIO_rust_decompress_multiple_destination_projection_t>() == 6 * 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,
|
|
}
|
|
|
|
pub const FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION: c_int = 0;
|
|
pub const FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION: c_int = 1;
|
|
|
|
type FIO_rust_multiple_summary_progress_fn = unsafe extern "C" fn(*mut c_void);
|
|
type FIO_rust_compress_multiple_summary_fn =
|
|
unsafe extern "C" fn(*mut c_void, c_int, u64, u64);
|
|
type FIO_rust_decompress_multiple_summary_fn = unsafe extern "C" fn(*mut c_void, c_int, u64);
|
|
|
|
/// C supplies only display sinks. The multi-file counters are passed as
|
|
/// scalars so the summary policy never depends on private FIO/cRess/dRess
|
|
/// layouts or external codec state.
|
|
#[repr(C)]
|
|
pub struct FIO_rust_multiple_summary_callbacks_t {
|
|
pub opaque: *mut c_void,
|
|
pub display_progress: Option<FIO_rust_multiple_summary_progress_fn>,
|
|
pub display_compression: Option<FIO_rust_compress_multiple_summary_fn>,
|
|
pub display_decompression: Option<FIO_rust_decompress_multiple_summary_fn>,
|
|
}
|
|
|
|
/// Runs the final multi-file summary in the same position and order as the
|
|
/// original C callers: clear progress first, then select the compression or
|
|
/// decompression summary sink. C retains only the exact display formatting
|
|
/// callbacks, preserving diagnostics and the CLI ABI.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_displayMultipleFileSummary(
|
|
nb_files_total: c_int,
|
|
nb_files_processed: c_int,
|
|
total_bytes_input: u64,
|
|
total_bytes_output: u64,
|
|
summary_kind: c_int,
|
|
callbacks: *const FIO_rust_multiple_summary_callbacks_t,
|
|
) {
|
|
if nb_files_total <= 1 || nb_files_processed < 1 {
|
|
return;
|
|
}
|
|
|
|
assert!(!callbacks.is_null());
|
|
let callbacks = unsafe { &*callbacks };
|
|
let display_progress = callbacks
|
|
.display_progress
|
|
.expect("multi-file summary progress callback is required");
|
|
unsafe { display_progress(callbacks.opaque) };
|
|
|
|
match summary_kind {
|
|
FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION => {
|
|
let display = callbacks
|
|
.display_compression
|
|
.expect("compression summary callback is required");
|
|
unsafe {
|
|
display(
|
|
callbacks.opaque,
|
|
nb_files_processed,
|
|
total_bytes_input,
|
|
total_bytes_output,
|
|
)
|
|
};
|
|
}
|
|
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION => {
|
|
let display = callbacks
|
|
.display_decompression
|
|
.expect("decompression summary callback is required");
|
|
unsafe { display(callbacks.opaque, nb_files_processed, total_bytes_output) };
|
|
}
|
|
_ => unreachable!("invalid multi-file summary kind"),
|
|
}
|
|
}
|
|
|
|
type PoolFunction = unsafe extern "C" fn(*mut c_void);
|
|
|
|
#[cfg(not(test))]
|
|
unsafe extern "C" {
|
|
/// Exported by `lib/common/pool.c`; this is the C preprocessor bridge for
|
|
/// `ZSTD_MULTITHREAD` used by the Rust pool implementation as well.
|
|
fn ZSTD_rust_pool_is_multithreaded() -> c_int;
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
unsafe extern "C" {
|
|
fn _fseeki64(file: *mut libc::FILE, offset: i64, whence: c_int) -> c_int;
|
|
}
|
|
|
|
#[inline]
|
|
fn multithreading_enabled() -> bool {
|
|
#[cfg(test)]
|
|
{
|
|
true
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
unsafe { ZSTD_rust_pool_is_multithreaded() != 0 }
|
|
}
|
|
}
|
|
|
|
/// A Windows `CRITICAL_SECTION` layout used only to reserve the C-visible
|
|
/// field. Rust owns synchronization, so no value of this type is initialized.
|
|
#[cfg(windows)]
|
|
#[repr(C)]
|
|
struct WindowsCriticalSection {
|
|
debug_info: *mut c_void,
|
|
lock_count: c_long,
|
|
recursion_count: c_long,
|
|
owning_thread: *mut c_void,
|
|
lock_semaphore: *mut c_void,
|
|
spin_count: usize,
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[repr(C)]
|
|
struct WindowsConditionVariable {
|
|
ptr: *mut c_void,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Copy, Debug)]
|
|
struct AbiLayout {
|
|
pointer_size: usize,
|
|
read_size: usize,
|
|
write_size: usize,
|
|
thread_pool: usize,
|
|
thread_pool_active: usize,
|
|
total_io_jobs: usize,
|
|
prefs: usize,
|
|
pool_function: usize,
|
|
file: usize,
|
|
io_jobs_mutex: usize,
|
|
available_jobs: usize,
|
|
available_jobs_count: usize,
|
|
job_buffer_size: usize,
|
|
write_stored_skips: usize,
|
|
read_reached_eof: usize,
|
|
read_next_offset: usize,
|
|
read_waiting_offset: usize,
|
|
read_current_job: usize,
|
|
read_coalesce_buffer: usize,
|
|
read_src_buffer: usize,
|
|
read_src_buffer_loaded: usize,
|
|
read_completed_jobs: usize,
|
|
read_completed_jobs_count: usize,
|
|
read_job_completed_cond: usize,
|
|
}
|
|
|
|
#[inline]
|
|
const fn align_up(value: usize, alignment: usize) -> usize {
|
|
(value + alignment - 1) & !(alignment - 1)
|
|
}
|
|
|
|
fn abi_layout(threaded: bool) -> AbiLayout {
|
|
let pointer_size = size_of::<*mut c_void>();
|
|
let pointer_align = align_of::<*mut c_void>();
|
|
let int_size = size_of::<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
|
|
}
|
|
|
|
/// Open, attach, iterate, and close one destination shared by all input
|
|
/// files. C retains the private destination and write-pool operations.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_compressMultipleFilenamesWithDestination(
|
|
projection: *const FIO_rust_compress_multiple_destination_projection_t,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
let projection = unsafe { &*projection };
|
|
assert!(!projection.files.is_null());
|
|
let files = unsafe { &*projection.files };
|
|
assert!(!files.f_ctx.is_null());
|
|
assert!(!files.input_file_names.is_null());
|
|
assert!(!files.output_file_name.is_null());
|
|
assert!(!files.opaque.is_null());
|
|
assert!(files.compress_file.is_some());
|
|
|
|
let warning = projection
|
|
.warning
|
|
.expect("shared-destination warning callback is required");
|
|
if unsafe { warning(files.opaque) } != 0 {
|
|
return 1;
|
|
}
|
|
|
|
let open_destination = projection
|
|
.open_destination
|
|
.expect("shared-destination open callback is required");
|
|
let destination = unsafe { open_destination(files.opaque) };
|
|
if destination.is_null() {
|
|
return 1;
|
|
}
|
|
|
|
let attach_destination = projection
|
|
.attach_destination
|
|
.expect("shared-destination attach callback is required");
|
|
unsafe { attach_destination(files.opaque, destination) };
|
|
|
|
let result = unsafe { FIO_rust_compressMultipleFilenames(projection.files) };
|
|
let close_destination = projection
|
|
.close_destination
|
|
.expect("shared-destination close callback is required");
|
|
result | unsafe { close_destination(files.opaque) }
|
|
}
|
|
|
|
/// Select the destination-mode callback once, then iterate files that each
|
|
/// receive a separate destination. C retains destination-name construction,
|
|
/// resource state, diagnostics, and compression dispatch; Rust preserves the
|
|
/// original non-short-circuiting loop and its counter updates.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_compressMultipleSeparateFilenames(
|
|
projection: *const FIO_rust_compress_multiple_separate_projection_t,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
let projection = unsafe { &*projection };
|
|
assert!(!projection.f_ctx.is_null());
|
|
assert!(!projection.input_file_names.is_null());
|
|
let compress_file = if projection.mirror_output != 0 {
|
|
projection
|
|
.compress_mirrored_file
|
|
.expect("mirrored separate-destination compression callback is required")
|
|
} else {
|
|
projection
|
|
.compress_flat_file
|
|
.expect("flat separate-destination compression callback is required")
|
|
};
|
|
|
|
let f_ctx = projection.f_ctx.cast::<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
|
|
}
|
|
|
|
/// Run the shared-destination decompression lifecycle. Test mode still runs
|
|
/// the warning and file iterator, but intentionally skips destination I/O.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenamesWithDestination(
|
|
projection: *const FIO_rust_decompress_multiple_destination_projection_t,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
let projection = unsafe { &*projection };
|
|
assert!(!projection.files.is_null());
|
|
let files = unsafe { &*projection.files };
|
|
assert!(!files.f_ctx.is_null());
|
|
assert!(!files.src_names_table.is_null());
|
|
assert!(!files.out_file_name.is_null());
|
|
assert!(!files.opaque.is_null());
|
|
assert!(files.decompress_file.is_some());
|
|
|
|
let warning = projection
|
|
.warning
|
|
.expect("shared-destination warning callback is required");
|
|
if unsafe { warning(files.opaque) } != 0 {
|
|
return 1;
|
|
}
|
|
|
|
if projection.destination_enabled == 0 {
|
|
return unsafe { FIO_rust_decompressMultipleFilenames(projection.files) };
|
|
}
|
|
|
|
let open_destination = projection
|
|
.open_destination
|
|
.expect("shared-destination open callback is required");
|
|
let destination = unsafe { open_destination(files.opaque) };
|
|
if destination.is_null() {
|
|
return 1;
|
|
}
|
|
|
|
let attach_destination = projection
|
|
.attach_destination
|
|
.expect("shared-destination attach callback is required");
|
|
unsafe { attach_destination(files.opaque, destination) };
|
|
|
|
let result = unsafe { FIO_rust_decompressMultipleFilenames(projection.files) };
|
|
let close_destination = projection
|
|
.close_destination
|
|
.expect("shared-destination close callback is required");
|
|
result | unsafe { close_destination(files.opaque) }
|
|
}
|
|
|
|
/// Iterate files that each receive a separate destination. The C callback
|
|
/// retains destination-name construction, private I/O, format dispatch, and
|
|
/// diagnostics; Rust owns per-file ordering and removal policy.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_decompressMultipleSeparateFilenames(
|
|
projection: *const FIO_rust_decompress_multiple_separate_projection_t,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
let projection = unsafe { &*projection };
|
|
assert!(!projection.f_ctx.is_null());
|
|
assert!(!projection.src_names_table.is_null());
|
|
let decompress_file = projection
|
|
.decompress_file
|
|
.expect("separate-destination decompression callback is required");
|
|
|
|
let f_ctx = projection.f_ctx.cast::<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;
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
type FIO_rust_zstd_to_flush_now_fn = unsafe extern "C" fn(*mut c_void) -> usize;
|
|
#[cfg(feature = "compression")]
|
|
type FIO_rust_zstd_compress_stream2_fn =
|
|
unsafe extern "C" fn(*mut c_void, *mut ZSTD_outBuffer, *mut ZSTD_inBuffer, c_int) -> usize;
|
|
|
|
/// Build the public stream-buffer views, call the Rust compressor, and publish
|
|
/// the same scalar results as the former C-only callback. The codec context
|
|
/// stays opaque; only the public buffer ABI crosses into the compressor.
|
|
#[cfg(feature = "compression")]
|
|
unsafe fn fio_zstd_compress_stream_with(
|
|
cctx: *mut c_void,
|
|
directive: c_int,
|
|
input: *const u8,
|
|
input_size: usize,
|
|
input_pos: usize,
|
|
output: *mut u8,
|
|
output_size: usize,
|
|
input_pos_after: *mut usize,
|
|
output_produced: *mut usize,
|
|
to_flush_now: *mut usize,
|
|
zstd_result: *mut usize,
|
|
to_flush_now_fn: FIO_rust_zstd_to_flush_now_fn,
|
|
compress_stream2_fn: FIO_rust_zstd_compress_stream2_fn,
|
|
) -> c_int {
|
|
let mut input_view = ZSTD_inBuffer {
|
|
src: input.cast::<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(all(feature = "compression", not(test)))]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_zstd_compressStream(
|
|
cctx: *mut c_void,
|
|
_src_file_name: *const c_char,
|
|
directive: c_int,
|
|
input: *const u8,
|
|
input_size: usize,
|
|
input_pos: usize,
|
|
output: *mut u8,
|
|
output_size: usize,
|
|
input_pos_after: *mut usize,
|
|
output_produced: *mut usize,
|
|
to_flush_now: *mut usize,
|
|
zstd_result: *mut usize,
|
|
) -> c_int {
|
|
unsafe {
|
|
fio_zstd_compress_stream_with(
|
|
cctx,
|
|
directive,
|
|
input,
|
|
input_size,
|
|
input_pos,
|
|
output,
|
|
output_size,
|
|
input_pos_after,
|
|
output_produced,
|
|
to_flush_now,
|
|
zstd_result,
|
|
ZSTD_toFlushNow,
|
|
crate::zstd_compress::ZSTD_compressStream2,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Compresses one zstd frame through the C-owned zstd context and adaptive
|
|
/// policy. Rust owns the stream loop and codec call; C callbacks retain the
|
|
/// adaptive diagnostics and all private CLI state.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_compressZstdFrame(
|
|
projection: *const FIO_rust_zstd_compress_projection_t,
|
|
src_file_name: *const c_char,
|
|
src_file_size: u64,
|
|
compression_level: c_int,
|
|
read_size: *mut u64,
|
|
compressed_size: *mut u64,
|
|
zstd_result: *mut usize,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
assert!(!src_file_name.is_null());
|
|
assert!(!read_size.is_null());
|
|
assert!(!compressed_size.is_null());
|
|
assert!(!zstd_result.is_null());
|
|
|
|
unsafe {
|
|
*read_size = 0;
|
|
*compressed_size = 0;
|
|
*zstd_result = 0;
|
|
}
|
|
|
|
let projection = unsafe { &*projection };
|
|
if projection.read_buffer_size == 0 {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
}
|
|
let Some(read_fill) = projection.read_fill else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(read_consume) = projection.read_consume else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_acquire) = projection.write_acquire else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_enqueue) = projection.write_enqueue else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_release) = projection.write_release else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(sparse_write_end) = projection.sparse_write_end else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(compress_stream) = projection.compress_stream else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
let Some(iteration) = projection.iteration else {
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
};
|
|
if projection.adaptive_mode != 0
|
|
&& (projection.adaptive_progression.is_none()
|
|
|| projection.adaptive_set_parameter.is_none()
|
|
|| projection.adaptive_diagnostic.is_none())
|
|
{
|
|
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
|
}
|
|
|
|
let mut job = ptr::null_mut::<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()`.
|
|
#[cfg(feature = "decompression")]
|
|
unsafe fn decompress_zstd_frame_with(
|
|
f_ctx: *mut c_void,
|
|
dctx: *mut c_void,
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
write_ctx: *mut WritePoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
already_decoded: u64,
|
|
frame_size: *mut u64,
|
|
zstd_error: *mut usize,
|
|
progress: FIO_rust_frame_progress_fn,
|
|
reset: FIO_zstd_reset_fn,
|
|
decompress: FIO_zstd_decompress_fn,
|
|
dstream_in_size: FIO_zstd_in_size_fn,
|
|
) -> c_int {
|
|
assert!(!dctx.is_null());
|
|
assert!(!read_ctx.is_null());
|
|
assert!(!write_ctx.is_null());
|
|
assert!(!src_file_name.is_null());
|
|
assert!(!frame_size.is_null());
|
|
assert!(!zstd_error.is_null());
|
|
|
|
unsafe {
|
|
*frame_size = 0;
|
|
*zstd_error = 0;
|
|
}
|
|
|
|
let mut write_job = unsafe { AIO_WritePool_acquireJob(write_ctx) };
|
|
assert!(!write_job.is_null());
|
|
|
|
unsafe {
|
|
// The C implementation intentionally ignores this reset result.
|
|
reset(dctx, 1); // ZSTD_reset_session_only
|
|
AIO_ReadPool_fillBuffer(read_ctx, ZSTD_FRAMEHEADERSIZE_MAX);
|
|
}
|
|
|
|
loop {
|
|
let mut input = crate::zstd_decompress::ZSTD_inBuffer {
|
|
src: unsafe { read_buffer_ptr(read_ctx) }.cast::<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.
|
|
#[cfg(feature = "decompression")]
|
|
unsafe fn decompress_zstd_frames_with(
|
|
f_ctx: *mut c_void,
|
|
dctx: *mut c_void,
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
write_ctx: *mut WritePoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
already_decoded: u64,
|
|
decoded_size: *mut u64,
|
|
zstd_error: *mut usize,
|
|
progress: FIO_rust_frame_progress_fn,
|
|
reset: FIO_zstd_reset_fn,
|
|
decompress: FIO_zstd_decompress_fn,
|
|
dstream_in_size: FIO_zstd_in_size_fn,
|
|
is_frame: FIO_zstd_is_frame_fn,
|
|
) -> c_int {
|
|
assert!(!dctx.is_null());
|
|
assert!(!read_ctx.is_null());
|
|
assert!(!write_ctx.is_null());
|
|
assert!(!src_file_name.is_null());
|
|
assert!(!decoded_size.is_null());
|
|
assert!(!zstd_error.is_null());
|
|
|
|
unsafe {
|
|
*decoded_size = 0;
|
|
*zstd_error = 0;
|
|
}
|
|
|
|
loop {
|
|
unsafe { AIO_ReadPool_fillBuffer(read_ctx, 4) };
|
|
let loaded = unsafe { read_buffer_loaded(read_ctx) };
|
|
if loaded < 4 {
|
|
return FIO_RUST_ZSTD_FRAME_OK;
|
|
}
|
|
|
|
let source = unsafe { read_buffer_ptr(read_ctx) };
|
|
let is_zstd_frame = unsafe { is_frame(source.cast::<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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "decompression")]
|
|
unsafe extern "C" fn fio_zstd_reset(dctx: *mut c_void, reset: c_int) -> usize {
|
|
unsafe {
|
|
crate::zstd_decompress::ZSTD_DCtx_reset(
|
|
dctx.cast::<crate::zstd_decompress::ZSTD_DCtx>(),
|
|
reset,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "decompression")]
|
|
unsafe extern "C" fn fio_zstd_decompress(
|
|
dctx: *mut c_void,
|
|
output: *mut crate::zstd_decompress::ZSTD_outBuffer,
|
|
input: *mut crate::zstd_decompress::ZSTD_inBuffer,
|
|
) -> usize {
|
|
unsafe {
|
|
crate::zstd_decompress::ZSTD_decompressStream(
|
|
dctx.cast::<crate::zstd_decompress::ZSTD_DStream>(),
|
|
output,
|
|
input,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[cfg(feature = "decompression")]
|
|
pub unsafe extern "C" fn FIO_rust_decompressZstdFrame(
|
|
f_ctx: *mut c_void,
|
|
dctx: *mut c_void,
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
write_ctx: *mut WritePoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
already_decoded: u64,
|
|
frame_size: *mut u64,
|
|
zstd_error: *mut usize,
|
|
progress: FIO_rust_frame_progress_fn,
|
|
) -> c_int {
|
|
unsafe {
|
|
decompress_zstd_frame_with(
|
|
f_ctx,
|
|
dctx,
|
|
read_ctx,
|
|
write_ctx,
|
|
src_file_name,
|
|
already_decoded,
|
|
frame_size,
|
|
zstd_error,
|
|
progress,
|
|
fio_zstd_reset,
|
|
fio_zstd_decompress,
|
|
crate::zstd_decompress::ZSTD_DStreamInSize,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[cfg(feature = "decompression")]
|
|
pub unsafe extern "C" fn FIO_rust_decompressZstdFrames(
|
|
f_ctx: *mut c_void,
|
|
dctx: *mut c_void,
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
write_ctx: *mut WritePoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
already_decoded: u64,
|
|
decoded_size: *mut u64,
|
|
zstd_error: *mut usize,
|
|
progress: FIO_rust_frame_progress_fn,
|
|
) -> c_int {
|
|
unsafe {
|
|
decompress_zstd_frames_with(
|
|
f_ctx,
|
|
dctx,
|
|
read_ctx,
|
|
write_ctx,
|
|
src_file_name,
|
|
already_decoded,
|
|
decoded_size,
|
|
zstd_error,
|
|
progress,
|
|
fio_zstd_reset,
|
|
fio_zstd_decompress,
|
|
crate::zstd_decompress::ZSTD_DStreamInSize,
|
|
crate::zstd_decompress::ZSTD_isFrame,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "decompression")]
|
|
const FIO_ERROR_FRAME_DECODING: u64 = u64::MAX - 1;
|
|
|
|
#[inline]
|
|
#[cfg(feature = "decompression")]
|
|
unsafe fn zstd_frame_policy_result(
|
|
callback_context: *mut c_void,
|
|
src_file_name: *const c_char,
|
|
status: c_int,
|
|
decoded_size: u64,
|
|
zstd_error: usize,
|
|
display_decoding_error: FIO_rust_zstd_frame_decoding_error_fn,
|
|
display_premature_end: FIO_rust_zstd_frame_premature_end_fn,
|
|
) -> u64 {
|
|
match status {
|
|
FIO_RUST_ZSTD_FRAME_OK => decoded_size,
|
|
FIO_RUST_ZSTD_FRAME_DECODING_ERROR => {
|
|
unsafe { display_decoding_error(callback_context, src_file_name, zstd_error) };
|
|
FIO_ERROR_FRAME_DECODING
|
|
}
|
|
FIO_RUST_ZSTD_FRAME_PREMATURE_END => {
|
|
unsafe { display_premature_end(callback_context, src_file_name) };
|
|
FIO_ERROR_FRAME_DECODING
|
|
}
|
|
_ => FIO_ERROR_FRAME_DECODING,
|
|
}
|
|
}
|
|
|
|
/// Run the default zstd-frame loop and apply its historical result/error
|
|
/// policy. C retains only exact diagnostics and private resource callbacks.
|
|
#[no_mangle]
|
|
#[cfg(feature = "decompression")]
|
|
pub unsafe extern "C" fn FIO_rust_decompressZstdFramePolicy(
|
|
state: *const FIO_rust_zstd_frame_policy_state,
|
|
) -> u64 {
|
|
let Some(state) = (unsafe { state.as_ref() }) else {
|
|
return FIO_ERROR_FRAME_DECODING;
|
|
};
|
|
let Some(display_decoding_error) = state.display_decoding_error else {
|
|
return FIO_ERROR_FRAME_DECODING;
|
|
};
|
|
let Some(display_premature_end) = state.display_premature_end else {
|
|
return FIO_ERROR_FRAME_DECODING;
|
|
};
|
|
|
|
let mut decoded_size = 0;
|
|
let mut zstd_error = 0;
|
|
let status = unsafe {
|
|
FIO_rust_decompressZstdFrames(
|
|
state.f_ctx,
|
|
state.dctx,
|
|
state.read_ctx,
|
|
state.write_ctx,
|
|
state.src_file_name,
|
|
state.already_decoded,
|
|
&mut decoded_size,
|
|
&mut zstd_error,
|
|
state.progress,
|
|
)
|
|
};
|
|
unsafe {
|
|
zstd_frame_policy_result(
|
|
state.callback_context,
|
|
state.src_file_name,
|
|
status,
|
|
decoded_size,
|
|
zstd_error,
|
|
display_decoding_error,
|
|
display_premature_end,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Decompresses one gzip member through a C-owned zlib stream and the common
|
|
/// asynchronous I/O projection. Input is consumed immediately after each
|
|
/// inflate call; this is equivalent to the original C leaf's final
|
|
/// `avail_in` accounting while keeping any following member or format in the
|
|
/// read pool.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_decompressGzipFrame(
|
|
projection: *const FIO_rust_gzip_decompress_projection_t,
|
|
frame_size: *mut u64,
|
|
zlib_result: *mut c_int,
|
|
) -> c_int {
|
|
assert!(!projection.is_null());
|
|
assert!(!frame_size.is_null());
|
|
assert!(!zlib_result.is_null());
|
|
|
|
unsafe {
|
|
*frame_size = 0;
|
|
*zlib_result = FIO_RUST_GZIP_DECOMPRESS_Z_OK;
|
|
}
|
|
|
|
let projection = unsafe { &*projection };
|
|
if projection.io.read_buffer_size == 0 {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
}
|
|
let Some(read_fill) = projection.io.read_fill else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(read_consume) = projection.io.read_consume else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_acquire) = projection.io.write_acquire else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_enqueue) = projection.io.write_enqueue else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(write_release) = projection.io.write_release else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(sparse_write_end) = projection.io.sparse_write_end else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(zlib_init) = projection.zlib_init else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(zlib_inflate) = projection.zlib_inflate else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
let Some(zlib_end) = projection.zlib_end else {
|
|
return FIO_RUST_GZIP_DECOMPRESS_INVALID_PROJECTION;
|
|
};
|
|
|
|
let init_result = unsafe { zlib_init(projection.zlib_opaque) };
|
|
if init_result != FIO_RUST_GZIP_DECOMPRESS_Z_OK {
|
|
unsafe { *zlib_result = init_result };
|
|
return FIO_RUST_GZIP_DECOMPRESS_INIT_ERROR;
|
|
}
|
|
|
|
let mut job = ptr::null_mut::<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(any(test, not(feature = "decompression")))]
|
|
{
|
|
/* Standalone Rust tests do not link the C legacy-decoder shim. The
|
|
* dispatch tests only need the modern frame magic; production keeps
|
|
* the complete public predicate below. */
|
|
buffer.starts_with(&[0x28, 0xB5, 0x2F, 0xFD])
|
|
}
|
|
#[cfg(all(feature = "decompression", not(test)))]
|
|
{
|
|
unsafe { crate::zstd_decompress::ZSTD_isFrame(buffer.as_ptr().cast(), buffer.len()) != 0 }
|
|
}
|
|
}
|
|
|
|
unsafe fn run_pass_through_callback(callbacks: &FIO_rust_decompress_callbacks_t) -> c_int {
|
|
let Some(callback) = callbacks.pass_through else {
|
|
return FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR;
|
|
};
|
|
if unsafe { callback(callbacks.opaque) } == 0 {
|
|
FIO_RUST_DECOMPRESS_PASS_THROUGH
|
|
} else {
|
|
FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR
|
|
}
|
|
}
|
|
|
|
/// Drives the CLI's mixed-format decompression loop.
|
|
///
|
|
/// Rust owns input probing, format selection, repeated callback dispatch, and
|
|
/// decoded-size accumulation. Codec implementations and their private C
|
|
/// resource layout stay behind the callback projection above. A successful
|
|
/// pass-through is reported separately because the C caller historically
|
|
/// returns before final decompression accounting in that case.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_decompressFrames(
|
|
read_ctx: *mut ReadPoolCtx_t,
|
|
src_file_name: *const c_char,
|
|
pass_through: c_int,
|
|
decoded_size: *mut u64,
|
|
callbacks: *const FIO_rust_decompress_callbacks_t,
|
|
) -> c_int {
|
|
assert!(!read_ctx.is_null());
|
|
assert!(!src_file_name.is_null());
|
|
assert!(!decoded_size.is_null());
|
|
assert!(!callbacks.is_null());
|
|
assert!(pass_through == 0 || pass_through == 1);
|
|
|
|
let callbacks = unsafe { &*callbacks };
|
|
unsafe { *decoded_size = 0 };
|
|
let mut read_something = false;
|
|
|
|
loop {
|
|
unsafe { AIO_ReadPool_fillBuffer(read_ctx, 4) };
|
|
let loaded = unsafe { read_buffer_loaded(read_ctx) };
|
|
if loaded == 0 {
|
|
return if read_something {
|
|
FIO_RUST_DECOMPRESS_OK
|
|
} else {
|
|
FIO_RUST_DECOMPRESS_EMPTY_INPUT
|
|
};
|
|
}
|
|
read_something = true;
|
|
|
|
if loaded < 4 {
|
|
return if pass_through != 0 {
|
|
unsafe { run_pass_through_callback(callbacks) }
|
|
} else {
|
|
FIO_RUST_DECOMPRESS_SHORT_INPUT
|
|
};
|
|
}
|
|
|
|
let source = unsafe { read_buffer_ptr(read_ctx) };
|
|
let buffer = unsafe { std::slice::from_raw_parts(source, loaded) };
|
|
let format = classify_decompression_format(buffer, |bytes| unsafe {
|
|
is_zstd_frame_for_dispatch(bytes)
|
|
});
|
|
|
|
let (callback, missing_status, mode) = match format {
|
|
DecompressionFormat::Zstd => (
|
|
callbacks.decode_zstd,
|
|
FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED,
|
|
0,
|
|
),
|
|
DecompressionFormat::Gzip => (
|
|
callbacks.decode_gzip,
|
|
FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED,
|
|
0,
|
|
),
|
|
DecompressionFormat::Xz => (
|
|
callbacks.decode_lzma,
|
|
FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED,
|
|
0,
|
|
),
|
|
DecompressionFormat::Lzma => (
|
|
callbacks.decode_lzma,
|
|
FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED,
|
|
1,
|
|
),
|
|
DecompressionFormat::Lz4 => {
|
|
(callbacks.decode_lz4, FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED, 0)
|
|
}
|
|
DecompressionFormat::ShortHeader => unreachable!(),
|
|
DecompressionFormat::Unsupported => {
|
|
return if pass_through != 0 {
|
|
unsafe { run_pass_through_callback(callbacks) }
|
|
} else {
|
|
FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT
|
|
};
|
|
}
|
|
};
|
|
|
|
let Some(callback) = callback else {
|
|
return missing_status;
|
|
};
|
|
let mut frame_size = 0_u64;
|
|
let mut error_code = 0_usize;
|
|
let status = unsafe {
|
|
callback(
|
|
callbacks.opaque,
|
|
src_file_name,
|
|
*decoded_size,
|
|
&mut frame_size,
|
|
&mut error_code,
|
|
mode,
|
|
)
|
|
};
|
|
if status != 0 {
|
|
return FIO_RUST_DECOMPRESS_FRAME_ERROR;
|
|
}
|
|
unsafe {
|
|
*decoded_size = (*decoded_size).wrapping_add(frame_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Maps the mixed-format loop's result to the CLI result and performs the
|
|
/// successful-file finalization. C keeps the diagnostic and accounting
|
|
/// callbacks because they touch `DISPLAY*` state and the private `FIO_ctx_t`
|
|
/// layout; Rust owns the result policy and decides which callback is legal.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
enum DecompressFinishAction {
|
|
Finish,
|
|
PassThrough,
|
|
ReportStatus,
|
|
Invalid,
|
|
}
|
|
|
|
fn classify_decompress_finish_status(status: c_int) -> DecompressFinishAction {
|
|
match status {
|
|
FIO_RUST_DECOMPRESS_OK => DecompressFinishAction::Finish,
|
|
FIO_RUST_DECOMPRESS_PASS_THROUGH => DecompressFinishAction::PassThrough,
|
|
FIO_RUST_DECOMPRESS_EMPTY_INPUT
|
|
| FIO_RUST_DECOMPRESS_SHORT_INPUT
|
|
| FIO_RUST_DECOMPRESS_GZIP_UNSUPPORTED
|
|
| FIO_RUST_DECOMPRESS_LZMA_UNSUPPORTED
|
|
| FIO_RUST_DECOMPRESS_LZ4_UNSUPPORTED
|
|
| FIO_RUST_DECOMPRESS_FRAME_ERROR
|
|
| FIO_RUST_DECOMPRESS_UNSUPPORTED_FORMAT
|
|
| FIO_RUST_DECOMPRESS_PASS_THROUGH_ERROR
|
|
| FIO_RUST_DECOMPRESS_ZSTD_UNSUPPORTED => DecompressFinishAction::ReportStatus,
|
|
_ => DecompressFinishAction::Invalid,
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_finishDecompressFrames(
|
|
status: c_int,
|
|
src_file_name: *const c_char,
|
|
decoded_size: u64,
|
|
callbacks: *const FIO_rust_decompress_callbacks_t,
|
|
) -> c_int {
|
|
assert!(!src_file_name.is_null());
|
|
assert!(!callbacks.is_null());
|
|
|
|
let callbacks = unsafe { &*callbacks };
|
|
match classify_decompress_finish_status(status) {
|
|
DecompressFinishAction::Finish => {
|
|
if let Some(finish) = callbacks.finish {
|
|
unsafe { finish(callbacks.opaque, src_file_name, decoded_size) };
|
|
}
|
|
0
|
|
}
|
|
DecompressFinishAction::PassThrough => 0,
|
|
DecompressFinishAction::ReportStatus => {
|
|
if let Some(report_status) = callbacks.report_status {
|
|
unsafe { report_status(callbacks.opaque, status, src_file_name) };
|
|
}
|
|
1
|
|
}
|
|
DecompressFinishAction::Invalid => {
|
|
unreachable!("unknown Rust decompression status: {status}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn read_buffer_ptr(ctx: *mut ReadPoolCtx_t) -> *const u8 {
|
|
let context = ctx.cast::<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>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct MultipleSummaryState {
|
|
events: Vec<&'static str>,
|
|
compression: Option<(c_int, u64, u64)>,
|
|
decompression: Option<(c_int, u64)>,
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_summary_progress(opaque: *mut c_void) {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
|
|
state.events.push("progress");
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_compression_summary(
|
|
opaque: *mut c_void,
|
|
nb_files_processed: c_int,
|
|
total_bytes_input: u64,
|
|
total_bytes_output: u64,
|
|
) {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
|
|
state.events.push("compression");
|
|
state.compression = Some((
|
|
nb_files_processed,
|
|
total_bytes_input,
|
|
total_bytes_output,
|
|
));
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_decompression_summary(
|
|
opaque: *mut c_void,
|
|
nb_files_processed: c_int,
|
|
total_bytes_output: u64,
|
|
) {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
|
|
state.events.push("decompression");
|
|
state.decompression = Some((nb_files_processed, total_bytes_output));
|
|
}
|
|
|
|
fn multiple_summary_callbacks(
|
|
state: &mut MultipleSummaryState,
|
|
) -> FIO_rust_multiple_summary_callbacks_t {
|
|
FIO_rust_multiple_summary_callbacks_t {
|
|
opaque: (state as *mut MultipleSummaryState).cast(),
|
|
display_progress: Some(record_multiple_summary_progress),
|
|
display_compression: Some(record_multiple_compression_summary),
|
|
display_decompression: Some(record_multiple_decompression_summary),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_file_summary_dispatches_compression_after_progress() {
|
|
let mut state = MultipleSummaryState::default();
|
|
let callbacks = multiple_summary_callbacks(&mut state);
|
|
|
|
unsafe {
|
|
FIO_rust_displayMultipleFileSummary(
|
|
3,
|
|
2,
|
|
4096,
|
|
1024,
|
|
FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION,
|
|
&callbacks,
|
|
)
|
|
};
|
|
|
|
assert_eq!(state.events, ["progress", "compression"]);
|
|
assert_eq!(state.compression, Some((2, 4096, 1024)));
|
|
assert_eq!(state.decompression, None);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_file_summary_dispatches_decompression_and_skips_empty_sessions() {
|
|
let mut state = MultipleSummaryState::default();
|
|
let callbacks = multiple_summary_callbacks(&mut state);
|
|
|
|
unsafe {
|
|
FIO_rust_displayMultipleFileSummary(
|
|
4,
|
|
3,
|
|
0,
|
|
777,
|
|
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
|
|
&callbacks,
|
|
)
|
|
};
|
|
assert_eq!(state.events, ["progress", "decompression"]);
|
|
assert_eq!(state.decompression, Some((3, 777)));
|
|
|
|
state.events.clear();
|
|
state.decompression = None;
|
|
unsafe {
|
|
FIO_rust_displayMultipleFileSummary(
|
|
1,
|
|
3,
|
|
10,
|
|
20,
|
|
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
|
|
&callbacks,
|
|
)
|
|
};
|
|
unsafe {
|
|
FIO_rust_displayMultipleFileSummary(
|
|
4,
|
|
0,
|
|
10,
|
|
20,
|
|
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
|
|
&callbacks,
|
|
)
|
|
};
|
|
assert!(state.events.is_empty());
|
|
assert_eq!(state.decompression, None);
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_compression_file(
|
|
opaque: *mut c_void,
|
|
destination: *const c_char,
|
|
source: *const c_char,
|
|
) -> c_int {
|
|
let state = unsafe { &mut *opaque.cast::<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
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct MultipleDestinationSessionState {
|
|
events: Vec<&'static str>,
|
|
sources: Vec<*const c_char>,
|
|
destinations: Vec<*const c_char>,
|
|
statuses: Vec<c_int>,
|
|
allow_open: bool,
|
|
warning_status: c_int,
|
|
close_status: c_int,
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_destination_warning(opaque: *mut c_void) -> c_int {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
|
state.events.push("warning");
|
|
state.warning_status
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_destination_open(opaque: *mut c_void) -> *mut c_void {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
|
state.events.push("open");
|
|
if state.allow_open {
|
|
ptr::dangling_mut::<c_void>()
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_destination_attach(
|
|
opaque: *mut c_void,
|
|
destination: *mut c_void,
|
|
) {
|
|
assert!(!destination.is_null());
|
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
|
state.events.push("attach");
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_destination_close(opaque: *mut c_void) -> c_int {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
|
state.events.push("close");
|
|
state.close_status
|
|
}
|
|
|
|
unsafe extern "C" fn record_multiple_destination_file(
|
|
opaque: *mut c_void,
|
|
destination: *const c_char,
|
|
source: *const c_char,
|
|
) -> c_int {
|
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
|
let status = state
|
|
.statuses
|
|
.get(state.sources.len())
|
|
.copied()
|
|
.unwrap_or(0);
|
|
state.events.push("file");
|
|
state.sources.push(source);
|
|
state.destinations.push(destination);
|
|
status
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_destination_session_owns_lifecycle() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
|
let destination = c"archive.zst";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 2,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 0,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDestinationSessionState {
|
|
allow_open: true,
|
|
statuses: vec![0, 4],
|
|
..MultipleDestinationSessionState::default()
|
|
};
|
|
let files = FIO_rust_compress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
output_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
|
compress_file: Some(record_multiple_destination_file),
|
|
};
|
|
let projection = FIO_rust_compress_multiple_destination_projection_t {
|
|
files: &files,
|
|
warning: Some(record_multiple_destination_warning),
|
|
open_destination: Some(record_multiple_destination_open),
|
|
attach_destination: Some(record_multiple_destination_attach),
|
|
close_destination: Some(record_multiple_destination_close),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) },
|
|
4
|
|
);
|
|
assert_eq!(state.events, ["warning", "open", "attach", "file", "file", "close"]);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 2]);
|
|
assert_eq!(context.currFileIdx, 2);
|
|
assert_eq!(context.nbFilesProcessed, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_destination_session_stops_on_open_failure() {
|
|
let source_names = [c"one".as_ptr()];
|
|
let destination = c"archive.zst";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 1,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 3,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDestinationSessionState::default();
|
|
let files = FIO_rust_compress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
output_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
|
compress_file: Some(record_multiple_destination_file),
|
|
};
|
|
let projection = FIO_rust_compress_multiple_destination_projection_t {
|
|
files: &files,
|
|
warning: Some(record_multiple_destination_warning),
|
|
open_destination: Some(record_multiple_destination_open),
|
|
attach_destination: Some(record_multiple_destination_attach),
|
|
close_destination: Some(record_multiple_destination_close),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) },
|
|
1
|
|
);
|
|
assert_eq!(state.events, ["warning", "open"]);
|
|
assert!(state.sources.is_empty());
|
|
assert_eq!(context.currFileIdx, 0);
|
|
assert_eq!(context.nbFilesProcessed, 3);
|
|
}
|
|
|
|
#[test]
|
|
fn decompression_multiple_destination_session_skips_io_in_test_mode() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
|
let destination = c"archive";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 2,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 0,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDestinationSessionState {
|
|
statuses: vec![0, 8],
|
|
..MultipleDestinationSessionState::default()
|
|
};
|
|
let files = FIO_rust_decompress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
src_names_table: source_names.as_ptr(),
|
|
out_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
|
decompress_file: Some(record_multiple_destination_file),
|
|
};
|
|
let projection = FIO_rust_decompress_multiple_destination_projection_t {
|
|
files: &files,
|
|
destination_enabled: 0,
|
|
warning: Some(record_multiple_destination_warning),
|
|
open_destination: Some(record_multiple_destination_open),
|
|
attach_destination: Some(record_multiple_destination_attach),
|
|
close_destination: Some(record_multiple_destination_close),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_decompressMultipleFilenamesWithDestination(&projection) },
|
|
8
|
|
);
|
|
assert_eq!(state.events, ["warning", "file", "file"]);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 2);
|
|
assert_eq!(context.nbFilesProcessed, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_separate_destinations_selects_mirror_callback_only() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 2,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 0,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = SeparateCompressionRoutingState::default();
|
|
let projection = FIO_rust_compress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(),
|
|
mirror_output: 1,
|
|
compress_mirrored_file: Some(record_mirrored_compression_file),
|
|
compress_flat_file: Some(record_flat_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.routes, vec!["mirror", "mirror"]);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 2);
|
|
assert_eq!(context.nbFilesProcessed, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_separate_destinations_selects_flat_callback_only() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 2,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 0,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = SeparateCompressionRoutingState::default();
|
|
let projection = FIO_rust_compress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(),
|
|
mirror_output: 0,
|
|
compress_mirrored_file: Some(record_mirrored_compression_file),
|
|
compress_flat_file: Some(record_flat_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.routes, vec!["flat", "flat"]);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 2);
|
|
assert_eq!(context.nbFilesProcessed, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_separate_destinations_accumulates_selected_callback_errors_in_order() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 4,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = SeparateCompressionRoutingState {
|
|
statuses: vec![1, 4, 2],
|
|
..SeparateCompressionRoutingState::default()
|
|
};
|
|
let projection = FIO_rust_compress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut SeparateCompressionRoutingState).cast(),
|
|
mirror_output: 1,
|
|
compress_mirrored_file: Some(record_mirrored_compression_file),
|
|
compress_flat_file: Some(record_flat_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) },
|
|
1 | 4 | 2
|
|
);
|
|
assert_eq!(state.routes, vec!["mirror", "mirror", "mirror"]);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_separate_destinations_preserves_order_and_counters() {
|
|
let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 1,
|
|
nbFilesProcessed: 5,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleCompressionState {
|
|
statuses: vec![0, 0],
|
|
..MultipleCompressionState::default()
|
|
};
|
|
let projection = FIO_rust_compress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleCompressionState).cast(),
|
|
mirror_output: 0,
|
|
compress_mirrored_file: Some(record_multiple_separate_compression_file),
|
|
compress_flat_file: Some(record_multiple_separate_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.sources, vec![source_names[1], source_names[2]]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 7);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_separate_destinations_accumulates_errors_without_short_circuiting() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 4,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleCompressionState {
|
|
statuses: vec![1, 4, 2],
|
|
..MultipleCompressionState::default()
|
|
};
|
|
let projection = FIO_rust_compress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleCompressionState).cast(),
|
|
mirror_output: 0,
|
|
compress_mirrored_file: Some(record_multiple_separate_compression_file),
|
|
compress_flat_file: Some(record_multiple_separate_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleSeparateFilenames(&projection) },
|
|
1 | 4 | 2
|
|
);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_shared_destination_preserves_order_and_counters() {
|
|
let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()];
|
|
let destination = c"archive.zst";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 1,
|
|
nbFilesProcessed: 5,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleCompressionState {
|
|
statuses: vec![0, 0],
|
|
..MultipleCompressionState::default()
|
|
};
|
|
let projection = FIO_rust_compress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
output_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleCompressionState).cast(),
|
|
compress_file: Some(record_multiple_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.sources, vec![source_names[1], source_names[2]]);
|
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 2]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 7);
|
|
}
|
|
|
|
#[test]
|
|
fn compression_multiple_shared_destination_accumulates_errors_without_short_circuiting() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()];
|
|
let destination = c"archive.zst";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 4,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleCompressionState {
|
|
statuses: vec![1, 4, 2],
|
|
..MultipleCompressionState::default()
|
|
};
|
|
let projection = FIO_rust_compress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
input_file_names: source_names.as_ptr(),
|
|
output_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleCompressionState).cast(),
|
|
compress_file: Some(record_multiple_compression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_compressMultipleFilenames(&projection) },
|
|
1 | 4 | 2
|
|
);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 3]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn decompression_multiple_shared_destination_preserves_order_and_counters() {
|
|
let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()];
|
|
let destination = c"archive";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 1,
|
|
nbFilesProcessed: 5,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDecompressionState {
|
|
statuses: vec![0, 0],
|
|
..MultipleDecompressionState::default()
|
|
};
|
|
let projection = FIO_rust_decompress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
src_names_table: source_names.as_ptr(),
|
|
out_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDecompressionState).cast(),
|
|
decompress_file: Some(record_multiple_decompression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_decompressMultipleFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.sources, vec![source_names[1], source_names[2]]);
|
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 2]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 7);
|
|
}
|
|
|
|
#[test]
|
|
fn decompression_multiple_shared_destination_accumulates_errors_without_short_circuiting() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()];
|
|
let destination = c"archive";
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 4,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDecompressionState {
|
|
statuses: vec![1, 4, 2],
|
|
..MultipleDecompressionState::default()
|
|
};
|
|
let projection = FIO_rust_decompress_multiple_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
src_names_table: source_names.as_ptr(),
|
|
out_file_name: destination.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDecompressionState).cast(),
|
|
decompress_file: Some(record_multiple_decompression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_decompressMultipleFilenames(&projection) },
|
|
1 | 4 | 2
|
|
);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 3]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn decompression_multiple_separate_destinations_preserves_order_and_counters() {
|
|
let source_names = [c"zero".as_ptr(), c"one".as_ptr(), c"two".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 1,
|
|
nbFilesProcessed: 5,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDecompressionState {
|
|
statuses: vec![0, 0],
|
|
..MultipleDecompressionState::default()
|
|
};
|
|
let projection = FIO_rust_decompress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
src_names_table: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDecompressionState).cast(),
|
|
decompress_file: Some(record_multiple_separate_decompression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_decompressMultipleSeparateFilenames(&projection) },
|
|
0
|
|
);
|
|
assert_eq!(state.sources, vec![source_names[1], source_names[2]]);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 7);
|
|
}
|
|
|
|
#[test]
|
|
fn decompression_multiple_separate_destinations_accumulates_errors_without_short_circuiting() {
|
|
let source_names = [c"one".as_ptr(), c"two".as_ptr(), c"three".as_ptr()];
|
|
let mut context = FIO_rust_compression_context_t {
|
|
nbFilesTotal: 3,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: 4,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
};
|
|
let mut state = MultipleDecompressionState {
|
|
statuses: vec![1, 4, 2],
|
|
..MultipleDecompressionState::default()
|
|
};
|
|
let projection = FIO_rust_decompress_multiple_separate_projection_t {
|
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
|
src_names_table: source_names.as_ptr(),
|
|
opaque: (&mut state as *mut MultipleDecompressionState).cast(),
|
|
decompress_file: Some(record_multiple_separate_decompression_file),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_decompressMultipleSeparateFilenames(&projection) },
|
|
1 | 4 | 2
|
|
);
|
|
assert_eq!(state.sources, source_names);
|
|
assert_eq!(context.currFileIdx, 3);
|
|
assert_eq!(context.nbFilesProcessed, 4);
|
|
}
|
|
|
|
struct ZstdCodecCallbackTestState {
|
|
pending_flush: usize,
|
|
codec_result: usize,
|
|
observed_input: Option<(usize, usize, usize)>,
|
|
observed_output: Option<(usize, usize, usize)>,
|
|
observed_directive: c_int,
|
|
}
|
|
|
|
unsafe extern "C" fn zstd_test_to_flush_now(opaque: *mut c_void) -> usize {
|
|
let state = unsafe { &mut *opaque.cast::<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);
|
|
}
|
|
}
|
|
}
|