diff --git a/programs/fileio.c b/programs/fileio.c index 29aa14ab5..8f9979513 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -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) diff --git a/rust/src/fileio_backend.rs b/rust/src/fileio_backend.rs index cc69b9add..7683f455f 100644 --- a/rust/src/fileio_backend.rs +++ b/rust/src/fileio_backend.rs @@ -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;