diff --git a/programs/fileio.c b/programs/fileio.c index 27bc17920..bde1f9711 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -3820,11 +3820,62 @@ int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx, ***************************************************************************/ typedef struct { FIO_Dict_t dict; + stat_t dictFileStat; ZSTD_DStream* dctx; WritePoolCtx_t *writeCtx; ReadPoolCtx_t *readCtx; } dRess_t; +typedef void (*FIO_rust_createDResourcesPrepareDictionaryCallback_f)( + void* context, FIO_prefs_t* prefs, const char* dictFileName); +typedef void (*FIO_rust_createDResourcesCreateDctxCallback_f)(void* context); +typedef size_t (*FIO_rust_createDResourcesSetMaxWindowSizeCallback_f)( + void* context, unsigned memLimit); +typedef size_t (*FIO_rust_createDResourcesSetParameterCallback_f)( + void* context, int value); +typedef size_t (*FIO_rust_createDResourcesLoadDictionaryCallback_f)( + void* context, FIO_prefs_t* prefs, const char* dictFileName); +typedef void (*FIO_rust_createDResourcesCreatePoolCallback_f)( + void* context, const FIO_prefs_t* prefs); +typedef int (*FIO_rust_createDResourcesIsErrorCallback_f)(size_t result); +typedef struct { + void* callbackContext; + FIO_prefs_t* prefs; + const char* dictFileName; + FIO_rust_createDResourcesPrepareDictionaryCallback_f prepareDictionary; + FIO_rust_createDResourcesCreateDctxCallback_f createDctx; + FIO_rust_createDResourcesSetMaxWindowSizeCallback_f setMaxWindowSize; + FIO_rust_createDResourcesSetParameterCallback_f setParameter; + FIO_rust_createDResourcesLoadDictionaryCallback_f loadDictionary; + FIO_rust_createDResourcesCreatePoolCallback_f createWritePool; + FIO_rust_createDResourcesCreatePoolCallback_f createReadPool; + FIO_rust_createDResourcesIsErrorCallback_f isError; +} FIO_rust_createDResourcesState; +typedef char FIO_rust_create_d_resources_state_layout[ + (offsetof(FIO_rust_createDResourcesState, callbackContext) == 0 + && offsetof(FIO_rust_createDResourcesState, prefs) == sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, dictFileName) + == 2 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, prepareDictionary) + == 3 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, createDctx) + == 4 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, setMaxWindowSize) + == 5 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, setParameter) + == 6 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, loadDictionary) + == 7 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, createWritePool) + == 8 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, createReadPool) + == 9 * sizeof(void*) + && offsetof(FIO_rust_createDResourcesState, isError) + == 10 * sizeof(void*) + && sizeof(FIO_rust_createDResourcesState) == 11 * sizeof(void*)) + ? 1 : -1]; +size_t FIO_rust_createDResources(const FIO_rust_createDResourcesState* state); + typedef void (*FIO_rust_freeDResourcesCallback_f)(void* context); typedef struct { void* callbackContext; @@ -3870,45 +3921,107 @@ static void FIO_rust_freeDResources_readPool(void* context) AIO_ReadPool_free(ress->readCtx); } +static void FIO_rust_createDResources_prepareDictionary( + void* context, FIO_prefs_t* prefs, const char* dictFileName) +{ + dRess_t* const ress = (dRess_t*)context; + FIO_getDictFileStat(dictFileName, &ress->dictFileStat); + if (prefs->patchFromMode) { + unsigned long long const dictSize = + (unsigned long long)UTIL_getFileSizeStat(&ress->dictFileStat); + FIO_adjustMemLimitForPatchFromMode( + prefs, dictSize, 0 /* just use the dict size */); + } +} + +static void FIO_rust_createDResources_createDctx(void* context) +{ + dRess_t* const ress = (dRess_t*)context; + ress->dctx = ZSTD_createDStream(); + if (ress->dctx == NULL) + EXM_THROW(60, "Error: %s : can't create ZSTD_DStream", strerror(errno)); +} + +static size_t FIO_rust_createDResources_setMaxWindowSize( + void* context, unsigned memLimit) +{ + dRess_t* const ress = (dRess_t*)context; + return ZSTD_DCtx_setMaxWindowSize(ress->dctx, memLimit); +} + +static size_t FIO_rust_createDResources_setParameter(void* context, int value) +{ + dRess_t* const ress = (dRess_t*)context; + return ZSTD_DCtx_setParameter( + ress->dctx, ZSTD_d_forceIgnoreChecksum, value); +} + +static size_t FIO_rust_createDResources_loadDictionary( + void* context, FIO_prefs_t* prefs, const char* dictFileName) +{ + dRess_t* const ress = (dRess_t*)context; + unsigned long long const dictSize = prefs->patchFromMode + ? (unsigned long long)UTIL_getFileSizeStat(&ress->dictFileStat) + : 0; + FIO_dictBufferType_t const dictBufferType = + (FIO_dictBufferType_t)FIO_rust_selectDictBufferType( + prefs->mmapDict, prefs->patchFromMode, dictSize, + prefs->memLimit); + + FIO_initDict(&ress->dict, dictFileName, prefs, &ress->dictFileStat, + dictBufferType); + + { size_t const result = ZSTD_DCtx_reset( + ress->dctx, ZSTD_reset_session_only); + if (ZSTD_isError(result)) return result; + } + + if (prefs->patchFromMode) { + return ZSTD_DCtx_refPrefix( + ress->dctx, ress->dict.dictBuffer, ress->dict.dictBufferSize); + } + return ZSTD_DCtx_loadDictionary_byReference( + ress->dctx, ress->dict.dictBuffer, ress->dict.dictBufferSize); +} + +static void FIO_rust_createDResources_createWritePool( + void* context, const FIO_prefs_t* prefs) +{ + dRess_t* const ress = (dRess_t*)context; + ress->writeCtx = AIO_WritePool_create(prefs, ZSTD_DStreamOutSize()); +} + +static void FIO_rust_createDResources_createReadPool( + void* context, const FIO_prefs_t* prefs) +{ + dRess_t* const ress = (dRess_t*)context; + ress->readCtx = AIO_ReadPool_create(prefs, ZSTD_DStreamInSize()); +} + +static int FIO_rust_createDResources_isError(size_t result) +{ + return ZSTD_isError(result); +} + static dRess_t FIO_createDResources(FIO_prefs_t* const prefs, const char* dictFileName) { - unsigned long long dictSize = 0; - stat_t statbuf; dRess_t ress; - memset(&statbuf, 0, sizeof(statbuf)); memset(&ress, 0, sizeof(ress)); - FIO_getDictFileStat(dictFileName, &statbuf); - - if (prefs->patchFromMode){ - dictSize = (unsigned long long)UTIL_getFileSizeStat(&statbuf); - FIO_adjustMemLimitForPatchFromMode(prefs, dictSize, 0 /* just use the dict size */); - } - - /* Allocation */ - ress.dctx = ZSTD_createDStream(); - if (ress.dctx==NULL) - EXM_THROW(60, "Error: %s : can't create ZSTD_DStream", strerror(errno)); - CHECK( ZSTD_DCtx_setMaxWindowSize(ress.dctx, prefs->memLimit) ); - CHECK( ZSTD_DCtx_setParameter(ress.dctx, ZSTD_d_forceIgnoreChecksum, !prefs->checksumFlag)); - - /* dictionary */ - { - FIO_dictBufferType_t const dictBufferType = (FIO_dictBufferType_t)FIO_rust_selectDictBufferType( - prefs->mmapDict, prefs->patchFromMode, dictSize, prefs->memLimit); - FIO_initDict(&ress.dict, dictFileName, prefs, &statbuf, dictBufferType); - - CHECK(ZSTD_DCtx_reset(ress.dctx, ZSTD_reset_session_only) ); - - if (prefs->patchFromMode){ - CHECK(ZSTD_DCtx_refPrefix(ress.dctx, ress.dict.dictBuffer, ress.dict.dictBufferSize)); - } else { - CHECK(ZSTD_DCtx_loadDictionary_byReference(ress.dctx, ress.dict.dictBuffer, ress.dict.dictBufferSize)); - } - } - - ress.writeCtx = AIO_WritePool_create(prefs, ZSTD_DStreamOutSize()); - ress.readCtx = AIO_ReadPool_create(prefs, ZSTD_DStreamInSize()); + FIO_rust_createDResourcesState const state = { + &ress, + prefs, + dictFileName, + FIO_rust_createDResources_prepareDictionary, + FIO_rust_createDResources_createDctx, + FIO_rust_createDResources_setMaxWindowSize, + FIO_rust_createDResources_setParameter, + FIO_rust_createDResources_loadDictionary, + FIO_rust_createDResources_createWritePool, + FIO_rust_createDResources_createReadPool, + FIO_rust_createDResources_isError + }; + CHECK(FIO_rust_createDResources(&state)); return ress; } diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index ae5926f7c..c7e5ae7e8 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -358,6 +358,99 @@ pub unsafe extern "C" fn FIO_rust_freeDResources(state: *const FIO_rust_freeDRes } } +pub type FIO_createDResourcesPrepareDictionaryFn = + unsafe extern "C" fn(*mut c_void, *mut FIO_prefs_t, *const c_char); +pub type FIO_createDResourcesCreateDctxFn = unsafe extern "C" fn(*mut c_void); +pub type FIO_createDResourcesSetMaxWindowSizeFn = + unsafe extern "C" fn(*mut c_void, c_uint) -> usize; +pub type FIO_createDResourcesSetParameterFn = unsafe extern "C" fn(*mut c_void, c_int) -> usize; +pub type FIO_createDResourcesLoadDictionaryFn = + unsafe extern "C" fn(*mut c_void, *mut FIO_prefs_t, *const c_char) -> usize; +pub type FIO_createDResourcesCreatePoolFn = unsafe extern "C" fn(*mut c_void, *const FIO_prefs_t); +pub type FIO_createDResourcesIsErrorFn = unsafe extern "C" fn(usize) -> c_int; + +/// Decompression-resource initialization projection. +/// +/// Rust owns the stable creation order and error short-circuiting. C keeps +/// the `dRess_t`, decoder context, dictionary/stat storage, and asynchronous +/// pools private behind callbacks which preserve the original diagnostics and +/// resource operations. +#[repr(C)] +pub struct FIO_rust_createDResourcesState { + callback_context: *mut c_void, + prefs: *mut FIO_prefs_t, + dict_file_name: *const c_char, + prepare_dictionary: FIO_createDResourcesPrepareDictionaryFn, + create_dctx: FIO_createDResourcesCreateDctxFn, + set_max_window_size: FIO_createDResourcesSetMaxWindowSizeFn, + set_parameter: FIO_createDResourcesSetParameterFn, + load_dictionary: FIO_createDResourcesLoadDictionaryFn, + create_write_pool: FIO_createDResourcesCreatePoolFn, + create_read_pool: FIO_createDResourcesCreatePoolFn, + is_error: FIO_createDResourcesIsErrorFn, +} + +const _: () = { + let word = size_of::(); + assert!(offset_of!(FIO_rust_createDResourcesState, callback_context) == 0); + assert!(offset_of!(FIO_rust_createDResourcesState, prefs) == word); + assert!(offset_of!(FIO_rust_createDResourcesState, dict_file_name) == 2 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, prepare_dictionary) == 3 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, create_dctx) == 4 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, set_max_window_size) == 5 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, set_parameter) == 6 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, load_dictionary) == 7 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, create_write_pool) == 8 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, create_read_pool) == 9 * word); + assert!(offset_of!(FIO_rust_createDResourcesState, is_error) == 10 * word); + assert!(size_of::() == 11 * word); +}; + +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_createDResources( + state: *const FIO_rust_createDResourcesState, +) -> usize { + let Some(state) = (unsafe { state.as_ref() }) else { + return usize::MAX; + }; + if state.prefs.is_null() { + return usize::MAX; + } + + unsafe { + (state.prepare_dictionary)(state.callback_context, state.prefs, state.dict_file_name); + (state.create_dctx)(state.callback_context); + } + + // The preparation callback may adjust prefs->memLimit for patch mode, so + // read the scalar inputs only after that callback has completed. + let prefs = unsafe { &*state.prefs }; + let result = unsafe { (state.set_max_window_size)(state.callback_context, prefs.memLimit) }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + + let result = unsafe { + (state.set_parameter)(state.callback_context, c_int::from(prefs.checksumFlag == 0)) + }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + + let result = unsafe { + (state.load_dictionary)(state.callback_context, state.prefs, state.dict_file_name) + }; + if unsafe { (state.is_error)(result) } != 0 { + return result; + } + + unsafe { + (state.create_write_pool)(state.callback_context, state.prefs); + (state.create_read_pool)(state.callback_context, state.prefs); + } + 0 +} + #[cfg(feature = "decompression")] // `ZSTD_error_frameParameter_windowTooLarge` from zstd_errors.h. const ZSTD_ERROR_FRAME_PARAMETER_WINDOW_TOO_LARGE: c_int = 16; @@ -3189,6 +3282,164 @@ mod tests { assert!(context.events.is_empty()); } + #[derive(Default)] + struct CreateDResourcesTestState { + lifecycle: Vec<&'static str>, + max_window_size: Option, + ignore_checksum: Option, + fail_at: Option<&'static str>, + } + + unsafe extern "C" fn create_d_resources_prepare_dictionary_test( + context: *mut c_void, + _prefs: *mut FIO_prefs_t, + _dict_file_name: *const c_char, + ) { + unsafe { + (*context.cast::()) + .lifecycle + .push("prepare-dictionary"); + } + } + + unsafe extern "C" fn create_d_resources_create_dctx_test(context: *mut c_void) { + unsafe { + (*context.cast::()) + .lifecycle + .push("create-dctx"); + } + } + + unsafe extern "C" fn create_d_resources_set_max_window_size_test( + context: *mut c_void, + value: c_uint, + ) -> usize { + let state = unsafe { &mut *context.cast::() }; + state.lifecycle.push("set-max-window"); + state.max_window_size = Some(value); + usize::from(state.fail_at == Some("set-max-window")) * usize::MAX + } + + unsafe extern "C" fn create_d_resources_set_parameter_test( + context: *mut c_void, + value: c_int, + ) -> usize { + let state = unsafe { &mut *context.cast::() }; + state.lifecycle.push("set-parameter"); + state.ignore_checksum = Some(value); + usize::from(state.fail_at == Some("set-parameter")) * usize::MAX + } + + unsafe extern "C" fn create_d_resources_load_dictionary_test( + context: *mut c_void, + _prefs: *mut FIO_prefs_t, + _dict_file_name: *const c_char, + ) -> usize { + let state = unsafe { &mut *context.cast::() }; + state.lifecycle.push("load-dictionary"); + usize::from(state.fail_at == Some("load-dictionary")) * usize::MAX + } + + unsafe extern "C" fn create_d_resources_create_write_pool_test( + context: *mut c_void, + _prefs: *const FIO_prefs_t, + ) { + unsafe { + (*context.cast::()) + .lifecycle + .push("create-write-pool"); + } + } + + unsafe extern "C" fn create_d_resources_create_read_pool_test( + context: *mut c_void, + _prefs: *const FIO_prefs_t, + ) { + unsafe { + (*context.cast::()) + .lifecycle + .push("create-read-pool"); + } + } + + unsafe extern "C" fn create_d_resources_is_error_test(result: usize) -> c_int { + c_int::from(result == usize::MAX) + } + + fn create_d_resources_test_prefs() -> FIO_prefs_t { + let mut prefs: FIO_prefs_t = unsafe { std::mem::zeroed() }; + prefs.memLimit = 64; + prefs.checksumFlag = 1; + prefs + } + + fn create_d_resources_test_state( + prefs: &FIO_prefs_t, + context: &mut CreateDResourcesTestState, + ) -> FIO_rust_createDResourcesState { + FIO_rust_createDResourcesState { + callback_context: context as *mut CreateDResourcesTestState as *mut c_void, + prefs: prefs as *const FIO_prefs_t as *mut FIO_prefs_t, + dict_file_name: ptr::null(), + prepare_dictionary: create_d_resources_prepare_dictionary_test, + create_dctx: create_d_resources_create_dctx_test, + set_max_window_size: create_d_resources_set_max_window_size_test, + set_parameter: create_d_resources_set_parameter_test, + load_dictionary: create_d_resources_load_dictionary_test, + create_write_pool: create_d_resources_create_write_pool_test, + create_read_pool: create_d_resources_create_read_pool_test, + is_error: create_d_resources_is_error_test, + } + } + + #[test] + fn create_d_resources_preserves_creation_order_and_scalar_policy() { + let prefs = create_d_resources_test_prefs(); + let mut context = CreateDResourcesTestState::default(); + let state = create_d_resources_test_state(&prefs, &mut context); + + let result = unsafe { FIO_rust_createDResources(&state) }; + + assert_eq!(result, 0); + assert_eq!(context.max_window_size, Some(64)); + assert_eq!(context.ignore_checksum, Some(0)); + assert_eq!( + context.lifecycle, + vec![ + "prepare-dictionary", + "create-dctx", + "set-max-window", + "set-parameter", + "load-dictionary", + "create-write-pool", + "create-read-pool", + ] + ); + } + + #[test] + fn create_d_resources_short_circuits_on_decoder_parameter_error() { + let prefs = create_d_resources_test_prefs(); + let mut context = CreateDResourcesTestState { + fail_at: Some("set-parameter"), + ..CreateDResourcesTestState::default() + }; + let state = create_d_resources_test_state(&prefs, &mut context); + + let result = unsafe { FIO_rust_createDResources(&state) }; + + assert_eq!(result, usize::MAX); + assert_eq!( + context.lifecycle, + vec![ + "prepare-dictionary", + "create-dctx", + "set-max-window", + "set-parameter", + ] + ); + } + #[cfg(feature = "decompression")] #[derive(Default)] struct ZstdErrorHelpTestState {