feat(cli): move dictionary mmap lifecycle into Rust
Move the platform-specific dictionary mapping and release operations behind the Rust CLI archive while keeping size policy, diagnostics, and FIO_Dict_t ownership decisions in C. POSIX descriptors and Windows handles are cleaned up on every failure path, and zero-length dictionaries use an owned sentinel so the existing non-null dictionary invariant remains valid. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml --lib fileio_backend (24 passed) - cargo clippy --manifest-path rust/cli/Cargo.toml --lib -- -D warnings - make -C programs -j2 zstd - --mmap-dict compression/decompression round trip with programs/README.md - programs/zstd --mmap-dict --test on the generated frame - rustfmt +nightly --edition 2021 rust/src/fileio_backend.rs --check - git diff --check
This commit is contained in:
+67
-46
@@ -366,6 +366,22 @@ int FIO_rust_setDictBufferMalloc(const char* fileName,
|
||||
size_t maxSize,
|
||||
void** buffer,
|
||||
size_t* loadedSize);
|
||||
#if (PLATFORM_POSIX_VERSION > 0) || defined(_MSC_VER) || defined(_WIN32)
|
||||
enum {
|
||||
FIO_DICT_MMAP_SUCCESS = 0,
|
||||
FIO_DICT_MMAP_OPEN_FAILED = 1,
|
||||
FIO_DICT_MMAP_TOO_LARGE = 2,
|
||||
FIO_DICT_MMAP_FAILED = 3,
|
||||
FIO_DICT_MMAP_VIEW_FAILED = 4,
|
||||
};
|
||||
int FIO_rust_setDictBufferMMap(const char* fileName,
|
||||
unsigned long long expectedFileSize,
|
||||
size_t maxSize,
|
||||
void** buffer,
|
||||
size_t* mappedSize,
|
||||
void** dictHandle);
|
||||
void FIO_rust_munmapDict(void** buffer, size_t* bufferSize, void** dictHandle);
|
||||
#endif
|
||||
int FIO_rust_removeFile(const char* path);
|
||||
int FIO_rust_passThrough(ReadPoolCtx_t* readCtx, WritePoolCtx_t* writeCtx);
|
||||
#ifdef ZSTD_LZ4COMPRESS
|
||||
@@ -640,18 +656,16 @@ static size_t FIO_setDictBufferMalloc(FIO_Dict_t* dict, const char* fileName, FI
|
||||
}
|
||||
|
||||
#if (PLATFORM_POSIX_VERSION > 0)
|
||||
#include <sys/mman.h>
|
||||
static void FIO_munmap(FIO_Dict_t* dict)
|
||||
{
|
||||
munmap(dict->dictBuffer, dict->dictBufferSize);
|
||||
dict->dictBuffer = NULL;
|
||||
dict->dictBufferSize = 0;
|
||||
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)
|
||||
{
|
||||
int fileHandle;
|
||||
U64 fileSize;
|
||||
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
|
||||
void** bufferPtr = &dict->dictBuffer;
|
||||
int status;
|
||||
|
||||
assert(bufferPtr != NULL);
|
||||
assert(dictFileStat != NULL);
|
||||
@@ -660,41 +674,45 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
|
||||
|
||||
DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
|
||||
|
||||
fileHandle = open(fileName, O_RDONLY);
|
||||
fileSize = UTIL_getFileSizeStat(dictFileStat);
|
||||
if (fileSize > dictSizeMax) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
|
||||
if (fileHandle == -1) {
|
||||
status = FIO_rust_setDictBufferMMap(fileName,
|
||||
(unsigned long long)fileSize,
|
||||
dictSizeMax,
|
||||
bufferPtr,
|
||||
&dict->dictBufferSize,
|
||||
NULL);
|
||||
if (status == FIO_DICT_MMAP_SUCCESS) return dict->dictBufferSize;
|
||||
if (status == FIO_DICT_MMAP_OPEN_FAILED) {
|
||||
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
|
||||
}
|
||||
|
||||
fileSize = UTIL_getFileSizeStat(dictFileStat);
|
||||
{
|
||||
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
|
||||
if (fileSize > dictSizeMax) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
if (status == FIO_DICT_MMAP_TOO_LARGE) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
|
||||
*bufferPtr = mmap(NULL, (size_t)fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0);
|
||||
if (*bufferPtr==NULL) EXM_THROW(34, "%s", strerror(errno));
|
||||
|
||||
close(fileHandle);
|
||||
return (size_t)fileSize;
|
||||
if (status == FIO_DICT_MMAP_FAILED) {
|
||||
EXM_THROW(34, "%s", strerror(errno));
|
||||
}
|
||||
assert(0); /* unexpected Rust status */
|
||||
return 0;
|
||||
}
|
||||
#elif defined(_MSC_VER) || defined(_WIN32)
|
||||
#include <windows.h>
|
||||
static void FIO_munmap(FIO_Dict_t* dict)
|
||||
{
|
||||
UnmapViewOfFile(dict->dictBuffer);
|
||||
CloseHandle(dict->dictHandle);
|
||||
dict->dictBuffer = NULL;
|
||||
dict->dictBufferSize = 0;
|
||||
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)
|
||||
{
|
||||
HANDLE fileHandle, mapping;
|
||||
U64 fileSize;
|
||||
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
|
||||
void** bufferPtr = &dict->dictBuffer;
|
||||
int status;
|
||||
|
||||
assert(bufferPtr != NULL);
|
||||
assert(dictFileStat != NULL);
|
||||
@@ -703,31 +721,34 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
|
||||
|
||||
DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
|
||||
|
||||
fileHandle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
|
||||
fileSize = UTIL_getFileSizeStat(dictFileStat);
|
||||
if (fileSize > dictSizeMax) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
|
||||
if (fileHandle == INVALID_HANDLE_VALUE) {
|
||||
status = FIO_rust_setDictBufferMMap(fileName,
|
||||
(unsigned long long)fileSize,
|
||||
dictSizeMax,
|
||||
bufferPtr,
|
||||
&dict->dictBufferSize,
|
||||
(void**)&dict->dictHandle);
|
||||
if (status == FIO_DICT_MMAP_SUCCESS) return dict->dictBufferSize;
|
||||
if (status == FIO_DICT_MMAP_OPEN_FAILED) {
|
||||
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
|
||||
}
|
||||
|
||||
fileSize = UTIL_getFileSizeStat(dictFileStat);
|
||||
{
|
||||
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
|
||||
if (fileSize > dictSizeMax) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
if (status == FIO_DICT_MMAP_TOO_LARGE) {
|
||||
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
|
||||
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
|
||||
}
|
||||
|
||||
mapping = CreateFileMapping(fileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (mapping == NULL) {
|
||||
if (status == FIO_DICT_MMAP_FAILED) {
|
||||
EXM_THROW(35, "Couldn't map dictionary %s: %s", fileName, strerror(errno));
|
||||
}
|
||||
|
||||
*bufferPtr = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, (DWORD)fileSize); /* we can only cast to DWORD here because dictSize <= 2GB */
|
||||
if (*bufferPtr==NULL) EXM_THROW(36, "%s", strerror(errno));
|
||||
|
||||
dict->dictHandle = fileHandle;
|
||||
return (size_t)fileSize;
|
||||
if (status == FIO_DICT_MMAP_VIEW_FAILED) {
|
||||
EXM_THROW(36, "%s", strerror(errno));
|
||||
}
|
||||
assert(0); /* unexpected Rust status */
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
|
||||
|
||||
@@ -27,6 +27,14 @@ const FIO_DICT_LOAD_TOO_LARGE: c_int = 2;
|
||||
const FIO_DICT_LOAD_ALLOCATION_FAILED: c_int = 3;
|
||||
const FIO_DICT_LOAD_READ_FAILED: c_int = 4;
|
||||
|
||||
const FIO_DICT_MMAP_SUCCESS: c_int = 0;
|
||||
const FIO_DICT_MMAP_OPEN_FAILED: c_int = 1;
|
||||
const FIO_DICT_MMAP_TOO_LARGE: c_int = 2;
|
||||
const FIO_DICT_MMAP_FAILED: c_int = 3;
|
||||
#[cfg(windows)]
|
||||
const FIO_DICT_MMAP_VIEW_FAILED: c_int = 4;
|
||||
const FIO_DICT_MMAP_EMPTY_ALLOCATION_SIZE: usize = 1;
|
||||
|
||||
const FIO_DICT_STAT_SUCCESS: c_int = 0;
|
||||
const FIO_DICT_STAT_FAILED: c_int = 1;
|
||||
const FIO_DICT_STAT_NON_REGULAR: c_int = 2;
|
||||
@@ -44,6 +52,65 @@ unsafe extern "C" {
|
||||
fn fopen(file_name: *const c_char, mode: *const c_char) -> *mut c_void;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const INVALID_HANDLE_VALUE: *mut c_void = -1isize as *mut c_void;
|
||||
|
||||
#[cfg(windows)]
|
||||
const GENERIC_READ: u32 = 0x8000_0000;
|
||||
#[cfg(windows)]
|
||||
const FILE_SHARE_READ: u32 = 0x0000_0001;
|
||||
#[cfg(windows)]
|
||||
const OPEN_EXISTING: u32 = 3;
|
||||
#[cfg(windows)]
|
||||
const FILE_ATTRIBUTE_READONLY: u32 = 0x0000_0001;
|
||||
#[cfg(windows)]
|
||||
const PAGE_READONLY: u32 = 0x02;
|
||||
#[cfg(windows)]
|
||||
const FILE_MAP_READ: u32 = 0x0004;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[link(name = "kernel32")]
|
||||
unsafe extern "system" {
|
||||
fn CreateFileA(
|
||||
file_name: *const c_char,
|
||||
desired_access: u32,
|
||||
share_mode: u32,
|
||||
security_attributes: *mut c_void,
|
||||
creation_disposition: u32,
|
||||
flags_and_attributes: u32,
|
||||
template_file: *mut c_void,
|
||||
) -> *mut c_void;
|
||||
fn CreateFileMappingA(
|
||||
file: *mut c_void,
|
||||
attributes: *mut c_void,
|
||||
protect: u32,
|
||||
maximum_size_high: u32,
|
||||
maximum_size_low: u32,
|
||||
name: *const c_char,
|
||||
) -> *mut c_void;
|
||||
fn MapViewOfFile(
|
||||
mapping: *mut c_void,
|
||||
desired_access: u32,
|
||||
file_offset_high: u32,
|
||||
file_offset_low: u32,
|
||||
number_of_bytes_to_map: usize,
|
||||
) -> *mut c_void;
|
||||
fn UnmapViewOfFile(address: *const c_void) -> i32;
|
||||
fn CloseHandle(handle: *mut c_void) -> i32;
|
||||
}
|
||||
|
||||
unsafe fn publish_empty_dict_mapping(buffer: *mut *mut c_void, mapped_size: *mut usize) -> c_int {
|
||||
let allocation = unsafe { libc::malloc(FIO_DICT_MMAP_EMPTY_ALLOCATION_SIZE) };
|
||||
if allocation.is_null() {
|
||||
return FIO_DICT_MMAP_FAILED;
|
||||
}
|
||||
unsafe {
|
||||
*buffer = allocation;
|
||||
*mapped_size = 0;
|
||||
}
|
||||
FIO_DICT_MMAP_SUCCESS
|
||||
}
|
||||
|
||||
fn path_from_c(path: *const c_char) -> Option<PathBuf> {
|
||||
if path.is_null() {
|
||||
return None;
|
||||
@@ -212,6 +279,190 @@ pub unsafe extern "C" fn FIO_rust_setDictBufferMalloc(
|
||||
FIO_DICT_LOAD_SUCCESS
|
||||
}
|
||||
|
||||
/// Maps a dictionary file while leaving policy and diagnostics in the C CLI.
|
||||
///
|
||||
/// `mapped_size` is the file size, not the allocation size used for an empty
|
||||
/// dictionary. The release leaf treats a non-null zero-sized mapping as that
|
||||
/// one-byte owned allocation and frees it instead of calling `munmap()` with a
|
||||
/// zero length.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn FIO_rust_setDictBufferMMap(
|
||||
file_name: *const c_char,
|
||||
expected_file_size: u64,
|
||||
max_size: usize,
|
||||
buffer: *mut *mut c_void,
|
||||
mapped_size: *mut usize,
|
||||
dict_handle: *mut *mut c_void,
|
||||
) -> c_int {
|
||||
if buffer.is_null() || mapped_size.is_null() {
|
||||
return FIO_DICT_MMAP_FAILED;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*buffer = ptr::null_mut();
|
||||
*mapped_size = 0;
|
||||
if !dict_handle.is_null() {
|
||||
*dict_handle = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
let Some(expected_size) = usize::try_from(expected_file_size).ok() else {
|
||||
return FIO_DICT_MMAP_TOO_LARGE;
|
||||
};
|
||||
if expected_size > max_size {
|
||||
return FIO_DICT_MMAP_TOO_LARGE;
|
||||
}
|
||||
if file_name.is_null() {
|
||||
return FIO_DICT_MMAP_SUCCESS;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let file_descriptor = unsafe { libc::open(file_name, libc::O_RDONLY) };
|
||||
if file_descriptor < 0 {
|
||||
return FIO_DICT_MMAP_OPEN_FAILED;
|
||||
}
|
||||
|
||||
if expected_size == 0 {
|
||||
unsafe { libc::close(file_descriptor) };
|
||||
return unsafe { publish_empty_dict_mapping(buffer, mapped_size) };
|
||||
}
|
||||
|
||||
let mapping = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
expected_size,
|
||||
libc::PROT_READ,
|
||||
libc::MAP_PRIVATE,
|
||||
file_descriptor,
|
||||
0,
|
||||
)
|
||||
};
|
||||
unsafe { libc::close(file_descriptor) };
|
||||
if mapping == (-1isize as *mut c_void) {
|
||||
return FIO_DICT_MMAP_FAILED;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*buffer = mapping;
|
||||
*mapped_size = expected_size;
|
||||
}
|
||||
FIO_DICT_MMAP_SUCCESS
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let file_handle = unsafe {
|
||||
CreateFileA(
|
||||
file_name,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_READONLY,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if file_handle.is_null() || file_handle == INVALID_HANDLE_VALUE {
|
||||
return FIO_DICT_MMAP_OPEN_FAILED;
|
||||
}
|
||||
|
||||
if expected_size == 0 {
|
||||
unsafe { CloseHandle(file_handle) };
|
||||
return unsafe { publish_empty_dict_mapping(buffer, mapped_size) };
|
||||
}
|
||||
|
||||
let mapping = unsafe {
|
||||
CreateFileMappingA(
|
||||
file_handle,
|
||||
ptr::null_mut(),
|
||||
PAGE_READONLY,
|
||||
0,
|
||||
0,
|
||||
ptr::null(),
|
||||
)
|
||||
};
|
||||
if mapping.is_null() {
|
||||
unsafe { CloseHandle(file_handle) };
|
||||
return FIO_DICT_MMAP_FAILED;
|
||||
}
|
||||
|
||||
let view = unsafe { MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, expected_size) };
|
||||
unsafe { CloseHandle(mapping) };
|
||||
if view.is_null() {
|
||||
unsafe { CloseHandle(file_handle) };
|
||||
return FIO_DICT_MMAP_VIEW_FAILED;
|
||||
}
|
||||
|
||||
if dict_handle.is_null() {
|
||||
unsafe { CloseHandle(file_handle) };
|
||||
return FIO_DICT_MMAP_FAILED;
|
||||
}
|
||||
unsafe {
|
||||
*buffer = view;
|
||||
*mapped_size = expected_size;
|
||||
*dict_handle = file_handle;
|
||||
}
|
||||
FIO_DICT_MMAP_SUCCESS
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
{
|
||||
FIO_DICT_MMAP_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a dictionary mapping and clears all C-visible ownership fields.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn FIO_rust_munmapDict(
|
||||
buffer: *mut *mut c_void,
|
||||
buffer_size: *mut usize,
|
||||
dict_handle: *mut *mut c_void,
|
||||
) {
|
||||
if buffer.is_null() || buffer_size.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mapping = unsafe { *buffer };
|
||||
let size = unsafe { *buffer_size };
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if !mapping.is_null() {
|
||||
if size == 0 {
|
||||
unsafe { libc::free(mapping) };
|
||||
} else {
|
||||
unsafe { libc::munmap(mapping, size) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if !mapping.is_null() {
|
||||
if size == 0 {
|
||||
unsafe { libc::free(mapping) };
|
||||
} else {
|
||||
unsafe { UnmapViewOfFile(mapping) };
|
||||
}
|
||||
}
|
||||
if !dict_handle.is_null() {
|
||||
let handle = unsafe { *dict_handle };
|
||||
if !handle.is_null() && handle != INVALID_HANDLE_VALUE {
|
||||
unsafe { CloseHandle(handle) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*buffer = ptr::null_mut();
|
||||
*buffer_size = 0;
|
||||
if !dict_handle.is_null() {
|
||||
*dict_handle = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -388,6 +639,66 @@ mod tests {
|
||||
fs::remove_file(&path).expect("remove temporary file");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn maps_and_releases_dictionary() {
|
||||
let path = temp_path("mapped-dictionary");
|
||||
let payload = b"mapped dictionary payload";
|
||||
fs::write(&path, payload).expect("create temporary file");
|
||||
let c_path = c_path(&path);
|
||||
let mut buffer = ptr::null_mut();
|
||||
let mut mapped_size = usize::MAX;
|
||||
|
||||
let status = unsafe {
|
||||
FIO_rust_setDictBufferMMap(
|
||||
c_path.as_ptr(),
|
||||
payload.len() as u64,
|
||||
payload.len(),
|
||||
&mut buffer,
|
||||
&mut mapped_size,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
assert_eq!(status, FIO_DICT_MMAP_SUCCESS);
|
||||
assert_eq!(mapped_size, payload.len());
|
||||
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()) };
|
||||
assert!(buffer.is_null());
|
||||
assert_eq!(mapped_size, 0);
|
||||
fs::remove_file(path).expect("remove temporary file");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn maps_empty_dictionary_with_owned_zero_size_sentinel() {
|
||||
let path = temp_path("mapped-empty-dictionary");
|
||||
fs::write(&path, b"").expect("create empty temporary file");
|
||||
let c_path = c_path(&path);
|
||||
let mut buffer = ptr::null_mut();
|
||||
let mut mapped_size = usize::MAX;
|
||||
|
||||
let status = unsafe {
|
||||
FIO_rust_setDictBufferMMap(
|
||||
c_path.as_ptr(),
|
||||
0,
|
||||
0,
|
||||
&mut buffer,
|
||||
&mut mapped_size,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
assert_eq!(status, FIO_DICT_MMAP_SUCCESS);
|
||||
assert!(!buffer.is_null());
|
||||
assert_eq!(mapped_size, 0);
|
||||
|
||||
unsafe { FIO_rust_munmapDict(&mut buffer, &mut mapped_size, ptr::null_mut()) };
|
||||
assert!(buffer.is_null());
|
||||
assert_eq!(mapped_size, 0);
|
||||
fs::remove_file(path).expect("remove temporary file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_regular_dictionary_file() {
|
||||
let path = temp_path("regular-dictionary-stat");
|
||||
|
||||
Reference in New Issue
Block a user