refactor(cli): classify dictionary load diagnostics in Rust

The file-I/O wrapper already delegated dictionary loading to Rust but kept
all status interpretation in C. That duplicated the malloc and mmap status
families and made the platform-specific error branches part of the C policy
surface. Add a Rust classifier that maps the shared numeric loader statuses
to diagnostic actions, including the distinct mmap failure classes. Keep
metadata lookup, platform handles, ownership, and the exact EXM_THROW text
in C, where the configured platform APIs still belong. The classifier also
rejects out-of-range type/status values; the valid malloc and mmap enums
intentionally share numeric values and therefore cannot be distinguished
beyond their family tag.

Test Plan:
- `ulimit -v 41943040; cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check` -- passed
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings` -- passed
- `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml --all-targets` -- passed (192 tests)
- Broader native and original-test verification remains pending for the complete batch.
This commit is contained in:
2026-07-20 19:47:31 +02:00
parent d714aeac23
commit ee6953b1d7
2 changed files with 198 additions and 46 deletions
+64 -43
View File
@@ -378,6 +378,20 @@ int FIO_rust_setDictBufferMalloc(const char* fileName,
size_t maxSize,
void** buffer,
size_t* loadedSize);
/* Rust owns the dictionary-loader status classification. C keeps the
* metadata, ownership handles, and exact diagnostics around the existing
* narrow loader status ABI. */
enum {
FIO_RUST_DICT_DIAGNOSTIC_OK = 0,
FIO_RUST_DICT_DIAGNOSTIC_OPEN_FAILED = 1,
FIO_RUST_DICT_DIAGNOSTIC_TOO_LARGE = 2,
FIO_RUST_DICT_DIAGNOSTIC_ALLOCATION_FAILED = 3,
FIO_RUST_DICT_DIAGNOSTIC_READ_FAILED = 4,
FIO_RUST_DICT_DIAGNOSTIC_MAP_FAILED = 5,
FIO_RUST_DICT_DIAGNOSTIC_VIEW_FAILED = 6,
FIO_RUST_DICT_DIAGNOSTIC_INVALID = 7,
};
int FIO_rust_dictLoadDiagnostic(int dictBufferType, int status);
#if (PLATFORM_POSIX_VERSION > 0) || defined(_MSC_VER) || defined(_WIN32)
enum {
FIO_DICT_MMAP_SUCCESS = 0,
@@ -805,6 +819,7 @@ static size_t FIO_setDictBufferMalloc(FIO_Dict_t* dict, const char* fileName, FI
FIO_DICT_LOAD_READ_FAILED = 4,
};
int status;
int diagnostic;
assert(bufferPtr != NULL);
assert(dictFileStat != NULL);
@@ -824,23 +839,24 @@ static size_t FIO_setDictBufferMalloc(FIO_Dict_t* dict, const char* fileName, FI
dictSizeMax,
bufferPtr,
&loadedSize);
if (status == FIO_DICT_LOAD_SUCCESS) return loadedSize;
if (status == FIO_DICT_LOAD_OPEN_FAILED) {
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
diagnostic = FIO_rust_dictLoadDiagnostic((int)FIO_mallocDict, status);
switch (diagnostic) {
case FIO_RUST_DICT_DIAGNOSTIC_OK:
return loadedSize;
case FIO_RUST_DICT_DIAGNOSTIC_OPEN_FAILED:
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
case FIO_RUST_DICT_DIAGNOSTIC_TOO_LARGE:
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
case FIO_RUST_DICT_DIAGNOSTIC_ALLOCATION_FAILED:
EXM_THROW(34, "%s", strerror(errno));
case FIO_RUST_DICT_DIAGNOSTIC_READ_FAILED:
EXM_THROW(35, "Error reading dictionary file %s : %s",
fileName, strerror(errno));
default:
assert(0); /* unexpected Rust diagnostic */
return 0;
}
if (status == FIO_DICT_LOAD_TOO_LARGE) {
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
}
if (status == FIO_DICT_LOAD_ALLOCATION_FAILED) {
EXM_THROW(34, "%s", strerror(errno));
}
if (status == FIO_DICT_LOAD_READ_FAILED) {
EXM_THROW(35, "Error reading dictionary file %s : %s",
fileName, strerror(errno));
}
assert(0); /* unexpected Rust status */
return 0;
}
#if (PLATFORM_POSIX_VERSION > 0)
@@ -850,6 +866,7 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
void** bufferPtr = &dict->dictBuffer;
int status;
int diagnostic;
assert(bufferPtr != NULL);
assert(dictFileStat != NULL);
@@ -870,19 +887,21 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
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));
diagnostic = FIO_rust_dictLoadDiagnostic((int)FIO_mmapDict, status);
switch (diagnostic) {
case FIO_RUST_DICT_DIAGNOSTIC_OK:
return dict->dictBufferSize;
case FIO_RUST_DICT_DIAGNOSTIC_OPEN_FAILED:
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
case FIO_RUST_DICT_DIAGNOSTIC_TOO_LARGE:
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
case FIO_RUST_DICT_DIAGNOSTIC_MAP_FAILED:
EXM_THROW(34, "%s", strerror(errno));
default:
assert(0); /* unexpected Rust diagnostic */
return 0;
}
if (status == FIO_DICT_MMAP_TOO_LARGE) {
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
}
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)
static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
@@ -891,6 +910,7 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
void** bufferPtr = &dict->dictBuffer;
int status;
int diagnostic;
assert(bufferPtr != NULL);
assert(dictFileStat != NULL);
@@ -911,22 +931,23 @@ static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_
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));
diagnostic = FIO_rust_dictLoadDiagnostic((int)FIO_mmapDict, status);
switch (diagnostic) {
case FIO_RUST_DICT_DIAGNOSTIC_OK:
return dict->dictBufferSize;
case FIO_RUST_DICT_DIAGNOSTIC_OPEN_FAILED:
EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
case FIO_RUST_DICT_DIAGNOSTIC_TOO_LARGE:
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
case FIO_RUST_DICT_DIAGNOSTIC_MAP_FAILED:
EXM_THROW(35, "Couldn't map dictionary %s: %s", fileName, strerror(errno));
case FIO_RUST_DICT_DIAGNOSTIC_VIEW_FAILED:
EXM_THROW(36, "%s", strerror(errno));
default:
assert(0); /* unexpected Rust diagnostic */
return 0;
}
if (status == FIO_DICT_MMAP_TOO_LARGE) {
EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
fileName, (unsigned)dictSizeMax); /* avoid extreme cases */
}
if (status == FIO_DICT_MMAP_FAILED) {
EXM_THROW(35, "Couldn't map dictionary %s: %s", fileName, strerror(errno));
}
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)
+134 -3
View File
@@ -7,7 +7,10 @@
//! This module implements the filesystem operations behind
//! `FIO_openDstFile()`, `FIO_openSrcFile()`, `FIO_removeFile()`, and dictionary
//! loading, returning small status codes so the C wrappers can retain their
//! existing messages, policy fields, and conventions.
//! existing messages, policy fields, and conventions. Rust also classifies
//! dictionary-loader statuses into diagnostic actions; C retains the
//! metadata and exact diagnostic text while the existing loader leaves
//! continue to publish their narrow status ABI.
use std::ffi::{c_char, c_void, CStr};
use std::fs::File;
@@ -31,10 +34,18 @@ 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_DIAGNOSTIC_OK: c_int = 0;
const FIO_DICT_DIAGNOSTIC_OPEN_FAILED: c_int = 1;
const FIO_DICT_DIAGNOSTIC_TOO_LARGE: c_int = 2;
const FIO_DICT_DIAGNOSTIC_ALLOCATION_FAILED: c_int = 3;
const FIO_DICT_DIAGNOSTIC_READ_FAILED: c_int = 4;
const FIO_DICT_DIAGNOSTIC_MAP_FAILED: c_int = 5;
const FIO_DICT_DIAGNOSTIC_VIEW_FAILED: c_int = 6;
const FIO_DICT_DIAGNOSTIC_INVALID: c_int = 7;
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;
@@ -449,7 +460,39 @@ 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.
/// Classifies a dictionary-loader status for the C diagnostic adapter.
///
/// C retains the metadata query, ownership handles, and exact `EXM_THROW` text;
/// the existing loader leaves continue to publish status codes. Rust owns the
/// policy that interprets each loader family/status pair, including the
/// distinct mmap failure classes. Unknown combinations intentionally select
/// the C assert fallback instead of being treated as success.
#[inline]
fn dict_load_diagnostic(dict_buffer_type: c_int, status: c_int) -> c_int {
match (dict_buffer_type, status) {
(FIO_MALLOC_DICT, FIO_DICT_LOAD_SUCCESS) | (FIO_MMAP_DICT, FIO_DICT_MMAP_SUCCESS) => {
FIO_DICT_DIAGNOSTIC_OK
}
(FIO_MALLOC_DICT, FIO_DICT_LOAD_OPEN_FAILED)
| (FIO_MMAP_DICT, FIO_DICT_MMAP_OPEN_FAILED) => FIO_DICT_DIAGNOSTIC_OPEN_FAILED,
(FIO_MALLOC_DICT, FIO_DICT_LOAD_TOO_LARGE) | (FIO_MMAP_DICT, FIO_DICT_MMAP_TOO_LARGE) => {
FIO_DICT_DIAGNOSTIC_TOO_LARGE
}
(FIO_MALLOC_DICT, FIO_DICT_LOAD_ALLOCATION_FAILED) => FIO_DICT_DIAGNOSTIC_ALLOCATION_FAILED,
(FIO_MALLOC_DICT, FIO_DICT_LOAD_READ_FAILED) => FIO_DICT_DIAGNOSTIC_READ_FAILED,
(FIO_MMAP_DICT, FIO_DICT_MMAP_FAILED) => FIO_DICT_DIAGNOSTIC_MAP_FAILED,
(FIO_MMAP_DICT, FIO_DICT_MMAP_VIEW_FAILED) => FIO_DICT_DIAGNOSTIC_VIEW_FAILED,
_ => FIO_DICT_DIAGNOSTIC_INVALID,
}
}
/// Exposes the dictionary-loader status policy to the C diagnostic adapter.
#[no_mangle]
pub extern "C" fn FIO_rust_dictLoadDiagnostic(dict_buffer_type: c_int, status: c_int) -> c_int {
dict_load_diagnostic(dict_buffer_type, status)
}
/// Maps a dictionary file while keeping the platform loader in this module.
///
/// `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
@@ -848,6 +891,94 @@ mod tests {
);
}
#[test]
fn dictionary_loader_diagnostics_preserve_each_backend_status_class() {
let cases = [
(
FIO_MALLOC_DICT,
FIO_DICT_LOAD_SUCCESS,
FIO_DICT_DIAGNOSTIC_OK,
),
(
FIO_MALLOC_DICT,
FIO_DICT_LOAD_OPEN_FAILED,
FIO_DICT_DIAGNOSTIC_OPEN_FAILED,
),
(
FIO_MALLOC_DICT,
FIO_DICT_LOAD_TOO_LARGE,
FIO_DICT_DIAGNOSTIC_TOO_LARGE,
),
(
FIO_MALLOC_DICT,
FIO_DICT_LOAD_ALLOCATION_FAILED,
FIO_DICT_DIAGNOSTIC_ALLOCATION_FAILED,
),
(
FIO_MALLOC_DICT,
FIO_DICT_LOAD_READ_FAILED,
FIO_DICT_DIAGNOSTIC_READ_FAILED,
),
(FIO_MMAP_DICT, FIO_DICT_MMAP_SUCCESS, FIO_DICT_DIAGNOSTIC_OK),
(
FIO_MMAP_DICT,
FIO_DICT_MMAP_OPEN_FAILED,
FIO_DICT_DIAGNOSTIC_OPEN_FAILED,
),
(
FIO_MMAP_DICT,
FIO_DICT_MMAP_TOO_LARGE,
FIO_DICT_DIAGNOSTIC_TOO_LARGE,
),
(
FIO_MMAP_DICT,
FIO_DICT_MMAP_FAILED,
FIO_DICT_DIAGNOSTIC_MAP_FAILED,
),
(
FIO_MMAP_DICT,
FIO_DICT_MMAP_VIEW_FAILED,
FIO_DICT_DIAGNOSTIC_VIEW_FAILED,
),
];
for (dict_buffer_type, status, expected_diagnostic) in cases {
assert_eq!(
dict_load_diagnostic(dict_buffer_type, status),
expected_diagnostic,
"unexpected dictionary diagnostic for buffer type {dict_buffer_type} and status {status}"
);
assert_eq!(
FIO_rust_dictLoadDiagnostic(dict_buffer_type, status),
expected_diagnostic,
"export disagrees for buffer type {dict_buffer_type} and status {status}"
);
}
}
#[test]
fn dictionary_loader_diagnostics_reject_invalid_combinations() {
let cases = [
(FIO_MALLOC_DICT, c_int::MAX),
(FIO_MMAP_DICT, -1),
(c_int::MAX, FIO_DICT_LOAD_SUCCESS),
(-1, FIO_DICT_MMAP_SUCCESS),
];
for (dict_buffer_type, status) in cases {
assert_eq!(
dict_load_diagnostic(dict_buffer_type, status),
FIO_DICT_DIAGNOSTIC_INVALID,
"invalid dictionary loader combination was accepted"
);
assert_eq!(
FIO_rust_dictLoadDiagnostic(dict_buffer_type, status),
FIO_DICT_DIAGNOSTIC_INVALID,
"export accepted invalid dictionary loader combination"
);
}
}
#[test]
fn destination_test_mode_returns_before_path_validation() {
let mut is_dst_reg_file = c_int::MAX;