diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100644 index 00000000000..a39cf155772 --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,83 @@ +name: FreeBSD CI + +# Triggers the workflow on push or pull request or on demand +on: + workflow_dispatch: + push: + pull_request: + branches: [ develop ] + paths-ignore: + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - 'doc/**' + - 'release_docs/**' + - 'ACKNOWLEDGEMENTS' + - 'LICENSE**' + - '**.md' + +# Using concurrency to cancel any in-progress job or run +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + freebsd-build-and-test: + runs-on: ubuntu-latest + name: FreeBSD ${{ matrix.freebsd-version }} Build and Test + + # Don't run the action if the commit message says to skip CI + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + strategy: + fail-fast: false + matrix: + freebsd-version: ['13.5', '14.3', '15.0'] + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Build and test on FreeBSD + uses: vmactions/freebsd-vm@v1 + with: + release: ${{ matrix.freebsd-version }} + usesh: true + prepare: | + pkg install -y cmake ninja pkgconf bash curl + run: | + set -e + + # Configure the build + mkdir build + cd build + cmake -C ../config/cmake/cacheinit.cmake \ + -G Ninja \ + --log-level=VERBOSE \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DHDF5_ENABLE_ALL_WARNINGS:BOOL=ON \ + -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ + -DHDF5_BUILD_CPP_LIB:BOOL=ON \ + -DHDF5_BUILD_FORTRAN:BOOL=OFF \ + -DHDF5_BUILD_JAVA:BOOL=OFF \ + -DHDF5_BUILD_DOC:BOOL=OFF \ + -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ + -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=ON \ + -DLIBAEC_USE_LOCALCONTENT:BOOL=OFF \ + -DZLIB_USE_LOCALCONTENT:BOOL=OFF \ + -DHDF5_TEST_API:BOOL=ON \ + -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ + -DENABLE_EXTENDED_TESTS:BOOL=OFF \ + .. + echo "" + + # Build + cmake --build . --parallel 3 --config Release + echo "" + + # Run tests + ctest . --parallel 2 -C Release -V + echo "" diff --git a/.github/workflows/openbsd.yml b/.github/workflows/openbsd.yml new file mode 100644 index 00000000000..b3ceb68c977 --- /dev/null +++ b/.github/workflows/openbsd.yml @@ -0,0 +1,94 @@ +name: OpenBSD CI + +# Triggers the workflow on push or pull request or on demand +on: + workflow_dispatch: + push: + pull_request: + branches: [ develop ] + paths-ignore: + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - 'doc/**' + - 'release_docs/**' + - 'ACKNOWLEDGEMENTS' + - 'LICENSE**' + - '**.md' + +# Using concurrency to cancel any in-progress job or run +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + openbsd-build-and-test: + runs-on: ubuntu-latest + name: OpenBSD ${{ matrix.openbsd-version }} Build and Test + + # Don't run the action if the commit message says to skip CI + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + strategy: + fail-fast: false + matrix: + openbsd-version: ['7.5'] + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Build and test on OpenBSD + uses: vmactions/openbsd-vm@v1 + with: + release: ${{ matrix.openbsd-version }} + usesh: true + prepare: | + echo "https://ftp.openbsd.org/pub/OpenBSD" > /etc/installurl + pkg_add cmake gmake pkgconf curl gcc-11.2.0p11 + run: | + set -e + + # Set up library path for gcc + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + + # Get number of processors (OpenBSD uses sysctl in /sbin) + NPROC=$(/sbin/sysctl -n hw.ncpu) + echo "Number of processors: $NPROC" + + # Configure the build + mkdir build + cd build + cmake -C ../config/cmake/cacheinit.cmake \ + --log-level=VERBOSE \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=egcc \ + -DCMAKE_CXX_COMPILER=eg++ \ + -DCMAKE_MAKE_PROGRAM=gmake \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DHDF5_ENABLE_ALL_WARNINGS:BOOL=ON \ + -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ + -DHDF5_BUILD_CPP_LIB:BOOL=OFF \ + -DHDF5_BUILD_FORTRAN:BOOL=OFF \ + -DHDF5_BUILD_JAVA:BOOL=OFF \ + -DHDF5_BUILD_DOC:BOOL=OFF \ + -DHDF5_BUILD_HL_LIB:BOOL=OFF \ + -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ + -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=ON \ + -DLIBAEC_USE_LOCALCONTENT:BOOL=OFF \ + -DZLIB_USE_LOCALCONTENT:BOOL=OFF \ + -DHDF5_TEST_API:BOOL=ON \ + -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ + -DENABLE_EXTENDED_TESTS:BOOL=OFF \ + .. + echo "" + + # Build + cmake --build . --parallel $NPROC + echo "" + + # Run tests + ctest . --parallel $NPROC + echo "" diff --git a/config/ConfigureChecks.cmake b/config/ConfigureChecks.cmake index 72b9552f7a0..ae2948ed037 100644 --- a/config/ConfigureChecks.cmake +++ b/config/ConfigureChecks.cmake @@ -443,6 +443,13 @@ CHECK_FUNCTION_EXISTS (asprintf ${HDF_PREFIX}_HAVE_ASPRINTF) CHECK_FUNCTION_EXISTS (vasprintf ${HDF_PREFIX}_HAVE_VASPRINTF) CHECK_FUNCTION_EXISTS (waitpid ${HDF_PREFIX}_HAVE_WAITPID) +# Check for reentrant qsort variants (qsort_r on Unix/BSD, qsort_s on Windows) +CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP) +CHECK_FUNCTION_EXISTS (qsort_s _HAVE_QSORT_S_TMP) +if (_HAVE_QSORT_R_TMP OR _HAVE_QSORT_S_TMP) + set (${HDF_PREFIX}_HAVE_QSORT_REENTRANT 1) +endif () + #----------------------------------------------------------------------------- # sigsetjmp is special; may actually be a macro #----------------------------------------------------------------------------- diff --git a/src/H5RT.c b/src/H5RT.c index 606fd08fc69..8a4dd62e856 100644 --- a/src/H5RT.c +++ b/src/H5RT.c @@ -394,7 +394,9 @@ H5RT__bulk_load(H5RT_node_t *node, int rank, H5RT_leaf_t *leaves, size_t count, if (prev_sort_dim != rank - 1) { assert(prev_sort_dim < rank - 1); sort_dim = prev_sort_dim + 1; - HDqsort_r((void *)leaves, count, sizeof(H5RT_leaf_t), H5RT__leaf_compare, (void *)&sort_dim); + if (H5_UNLIKELY(HDqsort_r((void *)leaves, count, sizeof(H5RT_leaf_t), H5RT__leaf_compare, + (void *)&sort_dim) < 0)) + HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSORT, FAIL, "failed to sort R-tree leaves"); } else { sort_dim = prev_sort_dim; @@ -459,6 +461,11 @@ H5RT__bulk_load(H5RT_node_t *node, int rank, H5RT_leaf_t *leaves, size_t count, * On success, the R-tree takes ownership of the caller-allocated * leaves array. * + * NOTE: This routine uses a global variable internally, and + * is therefore not thread-safe. See the 'qsort_r_threadsafe' + * branch of the HDF5 GitHub repository for a beta + * implementation that is threadsafe. + * * Return: A valid pointer to the new R-tree on success/NULL on failure * *------------------------------------------------------------------------- diff --git a/src/H5private.h b/src/H5private.h index 19e02e14e50..7c0c1dbfa8f 100644 --- a/src/H5private.h +++ b/src/H5private.h @@ -650,8 +650,13 @@ H5_DLL H5_ATTR_CONST int Nflock(int fd, int operation); #endif /* HDflock */ #if defined(H5_HAVE_WIN32_API) || defined(H5_HAVE_DARWIN) || (defined(__FreeBSD__) && __FreeBSD__ < 14) -H5_DLL void HDqsort_context(void *base, size_t nel, size_t size, - int (*compar)(const void *, const void *, void *), void *arg); +H5_DLL herr_t HDqsort_context(void *base, size_t nel, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); +#endif + +#ifndef H5_HAVE_QSORT_REENTRANT +H5_DLL herr_t HDqsort_fallback(void *base, size_t nel, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); #endif #ifndef HDfseek @@ -770,10 +775,17 @@ H5_DLL void HDqsort_context(void *base, size_t nel, size_t size, #define HDunsetenv(S) unsetenv(S) #endif #ifndef HDqsort_r -#ifdef H5_HAVE_DARWIN +#ifdef H5_HAVE_QSORT_REENTRANT +#if defined(H5_HAVE_DARWIN) || (defined(__FreeBSD__) && __FreeBSD__ < 14) +/* Darwin and FreeBSD < 14 use BSD-style qsort_r with different signature/argument order */ #define HDqsort_r(B, N, S, C, A) HDqsort_context(B, N, S, C, A) #else -#define HDqsort_r(B, N, S, C, A) qsort_r(B, N, S, C, A) +/* Wrap native GNU qsort_r to vacuously return success */ +#define HDqsort_r(B, N, S, C, A) (qsort_r(B, N, S, C, A), SUCCEED) +#endif +#else +/* No native qsort_r/qsort_s available - use fallback implementation */ +#define HDqsort_r(B, N, S, C, A) HDqsort_fallback(B, N, S, C, A) #endif #endif #ifndef HDvasprintf diff --git a/src/H5pubconf.h.in b/src/H5pubconf.h.in index 93b28b5327f..82fc2f5f804 100644 --- a/src/H5pubconf.h.in +++ b/src/H5pubconf.h.in @@ -258,6 +258,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine H5_HAVE_PWD_H @H5_HAVE_PWD_H@ +/* Define to 1 if you have a reentrant qsort function (qsort_r or qsort_s). */ +#cmakedefine H5_HAVE_QSORT_REENTRANT @H5_HAVE_QSORT_REENTRANT@ + /* Define whether the Read-Only S3 virtual file driver (VFD) should be compiled */ #cmakedefine H5_HAVE_ROS3_VFD @H5_HAVE_ROS3_VFD@ diff --git a/src/H5system.c b/src/H5system.c index 8095b304aed..4610c770aa2 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1441,7 +1441,7 @@ HDqsort_context_wrapper_func(void *wrapper_arg, const void *a, const void *b) return w->gnu_compar(a, b, w->gnu_arg); } -void +herr_t HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), void *arg) { @@ -1454,5 +1454,53 @@ HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, /* Old BSD-style: context parameter comes before comparator function */ qsort_r(base, nel, size, &wrapper, HDqsort_context_wrapper_func); #endif + return SUCCEED; } #endif + +/* + * HDqsort_fallback - Fallback qsort implementation for platforms without qsort_r/qsort_s + * + * This implementation is not threadsafe, since it uses a global variable to store the + * comparator context, then uses standard qsort(). A beta branch of a threadsafe implementation + * of these routines may be found in the 'qsort_r_threadsafe' branch of the HDF5 GitHub repository. + * + */ +#ifndef H5_HAVE_QSORT_REENTRANT + +typedef struct HDqsort_fallback_context_t { + int (*gnu_compar)(const void *, const void *, void *); + void *gnu_arg; +} HDqsort_fallback_context_t; + +/* Non-threadsafe: use global variable */ +static HDqsort_fallback_context_t *HDqsort_fallback_global_ctx = NULL; + +static int +HDqsort_fallback_wrapper(const void *a, const void *b) +{ + /* Call the original GNU-style comparator with context from global */ + return HDqsort_fallback_global_ctx->gnu_compar(a, b, HDqsort_fallback_global_ctx->gnu_arg); +} + +herr_t +HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), + void *arg) +{ + HDqsort_fallback_context_t ctx; + + ctx.gnu_compar = compar; + ctx.gnu_arg = arg; + + /* Store context in global variable */ + HDqsort_fallback_global_ctx = &ctx; + + qsort(base, nel, size, HDqsort_fallback_wrapper); + + /* Clear the global pointer */ + HDqsort_fallback_global_ctx = NULL; + + return SUCCEED; +} + +#endif /* !H5_HAVE_QSORT_REENTRANT */