diff --git a/java/src-jni/jni/h5zImp.c b/java/src-jni/jni/h5zImp.c index b458533fa69..3cef8c22f8d 100644 --- a/java/src-jni/jni/h5zImp.c +++ b/java/src-jni/jni/h5zImp.c @@ -187,6 +187,8 @@ Java_hdf_hdf5lib_H5_H5Zconfig_1get_1param__Ljava_lang_String_2Ljava_lang_String_ H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: key string is NULL"); if (NULL == out) H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array is NULL"); + if (ENVPTR->GetArrayLength(ENVONLY, out) < 1) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array must have at least one element"); PIN_JAVA_STRING(ENVONLY, params, c_params, &isCopy1, "H5Zconfig_get_param: params not pinned"); PIN_JAVA_STRING(ENVONLY, key, c_key, &isCopy2, "H5Zconfig_get_param: key not pinned"); @@ -238,6 +240,8 @@ Java_hdf_hdf5lib_H5_H5Zconfig_1get_1param__Ljava_lang_String_2Ljava_lang_String_ H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: key string is NULL"); if (NULL == out) H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array is NULL"); + if (ENVPTR->GetArrayLength(ENVONLY, out) < 1) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array must have at least one element"); PIN_JAVA_STRING(ENVONLY, params, c_params, &isCopy1, "H5Zconfig_get_param: params not pinned"); PIN_JAVA_STRING(ENVONLY, key, c_key, &isCopy2, "H5Zconfig_get_param: key not pinned"); @@ -289,6 +293,8 @@ Java_hdf_hdf5lib_H5_H5Zconfig_1get_1param__Ljava_lang_String_2Ljava_lang_String_ H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: key string is NULL"); if (NULL == out) H5_NULL_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array is NULL"); + if (ENVPTR->GetArrayLength(ENVONLY, out) < 1) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "H5Zconfig_get_param: out array must have at least one element"); PIN_JAVA_STRING(ENVONLY, params, c_params, &isCopy1, "H5Zconfig_get_param: params not pinned"); PIN_JAVA_STRING(ENVONLY, key, c_key, &isCopy2, "H5Zconfig_get_param: key not pinned"); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e8f662fe575..53ce4d1182a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1016,6 +1016,35 @@ if (HDF5_C_HAS_FVISIBILITY_HIDDEN) ) endif () +# -fvisibility=hidden above only protects a *shared* libhdf5 -- it has no +# effect on a static libhdf5.a, whose object files still export tomlc17's +# ordinary global symbols (toml_parse, toml_free, ...), so an application +# statically linking libhdf5.a alongside its own copy of tomlc17 can hit a +# duplicate-symbol link error or silent symbol interposition. Force-include +# h5_toml_prefix.h (an HDF5-authored file, not part of the pristine vendored +# copy) ahead of tomlc17.c's own content so every tomlc17_xxx symbol it +# defines is renamed to H5Z__toml_c17_xxx via plain #define -- this needs no +# edit to the pristine tomlc17.c/.h files, so their checked-in SHA-256 hashes +# in src/tomlc17/README.md remain valid. H5Zconfig.c, the sole HDF5 caller, +# picks up the same renaming via its own explicit #include of the same file. +if (MSVC) + set_property (SOURCE ${HDF5_SRC_DIR}/tomlc17/tomlc17.c APPEND PROPERTY COMPILE_OPTIONS + "/FI${HDF5_SRC_DIR}/tomlc17/h5_toml_prefix.h" + ) +else () + check_c_compiler_flag ("-include ${HDF5_SRC_DIR}/tomlc17/h5_toml_prefix.h" HDF5_C_HAS_INCLUDE_FLAG) + if (HDF5_C_HAS_INCLUDE_FLAG) + set_property (SOURCE ${HDF5_SRC_DIR}/tomlc17/tomlc17.c APPEND PROPERTY COMPILE_OPTIONS + "-include" "${HDF5_SRC_DIR}/tomlc17/h5_toml_prefix.h" + ) + else () + message (WARNING + "Compiler does not support -include; the vendored tomlc17 symbols will " + "NOT be prefixed and may collide with an application's own copy of " + "tomlc17 when statically linked. See src/CMakeLists.txt.") + endif () +endif () + # Vendored third-party code should not be held to HDF5_ENABLE_WARNINGS_AS_ERRORS' # own strict internal warning set: it legitimately trips several of them # (e.g. -Werror=cast-align in its pointer-arithmetic tokenizer), which would diff --git a/src/H5Opline.c b/src/H5Opline.c index 3a55dafcc2d..ba251139f6e 100644 --- a/src/H5Opline.c +++ b/src/H5Opline.c @@ -763,8 +763,12 @@ H5O_pline_set_version(H5F_t *f, H5O_pline_t *pline) /* A filter carrying a verbatim configuration string needs the version-3 * encoding to persist it. Request v3 only when the file's high bound - * admits it; otherwise fall back silently to the current version -- the - * strings are simply not written and introspection relies on get_config. */ + * admits it; if it doesn't, fail outright instead of silently falling + * back to an older version that drops the string on encode (see the + * `pline->version >= H5O_PLINE_VERSION_3` guards in H5O__pline_encode()) + * -- a caller who set a config string is entitled to know it was not + * persisted, rather than getting a success return and a file that + * silently no longer round-trips the exact string they set. */ if (version < H5O_PLINE_VERSION_3) { bool have_config = false; @@ -774,8 +778,16 @@ H5O_pline_set_version(H5F_t *f, H5O_pline_t *pline) break; } - if (have_config && H5O_pline_ver_bounds[H5F_HIGH_BOUND(f)] >= H5O_PLINE_VERSION_3) - version = H5O_PLINE_VERSION_3; + if (have_config) { + if (H5O_pline_ver_bounds[H5F_HIGH_BOUND(f)] >= H5O_PLINE_VERSION_3) + version = H5O_PLINE_VERSION_3; + else + HGOTO_ERROR(H5E_PLINE, H5E_BADRANGE, FAIL, + "filter configuration string cannot be persisted: file's high library " + "version bound does not support the version-3 filter pipeline message " + "required to store it (raise the bound with H5Pset_libver_bounds(), or " + "append the filter without a configuration string)"); + } } /* Version bounds check */ diff --git a/src/H5Pocpl.c b/src/H5Pocpl.c index ed0d3f4cd7d..22f7410cba4 100644 --- a/src/H5Pocpl.c +++ b/src/H5Pocpl.c @@ -1447,14 +1447,23 @@ H5P__ocrt_pipeline_dec(const void **_pp, void *_value) assert(enc_size < 256); UINT64DECODE_VAR(*pp, config_len, enc_size); - /* This decode callback has no end-of-buffer pointer to bound - * the memcpy below against (unlike H5O__pline_decode, which - * validates against p_end for the on-disk equivalent of this - * same field) -- capping config_len here is what keeps a - * corrupted/malicious H5Pdecode() buffer from driving an - * unbounded allocation and an unbounded read past the real - * buffer, and also prevents (size_t)config_len + 1 from - * wrapping on a maximal config_len. */ + /* H5P_prp_decode_func_t (the public typedef every property + * decode callback, including this one, must match) is + * herr_t (*)(const void **buf, void *value) -- there is no + * end-of-buffer pointer anywhere in this call chain, down to + * H5Pdecode() itself, whose contract is "the user is + * responsible for passing in the correct buffer" (see its + * doxygen). That is a pre-existing, systemic property of every + * field this function decodes (cd_nelmts/cd_values above are + * equally unbounded against the real buffer length) -- adding + * an end pointer here alone would not close that, and doing so + * for real would mean changing a public callback typedef used + * by every third-party property, not a fix scoped to this + * field. The H5Z_CONFIG_STRING_MAX cap below is therefore only + * a bound on how FAR a malformed/corrupted config_len can walk + * past the real buffer on a bad H5Pdecode() input -- not a + * guarantee that it can't happen at all -- and it also keeps + * (size_t)config_len + 1 from wrapping on a maximal value. */ if (config_len > H5Z_CONFIG_STRING_MAX) { filter.cd_values = (unsigned *)H5MM_xfree(filter.cd_values); HGOTO_ERROR(H5E_PLIST, H5E_BADVALUE, FAIL, "filter config string exceeds maximum length"); @@ -2345,6 +2354,8 @@ H5Pget_filter_params_by_idx(hid_t plist_id, unsigned idx, char *params_buf, size size_t copy_len = (needed < params_buf_size - 1) ? needed : params_buf_size - 1; H5MM_memcpy(params_buf, filter->config, copy_len); params_buf[copy_len] = '\0'; + if (copy_len < needed) + HGOTO_ERROR(H5E_PLIST, H5E_OVERFLOW, FAIL, "params_buf too small for stored config string"); } HGOTO_DONE(SUCCEED); @@ -2352,9 +2363,15 @@ H5Pget_filter_params_by_idx(hid_t plist_id, unsigned idx, char *params_buf, size /* Trigger a plugin load if the filter isn't registered yet, so get_config * is available on the first query rather than only subsequent ones - * (mirrors H5Zget_filter_class_info()). Availability itself is ignored - * here; the fallback path below handles filters that remain unavailable. */ - (void)H5Z_filter_avail(filter->id); + * (mirrors H5Zget_filter_class_info()). A "not available" result is + * ignored here; the fallback path below handles filters that remain + * unavailable. A genuine failure (e.g. H5PL_load() found a plugin but + * H5Z_register() rejected it) pushes a real error onto the stack, which + * we must not leave for an unrelated later H5Eprint()/H5Ewalk() to + * surface confusingly -- clear it explicitly instead of discarding the + * return value outright. */ + if (H5Z_filter_avail(filter->id) < 0) + H5E_clear_stack(); /* Attempt to find the entry (try plugin load if not yet registered) */ (void)H5Z_find_entry(true, filter->id, &entry); @@ -2396,6 +2413,9 @@ H5Pget_filter_params_by_idx(hid_t plist_id, unsigned idx, char *params_buf, size size_t copy_len = (out_size < params_buf_size - 1) ? out_size : params_buf_size - 1; H5MM_memcpy(params_buf, tmp_buf, copy_len); params_buf[copy_len] = '\0'; + if (copy_len < out_size) + HGOTO_ERROR(H5E_PLIST, H5E_OVERFLOW, FAIL, + "params_buf too small for get_config-reconstructed string"); } } /* end if params_buf */ } /* end if get_config */ @@ -2442,6 +2462,9 @@ H5Pget_filter_params_by_idx(hid_t plist_id, unsigned idx, char *params_buf, size size_t copy_len = (true_len < params_buf_size - 1) ? true_len : params_buf_size - 1; H5MM_memcpy(params_buf, tmp_buf, copy_len); params_buf[copy_len] = '\0'; + if (copy_len < true_len) + HGOTO_ERROR(H5E_PLIST, H5E_OVERFLOW, FAIL, + "params_buf too small for synthesized cd_values string"); } } } /* end else (fallback) */ diff --git a/src/H5Ppublic.h b/src/H5Ppublic.h index 674803c544c..d08a1595c8f 100644 --- a/src/H5Ppublic.h +++ b/src/H5Ppublic.h @@ -2973,6 +2973,15 @@ H5_DLL herr_t H5Pmodify_filter_by_idx(hid_t plist_id, unsigned filter_idx, unsig * (excluding NUL) in \p params_len, then allocate \p params_len + 1 * bytes and call again with \p params_buf_size = \p params_len + 1. * + * \note If \p params_buf is non-NULL but \p params_buf_size is smaller + * than the required size, \p params_buf is still filled with a + * truncated, NUL-terminated string (in case the caller wants the + * partial content), \p params_len (if non-NULL) is still set to the + * true, untruncated required length, and the function fails with a + * minor error code of #H5E_OVERFLOW. Check the return value, not + * just whether \p params_buf came back non-empty, to detect + * truncation. + * * \since 3.0.0 */ H5_DLL herr_t H5Pget_filter_params_by_idx(hid_t plist_id, unsigned idx, char *params_buf, diff --git a/src/H5Z.c b/src/H5Z.c index 93bc095b532..7d4ce463d99 100644 --- a/src/H5Z.c +++ b/src/H5Z.c @@ -442,10 +442,14 @@ H5Z_register(const H5Z_class2_t *cls) /* H5Z_register() is typed as const H5Z_class2_t *, but external callers * (H5Zregister, H5PL_load) may pass a H5Z_class3_t * cast to that type. - * Re-sniff the version here so that the description field - * (at the same offset as can_apply in H5Z_class2_t) is never misread. - * Do NOT "simplify" away this check - the v3 dispatch must happen inside - * H5Z_register, not only at the H5Zregister API boundary. */ + * version is the first field in both structs, so it can be read safely + * through either type; re-sniff it here before touching anything past + * set_local, since H5Z_class3_t's filter field is a wider-signature + * H5Z_func2_t (not H5Z_func_t) and the struct carries three more fields + * (set_config, get_config, description) that H5Z_class2_t does not have + * at all -- treating a v3-cast struct as v2 past that point would misread + * those fields. Do NOT "simplify" away this check - the v3 dispatch must + * happen inside H5Z_register, not only at the H5Zregister API boundary. */ if (cls->version == H5Z_CLASS3_T_VERS_INTERNAL) { if (H5Z_register3((const H5Z_class3_t *)cls) < 0) HGOTO_ERROR(H5E_PLINE, H5E_CANTINIT, FAIL, "unable to register filter"); diff --git a/src/H5Zconfig.c b/src/H5Zconfig.c index f298e19f172..2b53975dfbf 100644 --- a/src/H5Zconfig.c +++ b/src/H5Zconfig.c @@ -40,6 +40,12 @@ #include "H5MMprivate.h" /* Memory management */ #include "H5Zpkg.h" /* Filter internals */ +/* Renames every public tomlc17 symbol to an H5Z__toml_c17_-prefixed name so + * a statically-linked libhdf5.a cannot collide with an application's own + * copy of tomlc17 (see h5_toml_prefix.h for the full rationale). Must be + * included before tomlc17.h so every call site below picks up the renamed + * declarations. */ +#include "tomlc17/h5_toml_prefix.h" #include "tomlc17/tomlc17.h" /* Append one source character to the output buffer, or skip it if full. */ @@ -337,6 +343,30 @@ done: FUNC_LEAVE_NOAPI(ret_value) } /* end H5Z_canonicalize_params() */ +/* + * H5Z__count_table_keys - recursively count leaf key=value assignments in a + * parsed TOML table, including keys nested inside inline tables / dotted-key + * groups (a nested table itself is not counted, only its own leaves are). + * Used to enforce H5Z_CONFIG_MAX_PARAMS. + */ +static size_t +H5Z__count_table_keys(toml_datum_t tab) +{ + size_t count = 0; + int32_t i; + + for (i = 0; i < tab.u.tab.size; i++) { + toml_datum_t v = tab.u.tab.value[i]; + + if (v.type == TOML_TABLE) + count += H5Z__count_table_keys(v); + else + count++; + } + + return count; +} + /* * H5Z__toml_parse_params - wrap params as a TOML document and parse it. * @@ -399,6 +429,14 @@ H5Z__toml_parse_params(const char *params, toml_result_t *tr_out, toml_datum_t * "malformed filter parameter string (not a valid TOML inline table)"); } + if (H5Z__count_table_keys(*ptab_out) > H5Z_CONFIG_MAX_PARAMS) { + toml_free(*tr_out); + memset(tr_out, 0, sizeof(*tr_out)); + HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, + "filter parameter string exceeds H5Z_CONFIG_MAX_PARAMS (%d key-value pairs)", + H5Z_CONFIG_MAX_PARAMS); + } + done: H5MM_xfree(wrapped); H5MM_xfree(expanded); diff --git a/src/H5Zdeflate.c b/src/H5Zdeflate.c index b9bca8d197e..06e2de09b05 100644 --- a/src/H5Zdeflate.c +++ b/src/H5Zdeflate.c @@ -37,7 +37,7 @@ static herr_t H5Z__deflate_get_config(unsigned flags, size_t cd_nelmts, const un /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN const H5Z_class3_t H5Z_DEFLATE[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_DEFLATE, /* Filter id number */ 1, /* encoder_present flag (set to true) */ 1, /* decoder_present flag (set to true) */ diff --git a/src/H5Zdevelop.h b/src/H5Zdevelop.h index 64e05b9a2e7..e6892523365 100644 --- a/src/H5Zdevelop.h +++ b/src/H5Zdevelop.h @@ -305,11 +305,16 @@ typedef struct H5Z_class3_t { H5Z_get_config_func_t get_config; /**< Parameter string reconstruction; may be NULL */ const char *description; /**< Human-readable description of the filter (e.g., "Deflate (zlib) general-purpose compression"); may be NULL. Appended last (not - inserted after \c name) so that a caller positionally initializing - this struct from an H5Z_class2_t literal -- version, id, + inserted after \c name) so the first eight fields -- version, id, encoder_present, decoder_present, name, can_apply, set_local, filter -- - and simply appending the new v3 fields keeps every original field in - its original slot. */ + share the same slots an H5Z_class2_t literal would occupy positionally. + \warning That slot correspondence does NOT make \c filter itself + reusable: it is typed \c H5Z_func2_t here (9 parameters, adding + \p dxpl_id, \p scaled, \p ndims) versus \c H5Z_func_t (7 parameters) + in H5Z_class2_t. A callback written against the old signature must be + rewritten to accept and, if unneeded, ignore the three new parameters + before it can be assigned to this field -- reusing the old function + pointer as-is is undefined behavior. */ } H5Z_class3_t; //! diff --git a/src/H5Zfletcher32.c b/src/H5Zfletcher32.c index cc971622e24..b9cfc47f23e 100644 --- a/src/H5Zfletcher32.c +++ b/src/H5Zfletcher32.c @@ -24,7 +24,7 @@ static size_t H5Z__filter_fletcher32(unsigned flags, size_t cd_nelmts, const uns /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN const H5Z_class3_t H5Z_FLETCHER32[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_FLETCHER32, /* Filter id number */ 1, /* encoder_present flag (set to true) */ 1, /* decoder_present flag (set to true) */ diff --git a/src/H5Znbit.c b/src/H5Znbit.c index 1ab5beb3e85..5e87b7f472d 100644 --- a/src/H5Znbit.c +++ b/src/H5Znbit.c @@ -85,7 +85,7 @@ static void H5Z__nbit_compress(unsigned char *data, unsigned d_nelmts, unsigne /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN H5Z_class3_t H5Z_NBIT[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_NBIT, /* Filter id number */ 1, /* Assume encoder present: check before registering */ 1, /* decoder_present flag (set to true) */ diff --git a/src/H5Zscaleoffset.c b/src/H5Zscaleoffset.c index 5ea1a14545e..85e41d657bb 100644 --- a/src/H5Zscaleoffset.c +++ b/src/H5Zscaleoffset.c @@ -95,7 +95,7 @@ static herr_t H5Z__scaleoffset_get_config(unsigned flags, size_t cd_nelmts, cons /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN H5Z_class3_t H5Z_SCALEOFFSET[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_SCALEOFFSET, /* Filter id number */ 1, /* Assume encoder present: check before registering */ 1, /* decoder_present flag (set to true) */ diff --git a/src/H5Zshuffle.c b/src/H5Zshuffle.c index 802a88581a4..5530a7e9e64 100644 --- a/src/H5Zshuffle.c +++ b/src/H5Zshuffle.c @@ -28,7 +28,7 @@ static size_t H5Z__filter_shuffle(unsigned flags, size_t cd_nelmts, const unsign /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN const H5Z_class3_t H5Z_SHUFFLE[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_SHUFFLE, /* Filter id number */ 1, /* encoder_present flag (set to true) */ 1, /* decoder_present flag (set to true) */ diff --git a/src/H5Zszip.c b/src/H5Zszip.c index 9e8583d1a66..651eadd7e05 100644 --- a/src/H5Zszip.c +++ b/src/H5Zszip.c @@ -41,7 +41,7 @@ static herr_t H5Z__szip_get_config(unsigned flags, size_t cd_nelmts, const unsig /* This message derives from H5Z */ H5_ATTR_VISIBILITY_HIDDEN H5Z_class3_t H5Z_SZIP[1] = {{ - 2, /* H5Z_class3_t version */ + H5Z_CLASS3_T_VERS, /* H5Z_class3_t version */ H5Z_FILTER_SZIP, /* Filter id number */ 1, /* Assume encoder present: check before registering */ 1, /* decoder_present flag (set to true) */ diff --git a/src/tomlc17/h5_toml_prefix.h b/src/tomlc17/h5_toml_prefix.h new file mode 100644 index 00000000000..14633687515 --- /dev/null +++ b/src/tomlc17/h5_toml_prefix.h @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright by The HDF Group. * + * All rights reserved. * + * * + * This file is part of HDF5. The full HDF5 copyright notice, including * + * terms governing use, modification, and redistribution, is contained in * + * the LICENSE file, which can be found at the root of the source code * + * distribution tree, or in https://www.hdfgroup.org/licenses. * + * If you do not have access to either file, you may request a copy from * + * help@hdfgroup.org. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * h5_toml_prefix.h - HDF5-authored symbol-prefixing shim for the vendored + * tomlc17 library (src/tomlc17/tomlc17.c, tomlc17.h -- pristine, unmodified + * copies; see src/tomlc17/README.md for provenance and the update procedure). + * + * -fvisibility=hidden (applied to tomlc17.c in src/CMakeLists.txt) only + * strips these symbols from the dynamic symbol table of a *shared* libhdf5; + * it does nothing for a static libhdf5.a, whose object files still export + * ordinary global symbols. An application that statically links libhdf5.a + * alongside its own copy of tomlc17 (same version or not) would then hit a + * duplicate-symbol link error, or worse, silently link against whichever + * copy the linker picks first. + * + * This header renames every public tomlc17 symbol to an H5Z__toml_-prefixed + * name via plain object-like macros, force-included ahead of both tomlc17.c + * itself (via a compiler flag in src/CMakeLists.txt, since editing an + * #include into the pristine tomlc17.c would defeat the point) and every + * HDF5 file that calls into it (H5Zconfig.c, via an explicit #include + * immediately before "tomlc17/tomlc17.h"). Because a #define is a textual + * substitution, this renames both tomlc17.c's function *definitions* and + * every call site consistently, without editing a single byte of the + * pristine vendored files -- their checked-in SHA-256 hashes in + * src/tomlc17/README.md remain valid. + * + * When updating the vendored copy (see README.md's "Updating the vendored + * copy" section), diff the new tomlc17.h's public API against the list + * below and add/remove entries to match -- an unprefixed new public + * function would silently reintroduce the exact collision this header + * exists to prevent. + */ + +#ifndef H5_TOML_PREFIX_H +#define H5_TOML_PREFIX_H + +#define toml_parse H5Z__toml_c17_parse +#define toml_parse_named H5Z__toml_c17_parse_named +#define toml_parse_file H5Z__toml_c17_parse_file +#define toml_parse_file_named H5Z__toml_c17_parse_file_named +#define toml_parse_file_ex H5Z__toml_c17_parse_file_ex +#define toml_free H5Z__toml_c17_free +#define toml_get H5Z__toml_c17_get +#define toml_seek H5Z__toml_c17_seek +#define toml_merge H5Z__toml_c17_merge +#define toml_equiv H5Z__toml_c17_equiv +#define toml_default_option H5Z__toml_c17_default_option +#define toml_set_option H5Z__toml_c17_set_option + +#endif /* H5_TOML_PREFIX_H */ diff --git a/test/tfilter2.c b/test/tfilter2.c index caaf298a9bc..124061d3c8e 100644 --- a/test/tfilter2.c +++ b/test/tfilter2.c @@ -2640,8 +2640,14 @@ test_config_string_ondisk(hid_t fapl) TEST_ERROR; PASSED(); - /* --- fmt-05: libver high bound below V300 silently omits the string --- */ - TESTING("config string: silent v2 downgrade when libver bound too low"); + /* --- fmt-05: libver high bound below V300 rejects the create, rather + * than silently persisting the dataset without the config string --- + * (H5O_pline_set_version() now fails outright instead of downgrading + * the pipeline message version and dropping the string: a caller who + * set a config string is entitled to know it will not round-trip, + * rather than getting a success return and a file that silently no + * longer carries the exact string they set.) */ + TESTING("config string: create fails when libver bound too low to persist it"); if ((fapl_dg = H5Pcopy(fapl)) < 0) TEST_ERROR; if (H5Pset_libver_bounds(fapl_dg, H5F_LIBVER_EARLIEST, H5F_LIBVER_V200) < 0) @@ -2650,26 +2656,36 @@ test_config_string_ondisk(hid_t fapl) TEST_ERROR; if ((file = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_dg)) < 0) TEST_ERROR; + H5E_BEGIN_TRY + { + dset = H5Dcreate2(file, "dset", H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl, H5P_DEFAULT); + } + H5E_END_TRY + if (dset >= 0) + TEST_ERROR; /* must fail: string cannot be persisted at this libver bound */ + dset = H5I_INVALID_HID; + if (H5Pclose(dcpl) < 0) + TEST_ERROR; + dcpl = H5I_INVALID_HID; + + /* The same filter with no config string (a fresh DCPL, raw cd_values + * form) is unaffected by the bound and still succeeds. */ + { + hsize_t chunk[2] = {4, 4}; + unsigned cd[1] = {5}; + + if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0) + TEST_ERROR; + if (H5Pset_chunk(dcpl, 2, chunk) < 0) + TEST_ERROR; + if (H5Pset_filter(dcpl, CFG_ONDISK_FILTER_ID, 0, 1, cd) < 0) + TEST_ERROR; + } if ((dset = H5Dcreate2(file, "dset", H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0) TEST_ERROR; - if (H5Dclose(dset) < 0 || H5Pclose(dcpl) < 0 || H5Fclose(file) < 0) + if (H5Dclose(dset) < 0 || H5Pclose(dcpl) < 0 || H5Fclose(file) < 0 || H5Pclose(fapl_dg) < 0) TEST_ERROR; - dset = dcpl = file = H5I_INVALID_HID; - /* Plugin still registered: getter falls back to get_config ("level = 5"), - * proving the verbatim string was not persisted at v2. */ - if ((file = H5Fopen(filename, H5F_ACC_RDONLY, fapl)) < 0) - TEST_ERROR; - if ((dset = H5Dopen2(file, "dset", H5P_DEFAULT)) < 0) - TEST_ERROR; - if ((dcpl_out = H5Dget_create_plist(dset)) < 0) - TEST_ERROR; - if (cfg_ondisk_get_params(dcpl_out, pbuf, sizeof(pbuf)) < 0) - TEST_ERROR; - if (strcmp(pbuf, "level = 5") != 0) /* get_config form, not the stored "level=5" */ - TEST_ERROR; - if (H5Pclose(dcpl_out) < 0 || H5Dclose(dset) < 0 || H5Fclose(file) < 0 || H5Pclose(fapl_dg) < 0) - TEST_ERROR; - dcpl_out = dset = file = fapl_dg = H5I_INVALID_HID; + dset = dcpl = file = fapl_dg = H5I_INVALID_HID; PASSED(); /* --- fmt-07: H5Pmodify_filter clears the stored string --- */ diff --git a/tools/lib/h5tools_dump.c b/tools/lib/h5tools_dump.c index 1073257a0f5..d6c512f88ce 100644 --- a/tools/lib/h5tools_dump.c +++ b/tools/lib/h5tools_dump.c @@ -3893,13 +3893,16 @@ h5tools_dump_dcpl(FILE *stream, const h5tool_format_t *info, h5tools_context_t * const char *filter_descr = NULL; /* library-owned, no free needed */ bool have_extra; /* true if this filter has a PARAMS_STRING and/or DESCRIPTION */ - if (!params_str_buf || !params_annot) { - free(params_str_buf); - free(params_annot); - continue; - } - - params_annot[0] = '\0'; + /* On allocation failure, do NOT skip the whole filter: fall through + * with both buffers left NULL, so the guard below simply omits the + * PARAMS_STRING/DESCRIPTION decoration (have_extra stays false) while + * the filter's own FILTERS{} entry is still rendered by the switch + * below. Silently dropping an entire filter from -p output would make + * the DDL look complete while actually under-reporting the pipeline. + * The unconditional free() calls at the end of this loop iteration + * handle cleanup either way (free(NULL) is a no-op). */ + if (params_annot) + params_annot[0] = '\0'; cd_nelmts = NELMTS(cd_values); filtn = H5Pget_filter2(dcpl_id, (unsigned)i, &filt_flags, &cd_nelmts, cd_values, @@ -3912,8 +3915,10 @@ h5tools_dump_dcpl(FILE *stream, const h5tool_format_t *info, h5tools_context_t * } /* -p prints PARAMS_STRING and DESCRIPTION nested inside this - * filter's own FILTERS{} entry. */ - if (dcpl_id >= 0 && ctx->show_filter_params) { + * filter's own FILTERS{} entry. Both buffers must have allocated + * successfully above; if either failed, skip decoration for this + * filter (have_extra stays false below) rather than the whole entry. */ + if (dcpl_id >= 0 && ctx->show_filter_params && params_str_buf && params_annot) { size_t plen = 0; if (H5Pget_filter_params_by_idx(dcpl_id, (unsigned)i, params_str_buf, params_buf_size, &plen) >= 0 &&