Move the CLI's strategy-dependent cycle-log calculation behind a scalar Rust ABI shim while retaining the C assertion and call-site behavior. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features cli,compression,decompression,benchmark (116 tests) - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets with the full CLI feature set - make -B -C programs -j2 zstd zstd-small zstd-frugal - make -C tests -j2 test-cli-tests (41 scenarios)
1668 lines
56 KiB
Rust
1668 lines
56 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(clippy::missing_safety_doc)]
|
|
|
|
//! Rust implementation of the small preference/context part of `fileio.c`.
|
|
//!
|
|
//! The file-I/O operations themselves remain in C. These layouts are passed
|
|
//! directly to that code, so allocation deliberately uses the C allocator and
|
|
//! preference creation initializes exactly the fields initialized by the C
|
|
//! implementation. In particular, the four legacy fields which C leaves
|
|
//! unspecified are not zero-filled here either.
|
|
|
|
use std::ffi::{c_void, CStr};
|
|
use std::io::{self, Write};
|
|
use std::mem::size_of;
|
|
use std::os::raw::{c_char, c_int, c_uint};
|
|
use std::ptr;
|
|
|
|
const FIO_ZSTD_COMPRESSION: c_int = 0;
|
|
const FIO_OVERLAP_LOG_NOTSET: c_int = 9999;
|
|
const FIO_LDM_PARAM_NOTSET: c_int = 9999;
|
|
const FIO_PATCH_MEM_LIMIT_SUCCESS: c_int = 0;
|
|
const FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE: c_int = 1;
|
|
const FIO_PATCH_MEM_LIMIT_TOO_LARGE: c_int = 2;
|
|
const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
|
|
const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 };
|
|
const ZSTD_BTLAZY2: c_int = 6;
|
|
static STDOUT_MARK: &[u8] = b"/*stdout*\\\0";
|
|
|
|
static mut COMPRESSED_NAME_CAPACITY: usize = 0;
|
|
static mut COMPRESSED_NAME_BUFFER: *mut c_char = ptr::null_mut();
|
|
static mut DESTINATION_NAME_CAPACITY: usize = 0;
|
|
static mut DESTINATION_NAME_BUFFER: *mut c_char = ptr::null_mut();
|
|
|
|
#[repr(C)]
|
|
pub struct FIO_inBuffer {
|
|
src: *const c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct FIO_outBuffer {
|
|
dst: *mut c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
/// C's private `fileInfo_t` from `programs/fileio.c`.
|
|
#[repr(C)]
|
|
pub struct FIO_fileInfo_t {
|
|
decompressedSize: u64,
|
|
compressedSize: u64,
|
|
windowSize: u64,
|
|
numActualFrames: c_int,
|
|
numSkippableFrames: c_int,
|
|
decompUnavailable: c_int,
|
|
usesCheck: c_int,
|
|
checksum: [u8; 4],
|
|
nbFiles: u32,
|
|
dictID: c_uint,
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
const ZSTD_SPARSE_DEFAULT: c_int = 0;
|
|
#[cfg(not(target_vendor = "apple"))]
|
|
const ZSTD_SPARSE_DEFAULT: c_int = 1;
|
|
|
|
#[repr(C)]
|
|
struct FIO_display_prefs_t {
|
|
displayLevel: c_int,
|
|
progressSetting: c_int,
|
|
}
|
|
|
|
/// C's `FIO_prefs_t` from `programs/fileio_types.h`.
|
|
#[repr(C)]
|
|
pub struct FIO_prefs_t {
|
|
compressionType: c_int,
|
|
sparseFileSupport: c_int,
|
|
dictIDFlag: c_int,
|
|
checksumFlag: c_int,
|
|
blockSize: c_int,
|
|
overlapLog: c_int,
|
|
adaptiveMode: c_int,
|
|
useRowMatchFinder: c_int,
|
|
rsyncable: c_int,
|
|
minAdaptLevel: c_int,
|
|
maxAdaptLevel: c_int,
|
|
ldmFlag: c_int,
|
|
ldmHashLog: c_int,
|
|
ldmMinMatch: c_int,
|
|
ldmBucketSizeLog: c_int,
|
|
ldmHashRateLog: c_int,
|
|
streamSrcSize: usize,
|
|
targetCBlockSize: usize,
|
|
srcSizeHint: c_int,
|
|
testMode: c_int,
|
|
literalCompressionMode: c_int,
|
|
removeSrcFile: c_int,
|
|
overwrite: c_int,
|
|
asyncIO: c_int,
|
|
memLimit: c_uint,
|
|
nbWorkers: c_int,
|
|
excludeCompressedFiles: c_int,
|
|
patchFromMode: c_int,
|
|
contentSize: c_int,
|
|
allowBlockDevices: c_int,
|
|
passThrough: c_int,
|
|
mmapDict: c_int,
|
|
}
|
|
|
|
/// C's private `FIO_ctx_s` from `programs/fileio.c`.
|
|
#[repr(C)]
|
|
pub struct FIO_ctx_t {
|
|
nbFilesTotal: c_int,
|
|
hasStdinInput: c_int,
|
|
hasStdoutOutput: c_int,
|
|
currFileIdx: c_int,
|
|
nbFilesProcessed: c_int,
|
|
totalBytesInput: usize,
|
|
totalBytesOutput: usize,
|
|
}
|
|
|
|
/// Mirror of the `FileNamesTable` used by `FIO_determineHasStdinInput`.
|
|
#[repr(C)]
|
|
pub struct FileNamesTable {
|
|
fileNames: *mut *const c_char,
|
|
buf: *mut c_char,
|
|
tableSize: usize,
|
|
tableCapacity: usize,
|
|
}
|
|
|
|
#[cfg(not(test))]
|
|
unsafe extern "C" {
|
|
static mut g_display_prefs: FIO_display_prefs_t;
|
|
fn AIO_supported() -> c_int;
|
|
#[cfg(feature = "compression")]
|
|
fn ZSTD_minCLevel() -> c_int;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
static mut TEST_DISPLAY_PREFS: FIO_display_prefs_t = FIO_display_prefs_t {
|
|
displayLevel: 2,
|
|
progressSetting: 0,
|
|
};
|
|
|
|
#[inline]
|
|
unsafe fn display_prefs() -> *mut FIO_display_prefs_t {
|
|
#[cfg(test)]
|
|
{
|
|
ptr::addr_of_mut!(TEST_DISPLAY_PREFS)
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
ptr::addr_of_mut!(g_display_prefs)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn aio_supported() -> bool {
|
|
#[cfg(test)]
|
|
{
|
|
false
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
unsafe { AIO_supported() != 0 }
|
|
}
|
|
}
|
|
|
|
fn display(level: c_int, message: &str) {
|
|
let enabled = unsafe { (*display_prefs()).displayLevel >= level };
|
|
if enabled {
|
|
eprint!("{message}");
|
|
}
|
|
}
|
|
|
|
fn throw(error: c_int, message: &str) -> ! {
|
|
let enabled = unsafe { (*display_prefs()).displayLevel >= 1 };
|
|
if enabled {
|
|
eprintln!("zstd: error {error} : {message} ");
|
|
}
|
|
std::process::exit(error);
|
|
}
|
|
|
|
unsafe fn allocate<T>() -> *mut T {
|
|
let allocation = unsafe { libc::malloc(size_of::<T>()) }.cast::<T>();
|
|
if allocation.is_null() {
|
|
throw(21, "Allocation error : not enough memory");
|
|
}
|
|
allocation
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_createPreferences() -> *mut FIO_prefs_t {
|
|
let ret = unsafe { allocate::<FIO_prefs_t>() };
|
|
|
|
/* Keep this list in lock-step with the original C initialization. The
|
|
* useRowMatchFinder, patchFromMode, contentSize, and mmapDict fields are
|
|
* intentionally left unspecified until their CLI setters are called. */
|
|
unsafe {
|
|
ptr::addr_of_mut!((*ret).compressionType).write(FIO_ZSTD_COMPRESSION);
|
|
ptr::addr_of_mut!((*ret).overwrite).write(0);
|
|
ptr::addr_of_mut!((*ret).sparseFileSupport).write(ZSTD_SPARSE_DEFAULT);
|
|
ptr::addr_of_mut!((*ret).dictIDFlag).write(1);
|
|
ptr::addr_of_mut!((*ret).checksumFlag).write(1);
|
|
ptr::addr_of_mut!((*ret).removeSrcFile).write(0);
|
|
ptr::addr_of_mut!((*ret).memLimit).write(0);
|
|
ptr::addr_of_mut!((*ret).nbWorkers).write(1);
|
|
ptr::addr_of_mut!((*ret).blockSize).write(0);
|
|
ptr::addr_of_mut!((*ret).overlapLog).write(FIO_OVERLAP_LOG_NOTSET);
|
|
ptr::addr_of_mut!((*ret).adaptiveMode).write(0);
|
|
ptr::addr_of_mut!((*ret).rsyncable).write(0);
|
|
ptr::addr_of_mut!((*ret).minAdaptLevel).write(-50);
|
|
ptr::addr_of_mut!((*ret).maxAdaptLevel).write(22);
|
|
ptr::addr_of_mut!((*ret).ldmFlag).write(0);
|
|
ptr::addr_of_mut!((*ret).ldmHashLog).write(0);
|
|
ptr::addr_of_mut!((*ret).ldmMinMatch).write(0);
|
|
ptr::addr_of_mut!((*ret).ldmBucketSizeLog).write(FIO_LDM_PARAM_NOTSET);
|
|
ptr::addr_of_mut!((*ret).ldmHashRateLog).write(FIO_LDM_PARAM_NOTSET);
|
|
ptr::addr_of_mut!((*ret).streamSrcSize).write(0);
|
|
ptr::addr_of_mut!((*ret).targetCBlockSize).write(0);
|
|
ptr::addr_of_mut!((*ret).srcSizeHint).write(0);
|
|
ptr::addr_of_mut!((*ret).testMode).write(0);
|
|
ptr::addr_of_mut!((*ret).literalCompressionMode).write(0);
|
|
ptr::addr_of_mut!((*ret).excludeCompressedFiles).write(0);
|
|
ptr::addr_of_mut!((*ret).allowBlockDevices).write(0);
|
|
ptr::addr_of_mut!((*ret).asyncIO).write(c_int::from(aio_supported()));
|
|
ptr::addr_of_mut!((*ret).passThrough).write(-1);
|
|
}
|
|
ret
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_createContext() -> *mut FIO_ctx_t {
|
|
let ret = unsafe { allocate::<FIO_ctx_t>() };
|
|
unsafe {
|
|
ptr::addr_of_mut!((*ret).currFileIdx).write(0);
|
|
ptr::addr_of_mut!((*ret).hasStdinInput).write(0);
|
|
ptr::addr_of_mut!((*ret).hasStdoutOutput).write(0);
|
|
ptr::addr_of_mut!((*ret).nbFilesTotal).write(1);
|
|
ptr::addr_of_mut!((*ret).nbFilesProcessed).write(0);
|
|
ptr::addr_of_mut!((*ret).totalBytesInput).write(0);
|
|
ptr::addr_of_mut!((*ret).totalBytesOutput).write(0);
|
|
}
|
|
ret
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_freePreferences(prefs: *mut FIO_prefs_t) {
|
|
unsafe { libc::free(prefs.cast::<c_void>()) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_freeContext(ctx: *mut FIO_ctx_t) {
|
|
unsafe { libc::free(ctx.cast::<c_void>()) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_setNotificationLevel(level: c_int) {
|
|
unsafe { (*display_prefs()).displayLevel = level };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_setProgressSetting(setting: c_int) {
|
|
unsafe { (*display_prefs()).progressSetting = setting };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setCompressionType(prefs: *mut FIO_prefs_t, compression_type: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).compressionType).write(compression_type) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_overwriteMode(prefs: *mut FIO_prefs_t) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).overwrite).write(1) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setSparseWrite(prefs: *mut FIO_prefs_t, sparse: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).sparseFileSupport).write(sparse) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setDictIDFlag(prefs: *mut FIO_prefs_t, dict_id_flag: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).dictIDFlag).write(dict_id_flag) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setChecksumFlag(prefs: *mut FIO_prefs_t, checksum_flag: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).checksumFlag).write(checksum_flag) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setRemoveSrcFile(prefs: *mut FIO_prefs_t, flag: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).removeSrcFile).write(c_int::from(flag != 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setMemLimit(prefs: *mut FIO_prefs_t, mem_limit: c_uint) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).memLimit).write(mem_limit) };
|
|
}
|
|
|
|
fn adjusted_patch_mem_limit(
|
|
current_mem_limit: c_uint,
|
|
dict_size: u64,
|
|
max_src_file_size: u64,
|
|
) -> Result<c_uint, c_int> {
|
|
let max_size = u64::from(current_mem_limit)
|
|
.max(dict_size)
|
|
.max(max_src_file_size);
|
|
|
|
if max_size == UTIL_FILESIZE_UNKNOWN {
|
|
return Err(FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE);
|
|
}
|
|
if max_size > (1u64 << ZSTD_WINDOWLOG_MAX) {
|
|
return Err(FIO_PATCH_MEM_LIMIT_TOO_LARGE);
|
|
}
|
|
Ok(max_size as c_uint)
|
|
}
|
|
|
|
/// Adjust the patch-from memory limit without changing it on a rejected size.
|
|
///
|
|
/// The status values are mirrored by the C diagnostic wrapper immediately
|
|
/// below its ABI declarations.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_adjustMemLimitForPatchFromMode(
|
|
prefs: *mut FIO_prefs_t,
|
|
dict_size: u64,
|
|
max_src_file_size: u64,
|
|
) -> c_int {
|
|
let current_mem_limit = unsafe { (*prefs).memLimit };
|
|
match adjusted_patch_mem_limit(current_mem_limit, dict_size, max_src_file_size) {
|
|
Ok(mem_limit) => {
|
|
unsafe { ptr::addr_of_mut!((*prefs).memLimit).write(mem_limit) };
|
|
FIO_PATCH_MEM_LIMIT_SUCCESS
|
|
}
|
|
Err(status) => status,
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setNbWorkers(prefs: *mut FIO_prefs_t, nb_workers: c_int) {
|
|
if unsafe { !aio_supported() } && nb_workers > 0 {
|
|
display(2, "Note : multi-threading is disabled \n");
|
|
}
|
|
unsafe { ptr::addr_of_mut!((*prefs).nbWorkers).write(nb_workers) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setExcludeCompressedFile(
|
|
prefs: *mut FIO_prefs_t,
|
|
exclude_compressed_files: c_int,
|
|
) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).excludeCompressedFiles).write(exclude_compressed_files) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setAllowBlockDevices(
|
|
prefs: *mut FIO_prefs_t,
|
|
allow_block_devices: c_int,
|
|
) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).allowBlockDevices).write(allow_block_devices) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setBlockSize(prefs: *mut FIO_prefs_t, block_size: c_int) {
|
|
if block_size != 0 && unsafe { (*prefs).nbWorkers == 0 } {
|
|
display(2, "Setting block size is useless in single-thread mode \n");
|
|
}
|
|
unsafe { ptr::addr_of_mut!((*prefs).blockSize).write(block_size) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setOverlapLog(prefs: *mut FIO_prefs_t, overlap_log: c_int) {
|
|
if overlap_log != 0 && unsafe { (*prefs).nbWorkers == 0 } {
|
|
display(2, "Setting overlapLog is useless in single-thread mode \n");
|
|
}
|
|
unsafe { ptr::addr_of_mut!((*prefs).overlapLog).write(overlap_log) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setAdaptiveMode(prefs: *mut FIO_prefs_t, adapt: c_int) {
|
|
if adapt > 0 && unsafe { (*prefs).nbWorkers == 0 } {
|
|
throw(
|
|
1,
|
|
"Adaptive mode is not compatible with single thread mode \n",
|
|
);
|
|
}
|
|
unsafe { ptr::addr_of_mut!((*prefs).adaptiveMode).write(adapt) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setUseRowMatchFinder(
|
|
prefs: *mut FIO_prefs_t,
|
|
use_row_match_finder: c_int,
|
|
) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).useRowMatchFinder).write(use_row_match_finder) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setRsyncable(prefs: *mut FIO_prefs_t, rsyncable: c_int) {
|
|
if rsyncable > 0 && unsafe { (*prefs).nbWorkers == 0 } {
|
|
throw(
|
|
1,
|
|
"Rsyncable mode is not compatible with single thread mode \n",
|
|
);
|
|
}
|
|
unsafe { ptr::addr_of_mut!((*prefs).rsyncable).write(rsyncable) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setStreamSrcSize(prefs: *mut FIO_prefs_t, stream_src_size: usize) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).streamSrcSize).write(stream_src_size) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setTargetCBlockSize(
|
|
prefs: *mut FIO_prefs_t,
|
|
target_c_block_size: usize,
|
|
) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).targetCBlockSize).write(target_c_block_size) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setSrcSizeHint(prefs: *mut FIO_prefs_t, src_size_hint: usize) {
|
|
let capped = src_size_hint.min(c_int::MAX as usize) as c_int;
|
|
unsafe { ptr::addr_of_mut!((*prefs).srcSizeHint).write(capped) };
|
|
}
|
|
|
|
#[cfg(feature = "decompression")]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setTestMode(prefs: *mut FIO_prefs_t, test_mode: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).testMode).write(c_int::from(test_mode != 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLiteralCompressionMode(prefs: *mut FIO_prefs_t, mode: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).literalCompressionMode).write(mode) };
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
#[inline]
|
|
fn min_compression_level() -> c_int {
|
|
#[cfg(test)]
|
|
{
|
|
-50
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
unsafe { ZSTD_minCLevel() }
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setAdaptMin(prefs: *mut FIO_prefs_t, min_c_level: c_int) {
|
|
assert!(min_c_level >= min_compression_level());
|
|
unsafe { ptr::addr_of_mut!((*prefs).minAdaptLevel).write(min_c_level) };
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setAdaptMax(prefs: *mut FIO_prefs_t, max_c_level: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).maxAdaptLevel).write(max_c_level) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLdmFlag(prefs: *mut FIO_prefs_t, ldm_flag: c_uint) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).ldmFlag).write(c_int::from(ldm_flag > 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLdmHashLog(prefs: *mut FIO_prefs_t, ldm_hash_log: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).ldmHashLog).write(ldm_hash_log) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLdmMinMatch(prefs: *mut FIO_prefs_t, ldm_min_match: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).ldmMinMatch).write(ldm_min_match) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLdmBucketSizeLog(
|
|
prefs: *mut FIO_prefs_t,
|
|
ldm_bucket_size_log: c_int,
|
|
) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).ldmBucketSizeLog).write(ldm_bucket_size_log) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setLdmHashRateLog(prefs: *mut FIO_prefs_t, ldm_hash_rate_log: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).ldmHashRateLog).write(ldm_hash_rate_log) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setPatchFromMode(prefs: *mut FIO_prefs_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).patchFromMode).write(c_int::from(value != 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setContentSize(prefs: *mut FIO_prefs_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).contentSize).write(c_int::from(value != 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setAsyncIOFlag(prefs: *mut FIO_prefs_t, value: c_int) {
|
|
if unsafe { aio_supported() } {
|
|
unsafe { ptr::addr_of_mut!((*prefs).asyncIO).write(value) };
|
|
} else {
|
|
display(
|
|
2,
|
|
"Note : asyncio is disabled (lack of multithreading support) \n",
|
|
);
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setPassThroughFlag(prefs: *mut FIO_prefs_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).passThrough).write(c_int::from(value != 0)) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setMMapDict(prefs: *mut FIO_prefs_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*prefs).mmapDict).write(value) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setHasStdoutOutput(ctx: *mut FIO_ctx_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*ctx).hasStdoutOutput).write(value) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setNbFilesTotal(ctx: *mut FIO_ctx_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*ctx).nbFilesTotal).write(value) };
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_setHasStdinInput(ctx: *mut FIO_ctx_t, value: c_int) {
|
|
unsafe { ptr::addr_of_mut!((*ctx).hasStdinInput).write(c_int::from(value != 0)) };
|
|
}
|
|
|
|
#[inline]
|
|
fn should_display_file_summary(ctx: &FIO_ctx_t) -> bool {
|
|
ctx.nbFilesTotal <= 1 || unsafe { (*display_prefs()).displayLevel >= 3 }
|
|
}
|
|
|
|
/// Return whether the single-file summary should be displayed.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_shouldDisplayFileSummary(ctx: *const FIO_ctx_t) -> c_int {
|
|
let ctx = unsafe { &*ctx };
|
|
c_int::from(should_display_file_summary(ctx))
|
|
}
|
|
|
|
/// Return whether the multiple-file summary should be displayed.
|
|
///
|
|
/// The assertion mirrors the original C helper's invariant exactly.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_shouldDisplayMultipleFileSummary(ctx: *const FIO_ctx_t) -> c_int {
|
|
let ctx = unsafe { &*ctx };
|
|
let should_display = ctx.nbFilesProcessed >= 1 && ctx.nbFilesTotal > 1;
|
|
assert!(should_display || should_display_file_summary(ctx) || ctx.nbFilesProcessed == 0);
|
|
c_int::from(should_display)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_determineHasStdinInput(
|
|
ctx: *mut FIO_ctx_t,
|
|
filenames: *const FileNamesTable,
|
|
) {
|
|
for index in 0..unsafe { (*filenames).tableSize } {
|
|
let file_name = unsafe { *(*filenames).fileNames.add(index) };
|
|
if unsafe { libc::strcmp(c"/*stdin*\\".as_ptr(), file_name) == 0 } {
|
|
unsafe { ptr::addr_of_mut!((*ctx).hasStdinInput).write(1) };
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return the position of the highest set bit in a non-zero 64-bit value.
|
|
///
|
|
/// This mirrors `FIO_highbit64()` and deliberately keeps its zero-input
|
|
/// assertion semantics. In builds where assertions are disabled, zero
|
|
/// still produces the same result as the original shift loop.
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_rust_highbit64(value: u64) -> c_uint {
|
|
debug_assert!(value != 0);
|
|
if value == 0 {
|
|
return 0;
|
|
}
|
|
(u64::BITS - 1 - value.leading_zeros()) as c_uint
|
|
}
|
|
|
|
#[inline]
|
|
fn cycle_log(hash_log: c_uint, strategy: c_int) -> c_uint {
|
|
hash_log.wrapping_sub(c_uint::from(strategy >= ZSTD_BTLAZY2))
|
|
}
|
|
|
|
/// Rust scalar ABI for the `ZSTD_cycleLog()` policy in `programs/fileio.c`.
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_rust_cycleLog(hash_log: c_uint, strategy: c_int) -> c_uint {
|
|
cycle_log(hash_log, strategy)
|
|
}
|
|
|
|
fn largest_file_size<I>(sizes: I) -> u64
|
|
where
|
|
I: IntoIterator<Item = u64>,
|
|
{
|
|
let mut largest = 0;
|
|
for size in sizes {
|
|
largest = size.max(largest);
|
|
}
|
|
largest
|
|
}
|
|
|
|
/// Rust implementation of the size scan used by `FIO_getLargestFileSize`.
|
|
///
|
|
/// `UTIL_getFileSize()` remains responsible for the actual stat operation.
|
|
/// In particular, an unknown size is `UTIL_FILESIZE_UNKNOWN` and therefore
|
|
/// wins the max scan exactly as it does in the original C implementation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_getLargestFileSize(
|
|
file_names: *const *const c_char,
|
|
nb_files: c_uint,
|
|
) -> u64 {
|
|
largest_file_size(
|
|
(0..nb_files as usize)
|
|
.map(|index| unsafe { crate::util::UTIL_getFileSize(*file_names.add(index)) }),
|
|
)
|
|
}
|
|
|
|
/// Fill a C-compatible input buffer through an output pointer.
|
|
///
|
|
/// The pointer form avoids relying on a cross-language struct return ABI while
|
|
/// retaining the public `ZSTD_inBuffer` field order and values.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_setInBuffer(
|
|
output: *mut FIO_inBuffer,
|
|
buf: *const c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
) {
|
|
unsafe {
|
|
output.write(FIO_inBuffer {
|
|
src: buf,
|
|
size,
|
|
pos,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Fill a C-compatible output buffer through an output pointer.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_setOutBuffer(
|
|
output: *mut FIO_outBuffer,
|
|
buf: *mut c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
) {
|
|
unsafe {
|
|
output.write(FIO_outBuffer {
|
|
dst: buf,
|
|
size,
|
|
pos,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Aggregate two file-info records through an output pointer to avoid a
|
|
/// cross-language struct return ABI.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_addFInfo(
|
|
output: *mut FIO_fileInfo_t,
|
|
fi1: *const FIO_fileInfo_t,
|
|
fi2: *const FIO_fileInfo_t,
|
|
) {
|
|
let fi1 = unsafe { &*fi1 };
|
|
let fi2 = unsafe { &*fi2 };
|
|
let mut total = FIO_fileInfo_t {
|
|
decompressedSize: 0,
|
|
compressedSize: 0,
|
|
windowSize: 0,
|
|
numActualFrames: 0,
|
|
numSkippableFrames: 0,
|
|
decompUnavailable: 0,
|
|
usesCheck: 0,
|
|
checksum: [0; 4],
|
|
nbFiles: 0,
|
|
dictID: 0,
|
|
};
|
|
|
|
total.numActualFrames = fi1.numActualFrames.wrapping_add(fi2.numActualFrames);
|
|
total.numSkippableFrames = fi1.numSkippableFrames.wrapping_add(fi2.numSkippableFrames);
|
|
total.compressedSize = fi1.compressedSize.wrapping_add(fi2.compressedSize);
|
|
total.decompressedSize = fi1.decompressedSize.wrapping_add(fi2.decompressedSize);
|
|
total.decompUnavailable = fi1.decompUnavailable | fi2.decompUnavailable;
|
|
total.usesCheck = fi1.usesCheck & fi2.usesCheck;
|
|
total.nbFiles = fi1.nbFiles.wrapping_add(fi2.nbFiles);
|
|
|
|
unsafe { output.write(total) };
|
|
}
|
|
|
|
#[inline]
|
|
fn lz4_block_size_from_block_id(id: c_int) -> c_int {
|
|
1 << (8 + 2 * id)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn FIO_rust_LZ4_GetBlockSize_FromBlockId(id: c_int) -> c_int {
|
|
lz4_block_size_from_block_id(id)
|
|
}
|
|
|
|
/// Build the compressed destination name used by the multi-file CLI path.
|
|
///
|
|
/// This deliberately keeps the original static allocation contract: callers
|
|
/// must consume the returned pointer before the next call, and the helper is
|
|
/// not thread-safe. The output-directory basename construction is shared
|
|
/// with `UTIL_createFilenameFromOutDir`, while suffix assembly remains here.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_determineCompressedName(
|
|
src_file_name: *const c_char,
|
|
out_dir_name: *const c_char,
|
|
suffix: *const c_char,
|
|
) -> *const c_char {
|
|
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
|
|
let suffix_bytes = unsafe { CStr::from_ptr(suffix).to_bytes() };
|
|
|
|
if source == b"/*stdin*\\" {
|
|
return STDOUT_MARK.as_ptr().cast();
|
|
}
|
|
|
|
let mut out_dir_filename = ptr::null_mut();
|
|
let source_bytes = if out_dir_name.is_null() {
|
|
source
|
|
} else {
|
|
out_dir_filename = unsafe {
|
|
crate::util::UTIL_createFilenameFromOutDir(
|
|
src_file_name,
|
|
out_dir_name,
|
|
suffix_bytes.len(),
|
|
)
|
|
};
|
|
assert!(!out_dir_filename.is_null());
|
|
unsafe { CStr::from_ptr(out_dir_filename).to_bytes() }
|
|
};
|
|
|
|
let source_len = source_bytes.len();
|
|
let suffix_len = suffix_bytes.len();
|
|
let required = source_len.wrapping_add(suffix_len).wrapping_add(1);
|
|
|
|
unsafe {
|
|
let capacity = ptr::addr_of_mut!(COMPRESSED_NAME_CAPACITY);
|
|
let buffer = ptr::addr_of_mut!(COMPRESSED_NAME_BUFFER);
|
|
if capacity.read() <= required {
|
|
libc::free(buffer.read().cast::<c_void>());
|
|
let new_capacity = source_len.wrapping_add(suffix_len).wrapping_add(30);
|
|
let new_buffer = libc::malloc(new_capacity).cast::<c_char>();
|
|
if new_buffer.is_null() {
|
|
throw(30, "Allocation error : not enough memory");
|
|
}
|
|
capacity.write(new_capacity);
|
|
buffer.write(new_buffer);
|
|
}
|
|
|
|
let destination = buffer.read();
|
|
assert!(!destination.is_null());
|
|
ptr::copy_nonoverlapping(source_bytes.as_ptr(), destination.cast(), source_len);
|
|
ptr::copy_nonoverlapping(
|
|
suffix_bytes.as_ptr(),
|
|
destination.add(source_len).cast(),
|
|
suffix_len + 1,
|
|
);
|
|
if !out_dir_filename.is_null() {
|
|
libc::free(out_dir_filename.cast::<c_void>());
|
|
}
|
|
destination.cast()
|
|
}
|
|
}
|
|
|
|
fn display_unknown_suffix(source: &[u8], suffix_list: &[u8]) {
|
|
let enabled = unsafe { (*display_prefs()).displayLevel >= 1 };
|
|
if !enabled {
|
|
return;
|
|
}
|
|
|
|
let mut stderr = io::stderr().lock();
|
|
let _ = stderr.write_all(b"zstd: ");
|
|
let _ = stderr.write_all(source);
|
|
let _ = stderr.write_all(b": unknown suffix (");
|
|
let _ = stderr.write_all(suffix_list);
|
|
let _ = stderr.write_all(
|
|
b" expected). Can't derive the output file name. Specify it with -o dstFileName. Ignoring.\n",
|
|
);
|
|
}
|
|
|
|
/// Build the decompressed destination name used by the multi-file CLI path.
|
|
///
|
|
/// The suffix table and its display string come from C because both are
|
|
/// selected by the C build configuration. As with the original helper, the
|
|
/// returned pointer refers to a static buffer and is overwritten by the next
|
|
/// successful call.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_determineDstName(
|
|
src_file_name: *const c_char,
|
|
out_dir_name: *const c_char,
|
|
suffix_list: *const *const c_char,
|
|
suffix_list_str: *const c_char,
|
|
) -> *const c_char {
|
|
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
|
|
let suffix_display = unsafe { CStr::from_ptr(suffix_list_str).to_bytes() };
|
|
|
|
if source == b"/*stdin*\\" {
|
|
return STDOUT_MARK.as_ptr().cast();
|
|
}
|
|
|
|
let Some(src_suffix_start) = source.iter().rposition(|&byte| byte == b'.') else {
|
|
display_unknown_suffix(source, suffix_display);
|
|
return ptr::null();
|
|
};
|
|
let src_suffix = &source[src_suffix_start..];
|
|
let src_suffix_len = src_suffix.len();
|
|
|
|
let mut matched_suffix = ptr::null();
|
|
let mut suffix_ptr = suffix_list;
|
|
unsafe {
|
|
while !suffix_ptr.is_null() {
|
|
let candidate = *suffix_ptr;
|
|
if candidate.is_null() {
|
|
break;
|
|
}
|
|
if CStr::from_ptr(candidate).to_bytes() == src_suffix {
|
|
matched_suffix = candidate;
|
|
break;
|
|
}
|
|
suffix_ptr = suffix_ptr.add(1);
|
|
}
|
|
}
|
|
|
|
if source.len() <= src_suffix_len || matched_suffix.is_null() {
|
|
display_unknown_suffix(source, suffix_display);
|
|
return ptr::null();
|
|
}
|
|
|
|
let matched_suffix_bytes = unsafe { CStr::from_ptr(matched_suffix).to_bytes() };
|
|
let dst_suffix: &[u8] = if matched_suffix_bytes.get(1) == Some(&b't') {
|
|
b".tar\0"
|
|
} else {
|
|
b"\0"
|
|
};
|
|
let dst_suffix_len = dst_suffix.len() - 1;
|
|
|
|
let mut out_dir_filename = ptr::null_mut();
|
|
let source_for_destination = if out_dir_name.is_null() {
|
|
source
|
|
} else {
|
|
out_dir_filename =
|
|
unsafe { crate::util::UTIL_createFilenameFromOutDir(src_file_name, out_dir_name, 0) };
|
|
assert!(!out_dir_filename.is_null());
|
|
unsafe { CStr::from_ptr(out_dir_filename).to_bytes() }
|
|
};
|
|
let source_len = source_for_destination.len();
|
|
|
|
unsafe {
|
|
let capacity = ptr::addr_of_mut!(DESTINATION_NAME_CAPACITY);
|
|
let buffer = ptr::addr_of_mut!(DESTINATION_NAME_BUFFER);
|
|
if capacity.read().wrapping_add(src_suffix_len)
|
|
<= source_len.wrapping_add(1).wrapping_add(dst_suffix_len)
|
|
{
|
|
libc::free(buffer.read().cast::<c_void>());
|
|
let new_capacity = source_len.wrapping_add(20);
|
|
let new_buffer = libc::malloc(new_capacity).cast::<c_char>();
|
|
if new_buffer.is_null() {
|
|
throw(
|
|
74,
|
|
&format!(
|
|
"{} : not enough memory for dstFileName",
|
|
std::io::Error::last_os_error()
|
|
),
|
|
);
|
|
}
|
|
capacity.write(new_capacity);
|
|
buffer.write(new_buffer);
|
|
}
|
|
|
|
let destination = buffer.read();
|
|
assert!(!destination.is_null());
|
|
let destination_end = source_len - src_suffix_len;
|
|
ptr::copy_nonoverlapping(
|
|
source_for_destination.as_ptr(),
|
|
destination.cast::<u8>(),
|
|
destination_end,
|
|
);
|
|
ptr::copy_nonoverlapping(
|
|
dst_suffix.as_ptr(),
|
|
destination.add(destination_end).cast::<u8>(),
|
|
dst_suffix.len(),
|
|
);
|
|
if !out_dir_filename.is_null() {
|
|
libc::free(out_dir_filename.cast::<c_void>());
|
|
}
|
|
destination.cast()
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn filename_path_separator() -> u8 {
|
|
if cfg!(windows) {
|
|
b'\\'
|
|
} else {
|
|
b'/'
|
|
}
|
|
}
|
|
|
|
/// Return the byte-level basename used for collision checks.
|
|
///
|
|
/// A null entry has no filename and is ignored by the exported checker. It
|
|
/// still maps to an empty key here so the key operation is safe to exercise
|
|
/// independently and never passes a null pointer to `CStr::from_ptr`.
|
|
unsafe fn filename_collision_key<'a>(filename: *const c_char) -> &'a [u8] {
|
|
if filename.is_null() {
|
|
return &[];
|
|
}
|
|
let bytes = unsafe { CStr::from_ptr(filename).to_bytes() };
|
|
match bytes
|
|
.iter()
|
|
.rposition(|&byte| byte == filename_path_separator())
|
|
{
|
|
Some(index) => &bytes[index + 1..],
|
|
None => bytes,
|
|
}
|
|
}
|
|
|
|
unsafe fn same_filename_collision_key(left: *const c_char, right: *const c_char) -> bool {
|
|
unsafe { filename_collision_key(left) == filename_collision_key(right) }
|
|
}
|
|
|
|
fn display_filename_collision_warning(filename: *const c_char) {
|
|
let enabled = unsafe { (*display_prefs()).displayLevel >= 2 };
|
|
if !enabled {
|
|
return;
|
|
}
|
|
|
|
let basename = unsafe { filename_collision_key(filename) };
|
|
let mut stderr = io::stderr().lock();
|
|
let _ = stderr.write_all(b"WARNING: Two files have same filename: ");
|
|
let _ = stderr.write_all(basename);
|
|
let _ = stderr.write_all(b"\n");
|
|
}
|
|
|
|
/// Rust implementation of the filename collision checker used by
|
|
/// `FIO_checkFilenameCollisions` in `programs/fileio.c`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn FIO_rust_checkFilenameCollisions(
|
|
filename_table: *const *const c_char,
|
|
nb_files: c_uint,
|
|
) -> c_int {
|
|
if filename_table.is_null() || nb_files == 0 {
|
|
return 0;
|
|
}
|
|
|
|
let mut filenames = Vec::<*const c_char>::new();
|
|
if filenames.try_reserve_exact(nb_files as usize).is_err() {
|
|
display(1, "Allocation error during filename collision checking \n");
|
|
return 1;
|
|
}
|
|
|
|
for index in 0..nb_files as usize {
|
|
let filename = unsafe { *filename_table.add(index) };
|
|
if !filename.is_null() {
|
|
filenames.push(filename);
|
|
}
|
|
}
|
|
|
|
filenames.sort_unstable_by(|left, right| unsafe {
|
|
filename_collision_key(*left).cmp(filename_collision_key(*right))
|
|
});
|
|
for pair in filenames.windows(2) {
|
|
if unsafe { same_filename_collision_key(pair[0], pair[1]) } {
|
|
display_filename_collision_warning(pair[0]);
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::ffi::CString;
|
|
use std::fs;
|
|
use std::mem::{align_of, offset_of, size_of};
|
|
|
|
fn temporary_file_path(name: &str) -> std::path::PathBuf {
|
|
std::env::temp_dir().join(format!("zstd-fileio-prefs-{}-{name}", std::process::id()))
|
|
}
|
|
|
|
fn prefs_with_mem_limit(mem_limit: c_uint) -> FIO_prefs_t {
|
|
let mut prefs = unsafe { std::mem::zeroed::<FIO_prefs_t>() };
|
|
prefs.memLimit = mem_limit;
|
|
prefs
|
|
}
|
|
|
|
#[test]
|
|
fn patch_mem_limit_preserves_existing_limit() {
|
|
let mut prefs = prefs_with_mem_limit(4096);
|
|
let status = unsafe { FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, 1024, 2048) };
|
|
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
|
|
assert_eq!(prefs.memLimit, 4096);
|
|
}
|
|
|
|
#[test]
|
|
fn patch_mem_limit_uses_dictionary_and_source_maxima() {
|
|
let mut prefs = prefs_with_mem_limit(128);
|
|
let status = unsafe { FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, 4096, 1024) };
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
|
|
assert_eq!(prefs.memLimit, 4096);
|
|
|
|
let mut prefs = prefs_with_mem_limit(128);
|
|
let status = unsafe { FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, 1024, 8192) };
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
|
|
assert_eq!(prefs.memLimit, 8192);
|
|
}
|
|
|
|
#[test]
|
|
fn patch_mem_limit_rejects_unknown_sizes_without_update() {
|
|
for (dict_size, max_src_file_size) in
|
|
[(UTIL_FILESIZE_UNKNOWN, 0), (0, UTIL_FILESIZE_UNKNOWN)]
|
|
{
|
|
let mut prefs = prefs_with_mem_limit(4096);
|
|
let status = unsafe {
|
|
FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, dict_size, max_src_file_size)
|
|
};
|
|
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_UNKNOWN_SIZE);
|
|
assert_eq!(prefs.memLimit, 4096);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn patch_mem_limit_rejects_sizes_over_the_window_without_update() {
|
|
let mut prefs = prefs_with_mem_limit(4096);
|
|
let status = unsafe {
|
|
FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, (1u64 << ZSTD_WINDOWLOG_MAX) + 1, 0)
|
|
};
|
|
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_TOO_LARGE);
|
|
assert_eq!(prefs.memLimit, 4096);
|
|
}
|
|
|
|
#[test]
|
|
fn patch_mem_limit_updates_on_success() {
|
|
let mut prefs = prefs_with_mem_limit(128);
|
|
let status = unsafe { FIO_rust_adjustMemLimitForPatchFromMode(&mut prefs, 1024, 8192) };
|
|
|
|
assert_eq!(status, FIO_PATCH_MEM_LIMIT_SUCCESS);
|
|
assert_eq!(prefs.memLimit, 8192);
|
|
}
|
|
|
|
#[test]
|
|
fn largest_file_size_scan_handles_empty_input() {
|
|
assert_eq!(unsafe { FIO_rust_getLargestFileSize(ptr::null(), 0) }, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn largest_file_size_scan_handles_ordinary_files() {
|
|
let smaller_path = temporary_file_path("smaller");
|
|
let larger_path = temporary_file_path("larger");
|
|
fs::write(&smaller_path, b"small").unwrap();
|
|
fs::write(&larger_path, b"larger file").unwrap();
|
|
let smaller = CString::new(smaller_path.to_string_lossy().as_bytes()).unwrap();
|
|
let larger = CString::new(larger_path.to_string_lossy().as_bytes()).unwrap();
|
|
let names = [smaller.as_ptr(), larger.as_ptr()];
|
|
|
|
assert_eq!(
|
|
unsafe { FIO_rust_getLargestFileSize(names.as_ptr(), names.len() as c_uint) },
|
|
11
|
|
);
|
|
|
|
fs::remove_file(smaller_path).unwrap();
|
|
fs::remove_file(larger_path).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn largest_file_size_scan_preserves_large_values_and_errors() {
|
|
assert_eq!(largest_file_size([0, 1u64 << 63, 7]), 1u64 << 63);
|
|
assert_eq!(FIO_rust_highbit64(1u64 << 63), 63);
|
|
assert_eq!(FIO_rust_highbit64(u64::MAX), 63);
|
|
|
|
let missing_path = temporary_file_path("missing");
|
|
let missing = CString::new(missing_path.to_string_lossy().as_bytes()).unwrap();
|
|
let names = [missing.as_ptr()];
|
|
assert_eq!(
|
|
unsafe { FIO_rust_getLargestFileSize(names.as_ptr(), 1) },
|
|
UTIL_FILESIZE_UNKNOWN
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cycle_log_keeps_lower_strategies_unchanged() {
|
|
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 - 1), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn cycle_log_subtracts_at_btlazy2_threshold() {
|
|
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2), 19);
|
|
}
|
|
|
|
#[test]
|
|
fn cycle_log_subtracts_for_upper_strategies() {
|
|
assert_eq!(FIO_rust_cycleLog(20, ZSTD_BTLAZY2 + 3), 19);
|
|
}
|
|
|
|
#[test]
|
|
fn buffer_shims_preserve_fields_and_c_layout() {
|
|
let input_word = size_of::<*const c_void>();
|
|
let output_word = size_of::<*mut c_void>();
|
|
|
|
assert_eq!(offset_of!(FIO_inBuffer, src), 0);
|
|
assert_eq!(offset_of!(FIO_inBuffer, size), input_word);
|
|
assert_eq!(
|
|
offset_of!(FIO_inBuffer, pos),
|
|
input_word + size_of::<usize>()
|
|
);
|
|
assert_eq!(
|
|
size_of::<FIO_inBuffer>(),
|
|
input_word + 2 * size_of::<usize>()
|
|
);
|
|
assert_eq!(offset_of!(FIO_outBuffer, dst), 0);
|
|
assert_eq!(offset_of!(FIO_outBuffer, size), output_word);
|
|
assert_eq!(
|
|
offset_of!(FIO_outBuffer, pos),
|
|
output_word + size_of::<usize>()
|
|
);
|
|
assert_eq!(
|
|
size_of::<FIO_outBuffer>(),
|
|
output_word + 2 * size_of::<usize>()
|
|
);
|
|
|
|
let source = [1u8, 2, 3, 4];
|
|
let mut destination = [0u8; 8];
|
|
let mut input = FIO_inBuffer {
|
|
src: ptr::null(),
|
|
size: 0,
|
|
pos: 0,
|
|
};
|
|
let mut output = FIO_outBuffer {
|
|
dst: ptr::null_mut(),
|
|
size: 0,
|
|
pos: 0,
|
|
};
|
|
|
|
unsafe {
|
|
FIO_rust_setInBuffer(
|
|
&mut input,
|
|
source.as_ptr().cast::<c_void>(),
|
|
source.len(),
|
|
1,
|
|
);
|
|
FIO_rust_setOutBuffer(
|
|
&mut output,
|
|
destination.as_mut_ptr().cast::<c_void>(),
|
|
destination.len(),
|
|
2,
|
|
);
|
|
}
|
|
|
|
assert_eq!(input.src, source.as_ptr().cast::<c_void>());
|
|
assert_eq!(input.size, source.len());
|
|
assert_eq!(input.pos, 1);
|
|
assert_eq!(output.dst, destination.as_mut_ptr().cast::<c_void>());
|
|
assert_eq!(output.size, destination.len());
|
|
assert_eq!(output.pos, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn file_info_aggregation_preserves_totals_flags_and_zero_fields() {
|
|
let first = FIO_fileInfo_t {
|
|
decompressedSize: 100,
|
|
compressedSize: 40,
|
|
windowSize: 1,
|
|
numActualFrames: 2,
|
|
numSkippableFrames: 3,
|
|
decompUnavailable: 0,
|
|
usesCheck: 1,
|
|
checksum: [1, 2, 3, 4],
|
|
nbFiles: 5,
|
|
dictID: 6,
|
|
};
|
|
let second = FIO_fileInfo_t {
|
|
decompressedSize: 200,
|
|
compressedSize: 60,
|
|
windowSize: 2,
|
|
numActualFrames: 7,
|
|
numSkippableFrames: 11,
|
|
decompUnavailable: 1,
|
|
usesCheck: 0,
|
|
checksum: [5, 6, 7, 8],
|
|
nbFiles: 13,
|
|
dictID: 14,
|
|
};
|
|
let mut total = FIO_fileInfo_t {
|
|
decompressedSize: u64::MAX,
|
|
compressedSize: u64::MAX,
|
|
windowSize: u64::MAX,
|
|
numActualFrames: -1,
|
|
numSkippableFrames: -1,
|
|
decompUnavailable: -1,
|
|
usesCheck: -1,
|
|
checksum: [u8::MAX; 4],
|
|
nbFiles: u32::MAX,
|
|
dictID: c_uint::MAX,
|
|
};
|
|
|
|
unsafe { FIO_rust_addFInfo(&mut total, &first, &second) };
|
|
|
|
assert_eq!(total.decompressedSize, 300);
|
|
assert_eq!(total.compressedSize, 100);
|
|
assert_eq!(total.windowSize, 0);
|
|
assert_eq!(total.numActualFrames, 9);
|
|
assert_eq!(total.numSkippableFrames, 14);
|
|
assert_eq!(total.decompUnavailable, 1);
|
|
assert_eq!(total.usesCheck, 0);
|
|
assert_eq!(total.checksum, [0; 4]);
|
|
assert_eq!(total.nbFiles, 18);
|
|
assert_eq!(total.dictID, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn file_info_layout_matches_private_c_record() {
|
|
let word = size_of::<u64>();
|
|
let int = size_of::<c_int>();
|
|
|
|
assert_eq!(align_of::<FIO_fileInfo_t>(), align_of::<u64>());
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, decompressedSize), 0);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, compressedSize), word);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, windowSize), 2 * word);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, numActualFrames), 3 * word);
|
|
assert_eq!(
|
|
offset_of!(FIO_fileInfo_t, numSkippableFrames),
|
|
3 * word + int
|
|
);
|
|
assert_eq!(
|
|
offset_of!(FIO_fileInfo_t, decompUnavailable),
|
|
3 * word + 2 * int
|
|
);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, usesCheck), 3 * word + 3 * int);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, checksum), 3 * word + 4 * int);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, nbFiles), 3 * word + 4 * int + 4);
|
|
assert_eq!(offset_of!(FIO_fileInfo_t, dictID), 3 * word + 4 * int + 8);
|
|
let expected_size = if size_of::<usize>() == 8 {
|
|
7 * word
|
|
} else {
|
|
13 * size_of::<u32>()
|
|
};
|
|
assert_eq!(size_of::<FIO_fileInfo_t>(), expected_size);
|
|
}
|
|
|
|
#[test]
|
|
fn lz4_block_size_shim_preserves_the_block_id_formula() {
|
|
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(0), 1 << 8);
|
|
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(1), 1 << 10);
|
|
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(3), 1 << 14);
|
|
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(4), 1 << 16);
|
|
}
|
|
|
|
#[test]
|
|
fn compressed_filename_shim_preserves_sentinels_suffixes_and_output_dirs() {
|
|
let source = CString::new("input/nested/file").unwrap();
|
|
let suffix = CString::new(".zst").unwrap();
|
|
let output_dir = CString::new("out").unwrap();
|
|
|
|
unsafe {
|
|
let name =
|
|
FIO_rust_determineCompressedName(source.as_ptr(), ptr::null(), suffix.as_ptr());
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"input/nested/file.zst");
|
|
|
|
let name = FIO_rust_determineCompressedName(
|
|
source.as_ptr(),
|
|
output_dir.as_ptr(),
|
|
suffix.as_ptr(),
|
|
);
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/file.zst");
|
|
|
|
let stdin = CString::new("/*stdin*\\").unwrap();
|
|
let name =
|
|
FIO_rust_determineCompressedName(stdin.as_ptr(), ptr::null(), suffix.as_ptr());
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"/*stdout*\\");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn destination_filename_shim_preserves_suffixes_tar_names_and_sentinels() {
|
|
let zst = CString::new(".zst").unwrap();
|
|
let tzst = CString::new(".tzst").unwrap();
|
|
let gz = CString::new(".gz").unwrap();
|
|
let tgz = CString::new(".tgz").unwrap();
|
|
let suffixes = [
|
|
zst.as_ptr(),
|
|
tzst.as_ptr(),
|
|
gz.as_ptr(),
|
|
tgz.as_ptr(),
|
|
ptr::null(),
|
|
];
|
|
let suffixes_display = CString::new(".zst/.tzst/.gz/.tgz").unwrap();
|
|
let source = CString::new("input/nested/file.zst").unwrap();
|
|
let tar_source = CString::new("input/nested/archive.tzst").unwrap();
|
|
let tar_gzip_source = CString::new("archive.tgz").unwrap();
|
|
let output_dir = CString::new("out").unwrap();
|
|
let stdin = CString::new("/*stdin*\\").unwrap();
|
|
|
|
unsafe {
|
|
let name = FIO_rust_determineDstName(
|
|
source.as_ptr(),
|
|
ptr::null(),
|
|
suffixes.as_ptr(),
|
|
suffixes_display.as_ptr(),
|
|
);
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"input/nested/file");
|
|
|
|
let name = FIO_rust_determineDstName(
|
|
tar_source.as_ptr(),
|
|
output_dir.as_ptr(),
|
|
suffixes.as_ptr(),
|
|
suffixes_display.as_ptr(),
|
|
);
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/archive.tar");
|
|
|
|
let name = FIO_rust_determineDstName(
|
|
tar_gzip_source.as_ptr(),
|
|
ptr::null(),
|
|
suffixes.as_ptr(),
|
|
suffixes_display.as_ptr(),
|
|
);
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"archive.tar");
|
|
|
|
let name = FIO_rust_determineDstName(
|
|
stdin.as_ptr(),
|
|
output_dir.as_ptr(),
|
|
suffixes.as_ptr(),
|
|
suffixes_display.as_ptr(),
|
|
);
|
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"/*stdout*\\");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn destination_filename_shim_rejects_unknown_suffix() {
|
|
let zst = CString::new(".zst").unwrap();
|
|
let suffixes = [zst.as_ptr(), ptr::null()];
|
|
let suffixes_display = CString::new(".zst").unwrap();
|
|
let source = CString::new("input/file.zip").unwrap();
|
|
|
|
unsafe {
|
|
assert!(FIO_rust_determineDstName(
|
|
source.as_ptr(),
|
|
ptr::null(),
|
|
suffixes.as_ptr(),
|
|
suffixes_display.as_ptr(),
|
|
)
|
|
.is_null());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn c_layouts_match_the_headers() {
|
|
let word = size_of::<usize>();
|
|
let int = size_of::<c_int>();
|
|
|
|
assert_eq!(align_of::<FIO_display_prefs_t>(), align_of::<c_int>());
|
|
assert_eq!(size_of::<FIO_display_prefs_t>(), 2 * int);
|
|
|
|
assert_eq!(align_of::<FIO_prefs_t>(), align_of::<usize>());
|
|
assert_eq!(offset_of!(FIO_prefs_t, compressionType), 0);
|
|
assert_eq!(offset_of!(FIO_prefs_t, sparseFileSupport), int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, ldmHashRateLog), 15 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, streamSrcSize), 16 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, targetCBlockSize), 16 * int + word);
|
|
let tail = 16 * int + 2 * word;
|
|
assert_eq!(offset_of!(FIO_prefs_t, srcSizeHint), tail);
|
|
assert_eq!(offset_of!(FIO_prefs_t, testMode), tail + int);
|
|
assert_eq!(
|
|
offset_of!(FIO_prefs_t, literalCompressionMode),
|
|
tail + 2 * int
|
|
);
|
|
assert_eq!(offset_of!(FIO_prefs_t, removeSrcFile), tail + 3 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, overwrite), tail + 4 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, asyncIO), tail + 5 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, memLimit), tail + 6 * int);
|
|
assert_eq!(offset_of!(FIO_prefs_t, mmapDict), tail + 13 * int);
|
|
assert_eq!(size_of::<FIO_prefs_t>(), tail + 14 * int);
|
|
|
|
assert_eq!(align_of::<FIO_ctx_t>(), align_of::<usize>());
|
|
assert_eq!(offset_of!(FIO_ctx_t, currFileIdx), 3 * int);
|
|
let total_bytes = (5 * int).next_multiple_of(word);
|
|
assert_eq!(offset_of!(FIO_ctx_t, totalBytesInput), total_bytes);
|
|
assert_eq!(offset_of!(FIO_ctx_t, totalBytesOutput), total_bytes + word);
|
|
assert_eq!(size_of::<FIO_ctx_t>(), total_bytes + 2 * word);
|
|
}
|
|
|
|
#[test]
|
|
fn filename_collision_checker_handles_null_and_empty_inputs() {
|
|
let empty = CString::new("").unwrap();
|
|
let names = [ptr::null(), empty.as_ptr()];
|
|
|
|
unsafe {
|
|
assert_eq!(FIO_rust_checkFilenameCollisions(ptr::null(), 0), 0);
|
|
assert_eq!(FIO_rust_checkFilenameCollisions(names.as_ptr(), 2), 0);
|
|
assert!(same_filename_collision_key(ptr::null(), empty.as_ptr()));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn filename_collision_keys_compare_basename_bytes_without_utf8_conversion() {
|
|
let first = CString::new(b"left/\xffname".to_vec()).unwrap();
|
|
let duplicate = CString::new(b"right/\xffname".to_vec()).unwrap();
|
|
let different = CString::new(b"right/\xfename".to_vec()).unwrap();
|
|
let names = [first.as_ptr(), duplicate.as_ptr()];
|
|
|
|
unsafe {
|
|
assert_eq!(filename_collision_key(first.as_ptr()), b"\xffname");
|
|
assert!(same_filename_collision_key(
|
|
first.as_ptr(),
|
|
duplicate.as_ptr()
|
|
));
|
|
assert!(!same_filename_collision_key(
|
|
first.as_ptr(),
|
|
different.as_ptr()
|
|
));
|
|
assert_eq!(FIO_rust_checkFilenameCollisions(names.as_ptr(), 2), 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn preference_defaults_match_fileio_c() {
|
|
let prefs = unsafe { FIO_createPreferences() };
|
|
assert!(!prefs.is_null());
|
|
let prefs = unsafe { &*prefs };
|
|
|
|
assert_eq!(prefs.compressionType, 0);
|
|
assert_eq!(prefs.overwrite, 0);
|
|
assert_eq!(prefs.sparseFileSupport, ZSTD_SPARSE_DEFAULT);
|
|
assert_eq!(prefs.dictIDFlag, 1);
|
|
assert_eq!(prefs.checksumFlag, 1);
|
|
assert_eq!(prefs.removeSrcFile, 0);
|
|
assert_eq!(prefs.memLimit, 0);
|
|
assert_eq!(prefs.nbWorkers, 1);
|
|
assert_eq!(prefs.blockSize, 0);
|
|
assert_eq!(prefs.overlapLog, FIO_OVERLAP_LOG_NOTSET);
|
|
assert_eq!(prefs.adaptiveMode, 0);
|
|
assert_eq!(prefs.rsyncable, 0);
|
|
assert_eq!(prefs.minAdaptLevel, -50);
|
|
assert_eq!(prefs.maxAdaptLevel, 22);
|
|
assert_eq!(prefs.ldmFlag, 0);
|
|
assert_eq!(prefs.ldmHashLog, 0);
|
|
assert_eq!(prefs.ldmMinMatch, 0);
|
|
assert_eq!(prefs.ldmBucketSizeLog, FIO_LDM_PARAM_NOTSET);
|
|
assert_eq!(prefs.ldmHashRateLog, FIO_LDM_PARAM_NOTSET);
|
|
assert_eq!(prefs.streamSrcSize, 0);
|
|
assert_eq!(prefs.targetCBlockSize, 0);
|
|
assert_eq!(prefs.srcSizeHint, 0);
|
|
assert_eq!(prefs.testMode, 0);
|
|
assert_eq!(prefs.literalCompressionMode, 0);
|
|
assert_eq!(prefs.excludeCompressedFiles, 0);
|
|
assert_eq!(prefs.allowBlockDevices, 0);
|
|
assert_eq!(prefs.asyncIO, 0);
|
|
assert_eq!(prefs.passThrough, -1);
|
|
|
|
unsafe { FIO_freePreferences(prefs as *const FIO_prefs_t as *mut FIO_prefs_t) };
|
|
}
|
|
|
|
#[test]
|
|
fn context_defaults_and_setters_match_fileio_c() {
|
|
let ctx = unsafe { FIO_createContext() };
|
|
assert!(!ctx.is_null());
|
|
unsafe {
|
|
assert_eq!((*ctx).currFileIdx, 0);
|
|
assert_eq!((*ctx).hasStdinInput, 0);
|
|
assert_eq!((*ctx).hasStdoutOutput, 0);
|
|
assert_eq!((*ctx).nbFilesTotal, 1);
|
|
assert_eq!((*ctx).nbFilesProcessed, 0);
|
|
assert_eq!((*ctx).totalBytesInput, 0);
|
|
assert_eq!((*ctx).totalBytesOutput, 0);
|
|
|
|
FIO_setNbFilesTotal(ctx, 7);
|
|
FIO_setHasStdinInput(ctx, -1);
|
|
FIO_setHasStdoutOutput(ctx, -2);
|
|
assert_eq!((*ctx).nbFilesTotal, 7);
|
|
assert_eq!((*ctx).hasStdinInput, 1);
|
|
assert_eq!((*ctx).hasStdoutOutput, -2);
|
|
}
|
|
|
|
let names = [c"input".as_ptr(), c"/*stdin*\\".as_ptr()];
|
|
let table = FileNamesTable {
|
|
fileNames: names.as_ptr().cast_mut(),
|
|
buf: ptr::null_mut(),
|
|
tableSize: names.len(),
|
|
tableCapacity: names.len(),
|
|
};
|
|
unsafe { FIO_determineHasStdinInput(ctx, &table) };
|
|
assert_eq!(unsafe { (*ctx).hasStdinInput }, 1);
|
|
unsafe { FIO_freeContext(ctx) };
|
|
}
|
|
|
|
fn summary_context(nb_files_total: c_int, nb_files_processed: c_int) -> FIO_ctx_t {
|
|
FIO_ctx_t {
|
|
nbFilesTotal: nb_files_total,
|
|
hasStdinInput: 0,
|
|
hasStdoutOutput: 0,
|
|
currFileIdx: 0,
|
|
nbFilesProcessed: nb_files_processed,
|
|
totalBytesInput: 0,
|
|
totalBytesOutput: 0,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn file_summary_display_matches_file_count_and_display_level() {
|
|
let mut ctx = summary_context(1, 0);
|
|
|
|
for display_level in [0, 2] {
|
|
FIO_setNotificationLevel(display_level);
|
|
assert_eq!(unsafe { FIO_rust_shouldDisplayFileSummary(&ctx) }, 1);
|
|
}
|
|
|
|
ctx.nbFilesTotal = 2;
|
|
for display_level in [0, 2] {
|
|
FIO_setNotificationLevel(display_level);
|
|
assert_eq!(unsafe { FIO_rust_shouldDisplayFileSummary(&ctx) }, 0);
|
|
}
|
|
|
|
FIO_setNotificationLevel(3);
|
|
assert_eq!(unsafe { FIO_rust_shouldDisplayFileSummary(&ctx) }, 1);
|
|
|
|
FIO_setNotificationLevel(2);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_file_summary_display_matches_processed_count_and_file_count() {
|
|
let mut ctx = summary_context(2, 0);
|
|
|
|
for display_level in [0, 2, 3] {
|
|
FIO_setNotificationLevel(display_level);
|
|
assert_eq!(
|
|
unsafe { FIO_rust_shouldDisplayMultipleFileSummary(&ctx) },
|
|
0
|
|
);
|
|
}
|
|
|
|
ctx.nbFilesProcessed = 1;
|
|
for display_level in [0, 2, 3] {
|
|
FIO_setNotificationLevel(display_level);
|
|
assert_eq!(
|
|
unsafe { FIO_rust_shouldDisplayMultipleFileSummary(&ctx) },
|
|
1
|
|
);
|
|
}
|
|
|
|
ctx.nbFilesProcessed = 2;
|
|
FIO_setNotificationLevel(0);
|
|
assert_eq!(
|
|
unsafe { FIO_rust_shouldDisplayMultipleFileSummary(&ctx) },
|
|
1
|
|
);
|
|
|
|
ctx.nbFilesTotal = 1;
|
|
assert_eq!(
|
|
unsafe { FIO_rust_shouldDisplayMultipleFileSummary(&ctx) },
|
|
0
|
|
);
|
|
|
|
FIO_setNotificationLevel(2);
|
|
}
|
|
|
|
#[test]
|
|
fn preference_setters_update_the_c_layout() {
|
|
let prefs = unsafe { FIO_createPreferences() };
|
|
unsafe {
|
|
FIO_setCompressionType(prefs, 3);
|
|
FIO_overwriteMode(prefs);
|
|
FIO_setSparseWrite(prefs, 2);
|
|
FIO_setDictIDFlag(prefs, 0);
|
|
FIO_setChecksumFlag(prefs, 2);
|
|
FIO_setRemoveSrcFile(prefs, -1);
|
|
FIO_setMemLimit(prefs, 123);
|
|
#[cfg(feature = "compression")]
|
|
FIO_setNbWorkers(prefs, 2);
|
|
FIO_setExcludeCompressedFile(prefs, 1);
|
|
FIO_setAllowBlockDevices(prefs, 1);
|
|
FIO_setUseRowMatchFinder(prefs, 2);
|
|
FIO_setBlockSize(prefs, 4096);
|
|
FIO_setOverlapLog(prefs, 5);
|
|
FIO_setAdaptiveMode(prefs, 1);
|
|
FIO_setRsyncable(prefs, 1);
|
|
FIO_setStreamSrcSize(prefs, 100);
|
|
FIO_setTargetCBlockSize(prefs, 200);
|
|
FIO_setSrcSizeHint(prefs, usize::MAX);
|
|
FIO_setLiteralCompressionMode(prefs, 2);
|
|
FIO_setLdmFlag(prefs, 1);
|
|
FIO_setLdmHashLog(prefs, 6);
|
|
FIO_setLdmMinMatch(prefs, 7);
|
|
FIO_setLdmBucketSizeLog(prefs, 8);
|
|
FIO_setLdmHashRateLog(prefs, 9);
|
|
FIO_setPatchFromMode(prefs, -1);
|
|
FIO_setContentSize(prefs, -1);
|
|
FIO_setPassThroughFlag(prefs, -1);
|
|
FIO_setMMapDict(prefs, 2);
|
|
FIO_setAsyncIOFlag(prefs, 1);
|
|
|
|
#[cfg(feature = "compression")]
|
|
{
|
|
FIO_setAdaptMin(prefs, -40);
|
|
FIO_setAdaptMax(prefs, 18);
|
|
}
|
|
#[cfg(feature = "decompression")]
|
|
FIO_setTestMode(prefs, -1);
|
|
|
|
assert_eq!((*prefs).compressionType, 3);
|
|
assert_eq!((*prefs).overwrite, 1);
|
|
assert_eq!((*prefs).sparseFileSupport, 2);
|
|
assert_eq!((*prefs).dictIDFlag, 0);
|
|
assert_eq!((*prefs).checksumFlag, 2);
|
|
assert_eq!((*prefs).removeSrcFile, 1);
|
|
assert_eq!((*prefs).memLimit, 123);
|
|
#[cfg(feature = "compression")]
|
|
assert_eq!((*prefs).nbWorkers, 2);
|
|
assert_eq!((*prefs).excludeCompressedFiles, 1);
|
|
assert_eq!((*prefs).allowBlockDevices, 1);
|
|
assert_eq!((*prefs).useRowMatchFinder, 2);
|
|
assert_eq!((*prefs).blockSize, 4096);
|
|
assert_eq!((*prefs).overlapLog, 5);
|
|
assert_eq!((*prefs).adaptiveMode, 1);
|
|
assert_eq!((*prefs).rsyncable, 1);
|
|
assert_eq!((*prefs).streamSrcSize, 100);
|
|
assert_eq!((*prefs).targetCBlockSize, 200);
|
|
assert_eq!((*prefs).srcSizeHint, c_int::MAX);
|
|
assert_eq!((*prefs).literalCompressionMode, 2);
|
|
assert_eq!((*prefs).ldmFlag, 1);
|
|
assert_eq!((*prefs).ldmHashLog, 6);
|
|
assert_eq!((*prefs).ldmMinMatch, 7);
|
|
assert_eq!((*prefs).ldmBucketSizeLog, 8);
|
|
assert_eq!((*prefs).ldmHashRateLog, 9);
|
|
assert_eq!((*prefs).patchFromMode, 1);
|
|
assert_eq!((*prefs).contentSize, 1);
|
|
assert_eq!((*prefs).passThrough, 1);
|
|
assert_eq!((*prefs).mmapDict, 2);
|
|
#[cfg(test)]
|
|
assert_eq!((*prefs).asyncIO, 0);
|
|
#[cfg(feature = "compression")]
|
|
{
|
|
assert_eq!((*prefs).minAdaptLevel, -40);
|
|
assert_eq!((*prefs).maxAdaptLevel, 18);
|
|
}
|
|
#[cfg(feature = "decompression")]
|
|
assert_eq!((*prefs).testMode, 1);
|
|
}
|
|
|
|
FIO_setNotificationLevel(0);
|
|
FIO_setProgressSetting(2);
|
|
unsafe {
|
|
assert_eq!((*display_prefs()).displayLevel, 0);
|
|
assert_eq!((*display_prefs()).progressSetting, 2);
|
|
FIO_freePreferences(prefs);
|
|
}
|
|
}
|
|
}
|