feat(cli): move dictionary release dispatch into Rust

Route FIO_freeDict through the Rust filesystem backend for malloc-backed and
mapped dictionaries. Keep enum validation in C, pair Windows views with their
file handles, support the non-POSIX malloc fallback, and clear every ownership
field so repeated cleanup remains harmless.

Test Plan:
- cargo test --manifest-path rust/cli/Cargo.toml --lib fileio_backend -- --test-threads=1 (25 passed)
- cargo clippy --manifest-path rust/cli/Cargo.toml --lib -- -D warnings
- rustfmt +nightly --edition 2021 rust/src/fileio_backend.rs --check
- make -C programs -j2 zstd
- git diff --check
This commit is contained in:
2026-07-18 17:45:19 +02:00
parent e557549ff4
commit 643cb45d89
2 changed files with 116 additions and 24 deletions
+20 -23
View File
@@ -380,8 +380,11 @@ int FIO_rust_setDictBufferMMap(const char* fileName,
void** buffer,
size_t* mappedSize,
void** dictHandle);
void FIO_rust_munmapDict(void** buffer, size_t* bufferSize, void** dictHandle);
#endif
void FIO_rust_freeDict(int dictBufferType,
void** buffer,
size_t* bufferSize,
void** dictHandle);
int FIO_rust_removeFile(const char* path);
int FIO_rust_passThrough(ReadPoolCtx_t* readCtx, WritePoolCtx_t* writeCtx);
#ifdef ZSTD_LZ4COMPRESS
@@ -656,10 +659,6 @@ static size_t FIO_setDictBufferMalloc(FIO_Dict_t* dict, const char* fileName, FI
}
#if (PLATFORM_POSIX_VERSION > 0)
static void FIO_munmap(FIO_Dict_t* dict)
{
FIO_rust_munmapDict(&dict->dictBuffer, &dict->dictBufferSize, NULL);
}
static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
{
U64 fileSize;
@@ -701,12 +700,6 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
return 0;
}
#elif defined(_MSC_VER) || defined(_WIN32)
static void FIO_munmap(FIO_Dict_t* dict)
{
FIO_rust_munmapDict(&dict->dictBuffer,
&dict->dictBufferSize,
(void**)&dict->dictHandle);
}
static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
{
U64 fileSize;
@@ -755,26 +748,30 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
{
return FIO_setDictBufferMalloc(dict, fileName, prefs, dictFileStat);
}
static void FIO_munmap(FIO_Dict_t* dict) {
free(dict->dictBuffer);
dict->dictBuffer = NULL;
dict->dictBufferSize = 0;
}
#endif
static void FIO_freeDict(FIO_Dict_t* dict) {
if (dict->dictBufferType == FIO_mallocDict) {
free(dict->dictBuffer);
dict->dictBuffer = NULL;
dict->dictBufferSize = 0;
} else if (dict->dictBufferType == FIO_mmapDict) {
FIO_munmap(dict);
} else {
if (dict->dictBufferType != FIO_mallocDict
&& dict->dictBufferType != FIO_mmapDict) {
assert(0); /* Should not reach this case */
return;
}
FIO_rust_freeDict((int)dict->dictBufferType,
&dict->dictBuffer,
&dict->dictBufferSize,
#if defined(_MSC_VER) || defined(_WIN32)
(void**)&dict->dictHandle
#else
NULL
#endif
);
}
static void FIO_initDict(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat, FIO_dictBufferType_t dictBufferType) {
#if defined(_MSC_VER) || defined(_WIN32)
dict->dictHandle = NULL;
#endif
dict->dictBufferType = dictBufferType;
if (dict->dictBufferType == FIO_mallocDict) {
dict->dictBufferSize = FIO_setDictBufferMalloc(dict, fileName, prefs, dictFileStat);
+96 -1
View File
@@ -454,6 +454,13 @@ pub unsafe extern "C" fn FIO_rust_munmapDict(
}
}
#[cfg(not(any(unix, windows)))]
{
if !mapping.is_null() {
unsafe { libc::free(mapping) };
}
}
unsafe {
*buffer = ptr::null_mut();
*buffer_size = 0;
@@ -463,6 +470,47 @@ pub unsafe extern "C" fn FIO_rust_munmapDict(
}
}
const FIO_MALLOC_DICT: c_int = 0;
const FIO_MMAP_DICT: c_int = 1;
/// Releases either kind of dictionary buffer and clears its C-visible fields.
///
/// C retains the enum validation and calls this leaf only for the two valid
/// `FIO_dictBufferType_t` values. Mapped buffers go through the existing
/// platform-aware release leaf so Windows views and file handles remain paired
/// correctly, while malloc-backed buffers use the matching libc allocator.
#[no_mangle]
pub unsafe extern "C" fn FIO_rust_freeDict(
dict_buffer_type: c_int,
buffer: *mut *mut c_void,
buffer_size: *mut usize,
dict_handle: *mut *mut c_void,
) {
match dict_buffer_type {
FIO_MALLOC_DICT => {
if !buffer.is_null() {
let allocation = unsafe { *buffer };
if !allocation.is_null() {
unsafe { libc::free(allocation) };
}
}
unsafe {
if !buffer.is_null() {
*buffer = ptr::null_mut();
}
if !buffer_size.is_null() {
*buffer_size = 0;
}
if !dict_handle.is_null() {
*dict_handle = ptr::null_mut();
}
}
}
FIO_MMAP_DICT => unsafe { FIO_rust_munmapDict(buffer, buffer_size, dict_handle) },
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -664,12 +712,59 @@ mod tests {
let mapped = unsafe { std::slice::from_raw_parts(buffer.cast::<u8>(), mapped_size) };
assert_eq!(mapped, payload);
unsafe { FIO_rust_munmapDict(&mut buffer, &mut mapped_size, ptr::null_mut()) };
let mut dict_handle = 1usize as *mut c_void;
unsafe {
FIO_rust_freeDict(
FIO_MMAP_DICT,
&mut buffer,
&mut mapped_size,
&mut dict_handle,
)
};
assert!(buffer.is_null());
assert_eq!(mapped_size, 0);
assert!(dict_handle.is_null());
// The release leaf is idempotent after the dispatch has cleared the
// ownership fields, so cleanup cannot double-release the mapping.
unsafe { FIO_rust_munmapDict(&mut buffer, &mut mapped_size, &mut dict_handle) };
fs::remove_file(path).expect("remove temporary file");
}
#[test]
fn frees_malloc_dictionary_and_clears_ownership() {
let allocation = unsafe { libc::malloc(16) };
assert!(!allocation.is_null());
let mut buffer = allocation;
let mut buffer_size = 16;
let mut dict_handle = 1usize as *mut c_void;
unsafe {
FIO_rust_freeDict(
FIO_MALLOC_DICT,
&mut buffer,
&mut buffer_size,
&mut dict_handle,
)
};
assert!(buffer.is_null());
assert_eq!(buffer_size, 0);
assert!(dict_handle.is_null());
// A second release sees only cleared fields and must not double-free.
unsafe {
FIO_rust_freeDict(
FIO_MALLOC_DICT,
&mut buffer,
&mut buffer_size,
&mut dict_handle,
)
};
assert!(buffer.is_null());
assert_eq!(buffer_size, 0);
assert!(dict_handle.is_null());
}
#[cfg(unix)]
#[test]
fn maps_empty_dictionary_with_owned_zero_size_sentinel() {