/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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 COPYING 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.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Programmer:  Robb Matzke
 *              Thursday, November 19, 1998
 *
 * Purpose:  Provides support functions for most of the hdf5 tests cases.
 *
 */

#undef NDEBUG /*override -DNDEBUG      */

#include "h5test.h"
#include "H5srcdir.h"
#include "H5srcdir_str.h"

/* Necessary for h5_verify_cached_stabs() */
#define H5G_FRIEND /*suppress error about including H5Gpkg      */
#define H5G_TESTING
#include "H5Gpkg.h"

#ifdef H5_HAVE_WIN32_API
#include <process.h>
#endif /* H5_HAVE_WIN32_API */

/*
 * Define these environment variables or constants to influence functions in
 * this test support library.  The environment variable is used in preference
 * to the cpp constant.  If neither is defined then use some default value.
 *
 * HDF5_DRIVER:    This string describes what low level file driver to
 *      use for HDF5 file access.
 *
 * HDF5_LIBVER_BOUNDS:    This string describes what library version bounds to
 *      use for HDF5 file access.  See h5_get_libver_fapl() for details.
 *
 * HDF5_PREFIX:    A string to add to the beginning of all serial test
 *      file names.  This can be used to run tests in a
 *      different file system (e.g., "/tmp" or "/tmp/myname").
 *      The prefix will be separated from the base file name
 *      by a slash. See h5_fixname() for details.
 *
 * HDF5_PARAPREFIX:  A string to add to the beginning of all parallel test
 *      file names.  This can be used to tell MPIO what driver
 *      to use (e.g., "gfs:", "ufs:", or "nfs:") or to use a
 *      different file system (e.g., "/tmp" or "/tmp/myname").
 *      The prefix will be separated from the base file name
 *      by a slash. See h5_fixname() for details.
 *
 */
/*
 * In a parallel machine, the filesystem suitable for compiling is
 * unlikely a parallel file system that is suitable for parallel I/O.
 * There is no standard pathname for the parallel file system.  /tmp
 * is about the best guess.
 */
#ifndef HDF5_PARAPREFIX
#define HDF5_PARAPREFIX ""
#endif
char *paraprefix = NULL; /* for command line option para-prefix */
#ifdef H5_HAVE_PARALLEL
MPI_Info h5_io_info_g = MPI_INFO_NULL; /* MPI INFO object for IO */
#endif

#define READ_BUF_SIZE 65536

/*
 * These are the letters that are appended to the file name when generating
 * names for the split and multi drivers. They are:
 *
 *   m: All meta data when using the split driver.
 *  s: The userblock, superblock, and driver info block
 *  b: B-tree nodes
 *  r: Dataset raw data
 *  g: Global heap
 *  l: local heap (object names)
 *  o: object headers
 */
static const char *multi_letters = "msbrglo";

/* Length of multi-file VFD filename buffers */
#define H5TEST_MULTI_FILENAME_LEN 1024

/* Temporary file for sending signal messages */
#define TMP_SIGNAL_FILE "tmp_signal_file"

/* The # of seconds to wait for the message file--used by h5_wait_message() */
#define MESSAGE_TIMEOUT 300 /* Timeout in seconds */

/* Buffer to construct path in and return pointer to */
static char srcdir_path[1024];

/* Buffer to construct file in and return pointer to */
static char srcdir_testpath[1024];

/*  The strings that correspond to library version bounds H5F_libver_t in H5Fpublic.h */
/*  This is used by h5_get_version_string() */
const char *LIBVER_NAMES[] = {"earliest", /* H5F_LIBVER_EARLIEST = 0  */
                              "v18",      /* H5F_LIBVER_V18 = 1       */
                              "v110",     /* H5F_LIBVER_V110 = 2      */
                              "v112",     /* H5F_LIBVER_V112 = 3      */
                              "latest",   /* H5F_LIBVER_V114 = 4      */
                              NULL};

/* Previous error reporting function */
static H5E_auto2_t err_func = NULL;

/* Global variables for testing */
size_t   n_tests_run_g     = 0;
size_t   n_tests_passed_g  = 0;
size_t   n_tests_failed_g  = 0;
size_t   n_tests_skipped_g = 0;
uint64_t vol_cap_flags_g   = H5VL_CAP_FLAG_NONE;

static herr_t h5_errors(hid_t estack, void *client_data);
static char  *h5_fixname_real(const char *base_name, hid_t fapl, const char *_suffix, char *fullname,
                              size_t size, hbool_t nest_printf, hbool_t subst_for_superblock);

/*-------------------------------------------------------------------------
 * Function:  h5_errors
 *
 * Purpose:  Displays the error stack after printing "*FAILED*".
 *
 * Return:  Success:  0
 *
 *    Failure:  -1
 *
 * Programmer:  Robb Matzke
 *    Wednesday, March  4, 1998
 *
 *-------------------------------------------------------------------------
 */
static herr_t
h5_errors(hid_t estack, void H5_ATTR_UNUSED *client_data)
{
    H5_FAILED();
    H5Eprint2(estack, stdout);
    return 0;
}

/*-------------------------------------------------------------------------
 * Function:  h5_clean_files
 *
 * Purpose:  Cleanup temporary test files (always).
 *    base_name contains the list of test file names.
 *
 * Return:  void
 *
 * Programmer:  Neil Fortner
 *              June 1, 2015
 *
 *-------------------------------------------------------------------------
 */
void
h5_clean_files(const char *base_name[], hid_t fapl)
{
    int i;

    for (i = 0; base_name[i]; i++) {
        h5_delete_test_file(base_name[i], fapl);
    }

    /* Close the FAPL used to access the file */
    H5Pclose(fapl);
} /* end h5_clean_files() */

/*-------------------------------------------------------------------------
 * Function:    h5_delete_test_file
 *
 * Purpose      Clean up temporary test files.
 *
 *              When a test calls h5_fixname() to get a VFD-dependent
 *              test file name, this function can be used to clean it up.
 *
 * Return:      void
 *
 *              Since this is a cleanup file, we don't care if it fails.
 *
 * Programmer:  Dana Robinson
 *              February 2016
 *
 *-------------------------------------------------------------------------
 */
void
h5_delete_test_file(const char *base_name, hid_t fapl)
{
    char filename[1024]; /* VFD-dependent filename to delete */

    /* Get the VFD-dependent filename */
    if (NULL == h5_fixname(base_name, fapl, filename, sizeof(filename)))
        return;

    H5E_BEGIN_TRY
    {
        H5Fdelete(filename, fapl);
    }
    H5E_END_TRY;

} /* end h5_delete_test_file() */

/*-------------------------------------------------------------------------
 * Function:    h5_delete_all_test_files
 *
 * Purpose      Clean up temporary test files.
 *
 *              When a test calls h5_fixname() get a VFD-dependent
 *              test file name, this function can be used to clean it up.
 *
 *              This function takes an array of filenames that ends with
 *              a NULL string and cleans them all.
 *
 * Return:      void
 *
 *              Since this is a cleanup file, we don't care if it fails.
 *
 * Programmer:  Dana Robinson
 *              February 2016
 *
 *-------------------------------------------------------------------------
 */
void
h5_delete_all_test_files(const char *base_name[], hid_t fapl)
{
    int i; /* iterator                             */

    for (i = 0; base_name[i]; i++) {
        h5_delete_test_file(base_name[i], fapl);
    } /* end for */

} /* end h5_delete_all_test_files() */

/*-------------------------------------------------------------------------
 * Function:  h5_cleanup
 *
 * Purpose:  Cleanup temporary test files.
 *    base_name contains the list of test file names.
 *    The file access property list is also closed.
 *
 * Return:  Non-zero if cleanup actions were performed; zero otherwise.
 *
 * Programmer:  Albert Cheng
 *              May 28, 1998
 *
 *-------------------------------------------------------------------------
 */
int
h5_cleanup(const char *base_name[], hid_t fapl)
{
    int retval = 0;

    if (GetTestCleanup()) {
        /* Clean up files in base_name, and the FAPL */
        h5_clean_files(base_name, fapl);

        retval = 1;
    } /* end if */

    /* Restore the original error reporting routine */
    h5_restore_err();

    return retval;
} /* end h5_cleanup() */

/*-------------------------------------------------------------------------
 * Function:    h5_test_shutdown
 *
 * Purpose:     Performs any special test cleanup required before the test
 *              ends.
 *
 *              NOTE: This function should normally only be called once
 *              in a given test, usually just before leaving main(). It
 *              is intended for use in the single-file unit tests, not
 *              testhdf5.
 *
 * Return:      void
 *
 * Programmer:  Dana Robinson
 *              February 2016
 *
 *-------------------------------------------------------------------------
 */
void
h5_test_shutdown(void)
{

    /* Restore the original error reporting routine */
    h5_restore_err();
} /* end h5_test_shutdown() */

/*-------------------------------------------------------------------------
 * Function:    h5_restore_err
 *
 * Purpose:     Restore the default error handler.
 *
 * Return:      N/A
 *
 * Programmer:  Quincey Koziol
 *              Sept 10, 2015
 *
 *-------------------------------------------------------------------------
 */
void
h5_restore_err(void)
{
    /* Restore the original error reporting routine */
    HDassert(err_func != NULL);
    H5Eset_auto2(H5E_DEFAULT, err_func, NULL);
    err_func = NULL;
}

/*-------------------------------------------------------------------------
 * Function:  h5_reset
 *
 * Purpose:  Reset the library by closing it.
 *
 * Return:  void
 *
 * Programmer:  Robb Matzke
 *              Friday, November 20, 1998
 *
 *-------------------------------------------------------------------------
 */
void
h5_reset(void)
{
    HDfflush(stdout);
    HDfflush(stderr);
    H5close();

    /* Save current error stack reporting routine and redirect to our local one */
    HDassert(err_func == NULL);
    H5Eget_auto2(H5E_DEFAULT, &err_func, NULL);
    H5Eset_auto2(H5E_DEFAULT, h5_errors, NULL);

/*
 * I commented this chunk of code out because it's not clear what diagnostics
 *      were being output and under what circumstances, and creating this file
 *      is throwing off debugging some of the tests.  I can't see any _direct_
 *      harm in keeping this section of code, but I can't see any _direct_
 *      benefit right now either.  If we figure out under which circumstances
 *      diagnostics are being output, we should enable this behavior based on
 *      appropriate configure flags/macros.  QAK - 2007/12/20
 */
#ifdef OLD_WAY
    {
        char filename[1024];

        /*
         * Cause the library to emit some diagnostics early so they don't
         * interfere with other formatted output.
         */
        HDsnprintf(filename, sizeof(filename), "/tmp/h5emit-%05d.h5", HDgetpid());
        H5E_BEGIN_TRY
        {
            hid_t file = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
            hid_t grp  = H5Gcreate2(file, "emit", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
            H5Gclose(grp);
            H5Fclose(file);
            HDunlink(filename);
        }
        H5E_END_TRY;
    }
#endif /* OLD_WAY */
}

/*-------------------------------------------------------------------------
 * Function:    h5_test_init
 *
 * Purpose:     Performs any special actions before the test begins.
 *
 *              NOTE: This function should normally only be called once
 *              in a given test, usually at the beginning of main(). It
 *              is intended for use in the single-file unit tests, not
 *              testhdf5.
 *
 * Return:      void
 *
 * Programmer:  Dana Robinson
 *              February 2016
 *
 *-------------------------------------------------------------------------
 */
void
h5_test_init(void)
{
    HDfflush(stdout);
    HDfflush(stderr);
    H5close();

    /* Save current error stack reporting routine and redirect to our local one */
    HDassert(err_func == NULL);
    H5Eget_auto2(H5E_DEFAULT, &err_func, NULL);
    H5Eset_auto2(H5E_DEFAULT, h5_errors, NULL);
} /* end h5_test_init() */

/*-------------------------------------------------------------------------
 * Function:  h5_fixname
 *
 * Purpose:  Create a file name from a file base name like `test' and
 *    return it through the FULLNAME (at most SIZE characters
 *    counting the null terminator). The full name is created by
 *    prepending the contents of HDF5_PREFIX (separated from the
 *    base name by a slash) and appending a file extension based on
 *    the driver supplied, resulting in something like
 *    `ufs:/u/matzke/test.h5'.
 *
 * Return:  Success:  The FULLNAME pointer.
 *
 *    Failure:  NULL if BASENAME or FULLNAME is the null
 *        pointer or if FULLNAME isn't large enough for
 *        the result.
 *
 * Programmer:  Robb Matzke
 *              Thursday, November 19, 1998
 *
 *-------------------------------------------------------------------------
 */
char *
h5_fixname(const char *base_name, hid_t fapl, char *fullname, size_t size)
{
    return (h5_fixname_real(base_name, fapl, ".h5", fullname, size, FALSE, FALSE));
}

/*-------------------------------------------------------------------------
 * Function:    h5_fixname_superblock
 *
 * Purpose:     Like h5_fixname() but returns the name of the file you'd
 *              open to find the superblock. Useful for when you have to
 *              open a file with open(2) but the h5_fixname() string
 *              contains stuff like format strings.
 *
 * Return:      Success:    The FULLNAME pointer.
 *
 *              Failure:    NULL if BASENAME or FULLNAME is the null
 *                          pointer or if FULLNAME isn't large enough for
 *                          the result.
 *
 * Programmer:  Dana Robinson
 *              Spring 2019
 *
 *-------------------------------------------------------------------------
 */
char *
h5_fixname_superblock(const char *base_name, hid_t fapl_id, char *fullname, size_t size)
{
    return (h5_fixname_real(base_name, fapl_id, ".h5", fullname, size, FALSE, TRUE));
}

/*-------------------------------------------------------------------------
 * Function:  h5_fixname_no_suffix
 *
 * Purpose:  Same as h5_fixname but with no suffix appended
 *
 * Return:  Success:  The FULLNAME pointer.
 *
 *    Failure:  NULL if BASENAME or FULLNAME is the null
 *        pointer or if FULLNAME isn't large enough for
 *        the result.
 *
 *-------------------------------------------------------------------------
 */
char *
h5_fixname_no_suffix(const char *base_name, hid_t fapl, char *fullname, size_t size)
{
    return (h5_fixname_real(base_name, fapl, NULL, fullname, size, FALSE, FALSE));
}

/*-------------------------------------------------------------------------
 * Function:  h5_fixname_printf
 *
 * Purpose:  Same as h5_fixname but returns a filename that can be passed
 *    through a printf-style function once before being passed to the file
 *    driver.  Basically, replaces all % characters used by the file
 *    driver with %%.
 *
 * Return:  Success:  The FULLNAME pointer.
 *
 *    Failure:  NULL if BASENAME or FULLNAME is the null
 *        pointer or if FULLNAME isn't large enough for
 *        the result.
 *
 * Programmer:  Neil Fortner
 *              Wednesday, July 15, 2015
 *
 *-------------------------------------------------------------------------
 */
char *
h5_fixname_printf(const char *base_name, hid_t fapl, char *fullname, size_t size)
{
    return (h5_fixname_real(base_name, fapl, ".h5", fullname, size, TRUE, FALSE));
}

/*-------------------------------------------------------------------------
 * Function:  h5_fixname_real
 *
 * Purpose:  Create a file name from a file base name like `test' and
 *    return it through the FULLNAME (at most SIZE characters
 *    counting the null terminator). The full name is created by
 *    prepending the contents of HDF5_PREFIX (separated from the
 *    base name by a slash) and appending a file extension based on
 *    the driver supplied, resulting in something like
 *    `ufs:/u/matzke/test.h5'.
 *
 * Return:  Success:  The FULLNAME pointer.
 *
 *    Failure:  NULL if BASENAME or FULLNAME is the null
 *        pointer or if FULLNAME isn't large enough for
 *        the result.
 *
 * Programmer:  Robb Matzke
 *              Thursday, November 19, 1998
 *
 *-------------------------------------------------------------------------
 */
static char *
h5_fixname_real(const char *base_name, hid_t fapl, const char *_suffix, char *fullname, size_t size,
                hbool_t nest_printf, hbool_t subst_for_superblock)
{
    const char *prefix         = NULL;
    const char *driver_env_var = NULL; /* HDF5_DRIVER environment variable     */
    char       *ptr, last = '\0';
    const char *suffix = _suffix;
    size_t      i, j;
    hid_t       driver     = -1;
    int         isppdriver = 0; /* if the driver is MPI parallel */

    if (!base_name || !fullname || size < 1)
        return NULL;

    HDmemset(fullname, 0, size);

    /* Determine if driver is set by environment variable. If it is,
     * only generate a suffix if fixing the filename for the superblock
     * file. */
    driver_env_var = HDgetenv(HDF5_DRIVER);
    if (driver_env_var && (H5P_DEFAULT == fapl) && subst_for_superblock)
        fapl = H5P_FILE_ACCESS_DEFAULT;

    /* figure out the suffix */
    if (H5P_DEFAULT != fapl) {
        if ((driver = H5Pget_driver(fapl)) < 0)
            return NULL;

        if (suffix) {
            if (H5FD_FAMILY == driver) {
                if (subst_for_superblock)
                    suffix = "-000000.h5";
                else
                    suffix = nest_printf ? "-%%06d.h5" : "-%06d.h5";
            }
            else if (H5FD_MULTI == driver) {

                /* Check the HDF5_DRIVER environment variable in case
                 * we are using the split driver since both of those
                 * use the multi VFD under the hood.
                 */
#ifdef HDF5_DRIVER
                /* Use the environment variable, then the compile-time constant */
                if (!driver_env_var)
                    driver_env_var = HDF5_DRIVER;
#endif
                if (driver_env_var && !HDstrcmp(driver_env_var, "split")) {
                    /* split VFD */
                    if (subst_for_superblock)
                        suffix = ".h5.meta";
                }
                else {
                    /* multi VFD */
                    if (subst_for_superblock)
                        suffix = "-s.h5";
                    else
                        suffix = NULL;
                }
            }
        }
    }

    /* Must first check fapl is not H5P_DEFAULT (-1) because H5FD_XXX
     * could be of value -1 if it is not defined.
     */
    isppdriver = ((H5P_DEFAULT != fapl) || driver_env_var) && (H5FD_MPIO == driver);

    /* Check HDF5_NOCLEANUP environment setting.
     * (The #ifdef is needed to prevent compile failure in case MPI is not
     * configured.)
     */
    if (isppdriver) {
#ifdef H5_HAVE_PARALLEL
        if (getenv_all(MPI_COMM_WORLD, 0, HDF5_NOCLEANUP))
            SetTestNoCleanup();
#endif /* H5_HAVE_PARALLEL */
    }
    else {
        if (HDgetenv(HDF5_NOCLEANUP))
            SetTestNoCleanup();
    }

    /* Check what prefix to use for test files. Process HDF5_PARAPREFIX and
     * HDF5_PREFIX.
     * Use different ones depending on parallel or serial driver used.
     * (The #ifdef is needed to prevent compile failure in case MPI is not
     * configured.)
     */
    if (isppdriver) {
#ifdef H5_HAVE_PARALLEL
        /*
         * For parallel:
         *      First use command line option, then the environment
         *      variable, then try the constant
         */
        static int explained = 0;

        prefix = (paraprefix ? paraprefix : getenv_all(MPI_COMM_WORLD, 0, "HDF5_PARAPREFIX"));

        if (!prefix && !explained) {
            /* print hint by process 0 once. */
            int mpi_rank;

            MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank);

            if (mpi_rank == 0)
                HDprintf("*** Hint ***\n"
                         "You can use environment variable HDF5_PARAPREFIX to "
                         "run parallel test files in a\n"
                         "different directory or to add file type prefix. e.g.,\n"
                         "   HDF5_PARAPREFIX=pfs:/PFS/user/me\n"
                         "   export HDF5_PARAPREFIX\n"
                         "*** End of Hint ***\n");

            explained = TRUE;
#ifdef HDF5_PARAPREFIX
            prefix = HDF5_PARAPREFIX;
#endif /* HDF5_PARAPREFIX */
        }
#endif /* H5_HAVE_PARALLEL */
    }
    else {
        /*
         * For serial:
         *      First use the environment variable, then try the constant
         */
        prefix = HDgetenv("HDF5_PREFIX");

#ifdef HDF5_PREFIX
        if (!prefix)
            prefix = HDF5_PREFIX;
#endif /* HDF5_PREFIX */
    }

    /* Prepend the prefix value to the base name */
    if (prefix && *prefix) {
        if (isppdriver) {
            /* This is a parallel system */
            char *subdir;

            if (!HDstrcmp(prefix, HDF5_PARAPREFIX)) {
                /*
                 * If the prefix specifies the HDF5_PARAPREFIX directory, then
                 * default to using the "/tmp/$USER" or "/tmp/$LOGIN"
                 * directory instead.
                 */
                char *user, *login;

                user   = HDgetenv("USER");
                login  = HDgetenv("LOGIN");
                subdir = (user ? user : login);

                if (subdir) {
                    for (i = 0; i < size && prefix[i]; i++)
                        fullname[i] = prefix[i];

                    fullname[i++] = '/';

                    for (j = 0; i < size && subdir[j]; ++i, ++j)
                        fullname[i] = subdir[j];
                }
            }

            if (!fullname[0]) {
                /* We didn't append the prefix yet */
                HDstrncpy(fullname, prefix, size);
                fullname[size - 1] = '\0';
            }

            if (HDstrlen(fullname) + HDstrlen(base_name) + 1 < size) {
                /*
                 * Append the base_name with a slash first. Multiple
                 * slashes are handled below.
                 */
                h5_stat_t buf;

                if (HDstat(fullname, &buf) < 0)
                    /* The directory doesn't exist just yet */
                    if (HDmkdir(fullname, (mode_t)0755) < 0 && errno != EEXIST)
                        /*
                         * We couldn't make the "/tmp/${USER,LOGIN}"
                         * subdirectory.  Default to PREFIX's original
                         * prefix value.
                         */
                        HDstrcpy(fullname, prefix);

                HDstrcat(fullname, "/");
                HDstrcat(fullname, base_name);
            }
            else {
                /* Buffer is too small */
                return NULL;
            }
        }
        else {
            if (HDsnprintf(fullname, size, "%s/%s", prefix, base_name) == (int)size)
                /* Buffer is too small */
                return NULL;
        }
    }
    else if (HDstrlen(base_name) >= size) {
        /* Buffer is too small */
        return NULL;
    }
    else {
        HDstrcpy(fullname, base_name);
    }

    /* Append a suffix */
    if (suffix) {
        if (HDstrlen(fullname) + HDstrlen(suffix) >= size)
            return NULL;

        HDstrcat(fullname, suffix);
    }

    /* Remove any double slashes in the filename */
    for (ptr = fullname, i = j = 0; ptr && i < size; i++, ptr++) {
        if (*ptr != '/' || last != '/')
            fullname[j++] = *ptr;

        last = *ptr;
    }

    return fullname;
}

/*-------------------------------------------------------------------------
 * Function:  h5_rmprefix
 *
 * Purpose:  This "removes" the MPIO driver prefix part of the file name
 *    by returning a pointer that points at the non-prefix component
 *              part of the file name.  E.g.,
 *        Input      Return
 *        pfs:/scratch1/dataX    /scratch1/dataX
 *        /scratch2/dataY           /scratch2/dataY
 *    Note that there is no change to the original file name.
 *
 * Return:  Success:  a pointer at the non-prefix part.
 *
 * Programmer:  Albert Cheng; Jun  1, 2006
 *
 *-------------------------------------------------------------------------
 */
H5_ATTR_PURE const char *
h5_rmprefix(const char *filename)
{
    const char *ret_ptr;

    if ((ret_ptr = HDstrstr(filename, ":")) == NULL)
        ret_ptr = filename;
    else
        ret_ptr++;

    return (ret_ptr);
}

/*-------------------------------------------------------------------------
 * Function:    h5_fileaccess
 *
 * Purpose:     Returns a file access template which is the default template
 *              but with a file driver, VOL connector, or libver bound set
 *              according to a constant or environment variable
 *
 * Return:      Success:    A file access property list
 *              Failure:    H5I_INVALID_HID
 *
 * Programmer:  Robb Matzke
 *              Thursday, November 19, 1998
 *
 *-------------------------------------------------------------------------
 */
hid_t
h5_fileaccess(void)
{
    hid_t fapl_id = H5I_INVALID_HID;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        goto error;

    /* Check for libver bounds */
    if (h5_get_libver_fapl(fapl_id) < 0)
        goto error;

    return fapl_id;

error:
    if (fapl_id != H5I_INVALID_HID)
        H5Pclose(fapl_id);
    return H5I_INVALID_HID;
} /* end h5_fileaccess() */

/*-------------------------------------------------------------------------
 * Function:    h5_fileaccess_flags
 *
 * Purpose:     Returns a file access template which is the default template
 *              but with a file driver, VOL connector, or libver bound set
 *              according to a constant or environment variable
 *
 * Return:      Success:    A file access property list
 *              Failure:    H5I_INVALID_HID
 *
 * Programmer:  Robb Matzke
 *              Thursday, November 19, 1998
 *
 *-------------------------------------------------------------------------
 */
hid_t
h5_fileaccess_flags(unsigned flags)
{
    hid_t fapl_id = H5I_INVALID_HID;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        goto error;

    /* Check for libver bounds */
    if ((flags & H5_FILEACCESS_LIBVER) && h5_get_libver_fapl(fapl_id) < 0)
        goto error;

    return fapl_id;

error:
    if (fapl_id != H5I_INVALID_HID)
        H5Pclose(fapl_id);
    return H5I_INVALID_HID;
} /* end h5_fileaccess_flags() */

/*-------------------------------------------------------------------------
 * Function:    h5_get_libver_fapl
 *
 * Purpose:     Sets the library version bounds for a FAPL according to the
 *              value in the constant or environment variable "HDF5_LIBVER_BOUNDS".
 *
 * Return:      Success:    0
 *              Failure:    -1
 *
 * Programmer:  Quincey Koziol
 *              November 2018
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_get_libver_fapl(hid_t fapl)
{
    const char *env   = NULL; /* HDF5_DRIVER environment variable     */
    const char *tok   = NULL; /* strtok pointer                       */
    char       *lasts = NULL; /* Context pointer for strtok_r() call */
    char        buf[1024];    /* buffer for tokenizing HDF5_DRIVER    */

    /* Get the environment variable, if it exists */
    env = HDgetenv("HDF5_LIBVER_BOUNDS");
#ifdef HDF5_LIBVER_BOUNDS
    /* Use the environment variable, then the compile-time constant */
    if (!env)
        env = HDF5_LIBVER_BOUNDS;
#endif

    /* If the environment variable was not set, just return
     * without modifying the FAPL.
     */
    if (!env || !*env)
        goto done;

    /* Get the first 'word' of the environment variable.
     * If it's nothing (environment variable was whitespace)
     * just return the default fapl.
     */
    HDstrncpy(buf, env, sizeof(buf));
    buf[sizeof(buf) - 1] = '\0';
    if (NULL == (tok = HDstrtok_r(buf, " \t\n\r", &lasts)))
        goto done;

    if (!HDstrcmp(tok, "latest")) {
        /* use the latest format */
        if (H5Pset_libver_bounds(fapl, H5F_LIBVER_LATEST, H5F_LIBVER_LATEST) < 0)
            goto error;
    } /* end if */
    else {
        /* Unknown setting */
        goto error;
    } /* end else */

done:
    return 0;

error:
    return -1;
} /* end h5_get_libver_fapl() */

/*-------------------------------------------------------------------------
 * Function:  h5_no_hwconv
 *
 * Purpose:  Turn off hardware data type conversions.
 *
 * Return:  void
 *
 * Programmer:  Robb Matzke
 *              Friday, November 20, 1998
 *
 *-------------------------------------------------------------------------
 */
void
h5_no_hwconv(void)
{
    H5Tunregister(H5T_PERS_HARD, NULL, (hid_t)-1, (hid_t)-1, NULL);
}

/*-------------------------------------------------------------------------
 * Function:  h5_show_hostname
 *
 * Purpose:  Show hostname.  Show process ID if in MPI environment.
 *
 * Return:  void
 *
 * Programmer:  Albert Cheng
 *              2002/04/22
 *
 *-------------------------------------------------------------------------
 */
void
h5_show_hostname(void)
{
    char hostname[80];
#ifdef H5_HAVE_WIN32_API
    WSADATA wsaData;
    int     err;
#endif

    /* try show the process or thread id in multiple processes cases*/
#ifdef H5_HAVE_PARALLEL
    {
        int mpi_rank, mpi_initialized, mpi_finalized;

        MPI_Initialized(&mpi_initialized);
        MPI_Finalized(&mpi_finalized);

        if (mpi_initialized && !mpi_finalized) {
            MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank);
            HDprintf("MPI-process %d.", mpi_rank);
        }
        else
            HDprintf("thread 0.");
    }
#else
    HDprintf("thread %" PRIu64 ".", H5TS_thread_id());
#endif
#ifdef H5_HAVE_WIN32_API

    err = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (err != 0) {
        /* could not find a usable WinSock DLL */
        return;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        /* could not find a usable WinSock DLL */
        WSACleanup();
        return;
    }

#endif
#ifdef H5_HAVE_GETHOSTNAME
    if (HDgethostname(hostname, (size_t)80) < 0)
        HDprintf(" gethostname failed\n");
    else
        HDprintf(" hostname=%s\n", hostname);
#else
    HDprintf(" gethostname not supported\n");
#endif
#ifdef H5_HAVE_WIN32_API
    WSACleanup();
#endif
}

#ifdef H5_HAVE_PARALLEL
/*
 * Function:    h5_set_info_object
 * Purpose:     Process environment variables setting to set up MPI Info
 *              object.
 * Return:      0 if all is fine; otherwise non-zero.
 * Programmer:  Albert Cheng, 2002/05/21.
 */
int
h5_set_info_object(void)
{
    char *envp; /* environment pointer */
    int   ret_value = 0;

    /* handle any MPI INFO hints via $HDF5_MPI_INFO */
    if ((envp = HDgetenv("HDF5_MPI_INFO")) != NULL) {
        char *next, *valp;

        valp = envp = next = HDstrdup(envp);

        if (!valp)
            return 0;

        /* create an INFO object if not created yet */
        if (h5_io_info_g == MPI_INFO_NULL)
            MPI_Info_create(&h5_io_info_g);

        do {
            size_t len;
            char  *key_val, *endp, *namep;

            if (*valp == ';')
                valp++;

            /* copy key/value pair into temporary buffer */
            len  = HDstrcspn(valp, ";");
            next = &valp[len];
            if (NULL == (key_val = (char *)HDcalloc(1, len + 1)))
                return -1;

            /* increment the next pointer past the terminating semicolon */
            if (*next == ';')
                ++next;

            namep = HDstrncpy(key_val, valp, len);

            /* pass up any beginning whitespaces */
            while (*namep && (*namep == ' ' || *namep == '\t'))
                namep++;

            if (!*namep)
                continue; /* was all white space, so move to next k/v pair */

            /* eat up any ending white spaces */
            endp = &namep[HDstrlen(namep) - 1];

            while (endp && (*endp == ' ' || *endp == '\t'))
                *endp-- = '\0';

            /* find the '=' */
            valp = HDstrchr(namep, '=');

            if (valp != NULL) { /* it's a valid key/value pairing */
                char *tmp_val = valp + 1;

                /* change '=' to \0, move valp down one */
                *valp-- = '\0';

                /* eat up ending whitespace on the "key" part */
                while (*valp == ' ' || *valp == '\t')
                    *valp-- = '\0';

                valp = tmp_val;

                /* eat up beginning whitespace on the "value" part */
                while (*valp == ' ' || *valp == '\t')
                    *valp++ = '\0';

                /* actually set the darned thing */
                if (MPI_SUCCESS != MPI_Info_set(h5_io_info_g, namep, valp)) {
                    HDprintf("MPI_Info_set failed\n");
                    ret_value = -1;
                }
            }

            valp = next;
            HDfree(key_val);
        } while (next && *next);

        HDfree(envp);
    }

    return ret_value;
}

/*
 * Function:    h5_dump_info_object
 * Purpose:     Display content of an MPI Info object
 * Return:      void
 * Programmer:  Albert Cheng 2002/05/21
 */
void
h5_dump_info_object(MPI_Info info)
{
    char key[MPI_MAX_INFO_KEY + 1];
    char value[MPI_MAX_INFO_VAL + 1];
    int  flag;
    int  i, nkeys;

    HDprintf("Dumping MPI Info Object (up to %d bytes per item):\n", MPI_MAX_INFO_VAL);
    if (info == MPI_INFO_NULL) {
        HDprintf("object is MPI_INFO_NULL\n");
    }
    else {
        MPI_Info_get_nkeys(info, &nkeys);
        HDprintf("object has %d items\n", nkeys);
        for (i = 0; i < nkeys; i++) {
            MPI_Info_get_nthkey(info, i, key);
            MPI_Info_get(info, key, MPI_MAX_INFO_VAL, value, &flag);
            HDprintf("%s=%s\n", key, value);
        }
    }
}
#endif /* H5_HAVE_PARALLEL */

/*-------------------------------------------------------------------------
 * Function:  h5_get_file_size
 *
 * Purpose:  Get the current size of a file (in bytes)
 *
 * Return:  Success:  Size of file in bytes
 *    Failure:  -1
 *
 * Programmer:  Quincey Koziol
 *              Saturday, March 22, 2003
 *
 *-------------------------------------------------------------------------
 */
/* Disable warning for "format not a string literal" here -QAK */
/*
 *      This pragma only needs to surround the snprintf() calls with
 *      temp in the code below, but early (4.4.7, at least) gcc only
 *      allows diagnostic pragmas to be toggled outside of functions.
 */
H5_GCC_CLANG_DIAG_OFF("format-nonliteral")
h5_stat_size_t
h5_get_file_size(const char *filename, hid_t fapl)
{
    char      temp[2048]; /* Temporary buffer for file names */
    h5_stat_t sb;         /* Structure for querying file info */
    int       j = 0;

    if (fapl == H5P_DEFAULT) {
        /* Get the file's statistics */
        if (0 == HDstat(filename, &sb))
            return ((h5_stat_size_t)sb.st_size);
    } /* end if */
    else {
        hid_t driver; /* VFD used for file */

        /* Get the driver used when creating the file */
        if ((driver = H5Pget_driver(fapl)) < 0)
            return (-1);

        /* Check for simple cases */
        if (driver == H5FD_SEC2 || driver == H5FD_STDIO || driver == H5FD_CORE ||
#ifdef H5_HAVE_WINDOWS
            driver == H5FD_WINDOWS ||
#endif /* H5_HAVE_WINDOWS */
#ifdef H5_HAVE_DIRECT
            driver == H5FD_DIRECT ||
#endif /* H5_HAVE_DIRECT */
            driver == H5FD_LOG || driver == H5FD_SPLITTER) {
            /* Get the file's statistics */
            if (0 == HDstat(filename, &sb))
                return ((h5_stat_size_t)sb.st_size);
        } /* end if */
        else if (driver == H5FD_MULTI) {
            H5FD_mem_t     mt;
            h5_stat_size_t tot_size       = 0;
            char          *driver_env_var = NULL;

            driver_env_var = HDgetenv(HDF5_DRIVER);
            if (driver_env_var && !HDstrcmp(driver_env_var, "split")) {
                for (mt = H5FD_MEM_DEFAULT; mt < H5FD_MEM_NTYPES; mt++) {
                    if (mt != H5FD_MEM_DRAW && mt != H5FD_MEM_SUPER)
                        continue;

                    /* Create the filename to query */
                    if (mt == H5FD_MEM_DRAW) {
                        HDsnprintf(temp, sizeof temp, "%s.raw", filename);
                    }
                    else {
                        HDsnprintf(temp, sizeof temp, "%s.meta", filename);
                    }

                    /* Check for existence of file */
                    if (0 == HDaccess(temp, F_OK)) {
                        /* Get the file's statistics */
                        if (0 != HDstat(temp, &sb))
                            return (-1);

                        /* Add to total size */
                        tot_size += (h5_stat_size_t)sb.st_size;
                    } /* end if */
                }     /* end for */
            }
            else {
                HDassert(HDstrlen(multi_letters) == H5FD_MEM_NTYPES);
                for (mt = H5FD_MEM_DEFAULT; mt < H5FD_MEM_NTYPES; mt++) {
                    /* Create the filename to query */
                    HDsnprintf(temp, sizeof temp, "%s-%c.h5", filename, multi_letters[mt]);

                    /* Check for existence of file */
                    if (0 == HDaccess(temp, F_OK)) {
                        /* Get the file's statistics */
                        if (0 != HDstat(temp, &sb))
                            return (-1);

                        /* Add to total size */
                        tot_size += (h5_stat_size_t)sb.st_size;
                    } /* end if */
                }     /* end for */
            }

            /* Return total size */
            return (tot_size);
        } /* end if */
#ifdef H5_HAVE_PARALLEL
        else if (driver == H5FD_MPIO) {
            MPI_File   fh; /* MPI file handle used to open the file and verify its size */
            int        mpi_ret;
            MPI_Offset file_size;

            mpi_ret = MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_RDONLY, MPI_INFO_NULL, &fh);
            if (mpi_ret != MPI_SUCCESS)
                return -1;
            mpi_ret = MPI_File_get_size(fh, &file_size);
            if (mpi_ret != MPI_SUCCESS)
                return -1;
            mpi_ret = MPI_File_close(&fh);
            if (mpi_ret != MPI_SUCCESS)
                return -1;

            return file_size;
        }
#endif /* H5_HAVE_PARALLEL */
        else if (driver == H5FD_FAMILY) {
            h5_stat_size_t tot_size = 0;

            /* Try all filenames possible, until we find one that's missing */
            for (j = 0; /*void*/; j++) {
                /* Create the filename to query */
                HDsnprintf(temp, sizeof temp, filename, j);

                /* Check for existence of file */
                if (HDaccess(temp, F_OK) < 0)
                    break;

                /* Get the file's statistics */
                if (0 != HDstat(temp, &sb))
                    return (-1);

                /* Add to total size */
                tot_size += (h5_stat_size_t)sb.st_size;
            } /* end for */

            /* Return total size */
            return (tot_size);
        } /* end if */
        else if (driver == H5FD_SUBFILING) {
            hsize_t size;
            hid_t   fid = H5I_INVALID_HID;

            if ((fid = H5Fopen(filename, H5F_ACC_RDONLY, fapl)) < 0)
                return -1;
            if (H5Fget_filesize(fid, &size) < 0) {
                H5Fclose(fid);
                return -1;
            }

            if (H5Fclose(fid) < 0)
                return -1;

            return (h5_stat_size_t)size;
        }
        else {
            /* Get the file's statistics */
            if (0 == HDstat(filename, &sb))
                return ((h5_stat_size_t)sb.st_size);
        } /* end else */
    }     /* end else */

    return (-1);
} /* end get_file_size() */
H5_GCC_CLANG_DIAG_ON("format-nonliteral")

/*
 * This routine is designed to provide equivalent functionality to 'printf'
 * and allow easy replacement for environments which don't have stdin/stdout
 * available. (i.e. Windows & the Mac)
 */
H5_ATTR_FORMAT(printf, 1, 2)
int
print_func(const char *format, ...)
{
    va_list arglist;
    int     ret_value;

    HDva_start(arglist, format);
    ret_value = HDvprintf(format, arglist);
    HDva_end(arglist);
    return ret_value;
}

#ifdef H5_HAVE_FILTER_SZIP

/*-------------------------------------------------------------------------
 * Function:  h5_szip_can_encode
 *
 * Purpose:  Retrieve the filter config flags for szip, tell if
 *              encoder is available.
 *
 * Return:  1:  decode+encode is enabled
 *    0:  only decode is enabled
 *              -1: other
 *
 * Programmer:
 *
 *-------------------------------------------------------------------------
 */
int
h5_szip_can_encode(void)
{
    unsigned int filter_config_flags;

    H5Zget_filter_info(H5Z_FILTER_SZIP, &filter_config_flags);
    if ((filter_config_flags & (H5Z_FILTER_CONFIG_ENCODE_ENABLED | H5Z_FILTER_CONFIG_DECODE_ENABLED)) == 0) {
        /* filter present but neither encode nor decode is supported (???) */
        return -1;
    }
    else if ((filter_config_flags & (H5Z_FILTER_CONFIG_ENCODE_ENABLED | H5Z_FILTER_CONFIG_DECODE_ENABLED)) ==
             H5Z_FILTER_CONFIG_DECODE_ENABLED) {
        /* decoder only: read but not write */
        return 0;
    }
    else if ((filter_config_flags & (H5Z_FILTER_CONFIG_ENCODE_ENABLED | H5Z_FILTER_CONFIG_DECODE_ENABLED)) ==
             H5Z_FILTER_CONFIG_ENCODE_ENABLED) {
        /* encoder only: write but not read (???) */
        return -1;
    }
    else if ((filter_config_flags & (H5Z_FILTER_CONFIG_ENCODE_ENABLED | H5Z_FILTER_CONFIG_DECODE_ENABLED)) ==
             (H5Z_FILTER_CONFIG_ENCODE_ENABLED | H5Z_FILTER_CONFIG_DECODE_ENABLED)) {
        return 1;
    }
    return (-1);
}
#endif /* H5_HAVE_FILTER_SZIP */

#ifdef H5_HAVE_PARALLEL
/*-------------------------------------------------------------------------
 * Function:  getenv_all
 *
 * Purpose:  Used to get the environment that the root MPI task has.
 *     name specifies which environment variable to look for
 *     val is the string to which the value of that environment
 *     variable will be copied.
 *
 *     NOTE: The pointer returned by this function is only
 *     valid until the next call to getenv_all and the data
 *     stored there must be copied somewhere else before any
 *     further calls to getenv_all take place.
 *
 * Return:  pointer to a string containing the value of the environment variable
 *     NULL if the variable doesn't exist in task 'root's environment.
 *
 * Programmer:  Leon Arber
 *              4/4/05
 *
 *-------------------------------------------------------------------------
 */
char *
getenv_all(MPI_Comm comm, int root, const char *name)
{
    int          mpi_size, mpi_rank, mpi_initialized, mpi_finalized;
    int          len;
    static char *env = NULL;

    HDassert(name);

    MPI_Initialized(&mpi_initialized);
    MPI_Finalized(&mpi_finalized);

    if (mpi_initialized && !mpi_finalized) {
        MPI_Comm_rank(comm, &mpi_rank);
        MPI_Comm_size(comm, &mpi_size);
        HDassert(root < mpi_size);

        /* The root task does the getenv call
         * and sends the result to the other tasks */
        if (mpi_rank == root) {
            env = HDgetenv(name);
            if (env) {
                len = (int)HDstrlen(env);
                MPI_Bcast(&len, 1, MPI_INT, root, comm);
                MPI_Bcast(env, len, MPI_CHAR, root, comm);
            }
            else {
                /* len -1 indicates that the variable was not in the environment */
                len = -1;
                MPI_Bcast(&len, 1, MPI_INT, root, comm);
            }
        }
        else {
            MPI_Bcast(&len, 1, MPI_INT, root, comm);
            if (len >= 0) {
                if (env == NULL)
                    env = (char *)HDmalloc((size_t)len + 1);
                else if (HDstrlen(env) < (size_t)len)
                    env = (char *)HDrealloc(env, (size_t)len + 1);

                MPI_Bcast(env, len, MPI_CHAR, root, comm);
                env[len] = '\0';
            }
            else {
                if (env)
                    HDfree(env);
                env = NULL;
            }
        }
#ifndef NDEBUG
        MPI_Barrier(comm);
#endif
    }
    else {
        /* use original getenv */
        if (env)
            HDfree(env);
        env = HDgetenv(name);
    } /* end if */

    return env;
}

#endif

/*-------------------------------------------------------------------------
 * Function:    h5_make_local_copy
 *
 * Purpose:     Make copy of file.  Some tests write to data files under that
 *              are under version control.  Those tests should make a copy of
 *              the versioned file and write to the copy.  This function
 *              prepends srcdir to the name of the file to be copied and uses
 *              the name of the copy as is.
 *
 * Return:      Success:        0
 *
 *              Failure:        -1
 *
 * Programmer:  Larry Knox
 *              Monday, October 13, 2009
 *
 *-------------------------------------------------------------------------
 */
int
h5_make_local_copy(const char *origfilename, const char *local_copy_name)
{
    int         fd_old = (-1), fd_new = (-1);                    /* File descriptors for copying data */
    ssize_t     nread;                                           /* Number of bytes read in */
    void       *buf      = NULL;                                 /* Buffer for copying data */
    const char *filename = H5_get_srcdir_filename(origfilename); /* Get the test file name to copy */

    if (!filename)
        goto error;

    /* Allocate copy buffer */
    if (NULL == (buf = HDcalloc((size_t)1, (size_t)READ_BUF_SIZE)))
        goto error;

    /* Copy old file into temporary file */
    if ((fd_old = HDopen(filename, O_RDONLY)) < 0)
        goto error;
    if ((fd_new = HDopen(local_copy_name, O_RDWR | O_CREAT | O_TRUNC, H5_POSIX_CREATE_MODE_RW)) < 0)
        goto error;

    /* Copy data */
    while ((nread = HDread(fd_old, buf, (size_t)READ_BUF_SIZE)) > 0)
        if (HDwrite(fd_new, buf, (size_t)nread) < 0)
            goto error;

    /* Close files */
    if (HDclose(fd_old) < 0)
        goto error;
    if (HDclose(fd_new) < 0)
        goto error;

    /* Release memory */
    HDfree(buf);

    return 0;

error:
    /* ignore return values since we're already noted the problem */
    if (fd_old > 0)
        HDclose(fd_old);
    if (fd_new > 0)
        HDclose(fd_new);
    HDfree(buf);
    return -1;
} /* end h5_make_local_copy() */

/*-------------------------------------------------------------------------
 * Function:    h5_verify_cached_stabs_cb
 *
 * Purpose:     Callback function for h5_verify_cached_stabs.
 *
 * Return:      SUCCEED/FAIL
 *
 * Programmer:  Neil Fortner
 *              Tuesday, April 12, 2011
 *
 *-------------------------------------------------------------------------
 */
static herr_t
h5_verify_cached_stabs_cb(hid_t oid, const char H5_ATTR_UNUSED *name, const H5O_info2_t *oinfo,
                          void H5_ATTR_UNUSED *udata)
{
    if (oinfo->type == H5O_TYPE_GROUP)
        return H5G__verify_cached_stabs_test(oid);
    else
        return SUCCEED;
} /* end h5_verify_cached_stabs_cb() */

/*-------------------------------------------------------------------------
 * Function:    h5_verify_cached_stabs
 *
 * Purpose:     Verifies that all groups in every file in base_name have
 *              their symbol table information cached (if present, and if
 *              the parent group also uses a symbol table).  Does not
 *              check that the root group's symbol table information is
 *              cached in the superblock.
 *
 * Return:      Success:        0
 *
 *              Failure:        -1
 *
 * Programmer:  Neil Fortner
 *              Tuesday, April 12, 2011
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_verify_cached_stabs(const char *base_name[], hid_t fapl)
{
    hid_t file = -1;
    char  filename[1024];
    int   i = 0;

    while (base_name[i]) {
        if (h5_fixname(base_name[i], fapl, filename, sizeof(filename)) == NULL)
            continue;

        H5E_BEGIN_TRY
        {
            file = H5Fopen(filename, H5F_ACC_RDONLY, fapl);
        }
        H5E_END_TRY
        if (file < 0) {
            i++;
            continue;
        } /* end if */

        if (H5Ovisit3(file, H5_INDEX_NAME, H5_ITER_NATIVE, h5_verify_cached_stabs_cb, NULL, H5O_INFO_BASIC) <
            0)
            goto error;

        if (H5Fclose(file) < 0)
            goto error;
        file = -1;

        i++;
    } /* end while */

    return 0;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(file);
    }
    H5E_END_TRY;

    return -1;
}

/*-------------------------------------------------------------------------
 * Function:    h5_send_message
 *
 * Purpose:     Sends the specified signal.
 *
 *              In terms of this test framework, a signal consists of a file
 *              on disk. Since there are multiple processes that need to
 *              communicate with each other, they do so by writing and
 *              reading signal files on disk, the names and contents of
 *              which are used to inform a process about when it can
 *              proceed and what it should do next.
 *
 *              This function writes a signal file. The first argument is
 *              the name of the signal file, and the second and third
 *              arguments are the contents of the first two lines of the
 *              signal file. The last two arguments may be NULL.
 *
 * Return:      void
 *
 * Programmer:  Mike McGreevy
 *              August 18, 2010
 *
 *-------------------------------------------------------------------------
 */
void
h5_send_message(const char *send, const char *arg1, const char *arg2)
{
    FILE *signalfile = NULL;

    /* Create signal file (which will send signal to some other process) */
    signalfile = HDfopen(TMP_SIGNAL_FILE, "w+");

    /* Write messages to signal file, if provided */
    if (arg2 != NULL) {
        HDassert(arg1);
        HDfprintf(signalfile, "%s\n%s\n", arg1, arg2);
    } /* end if */
    else if (arg1 != NULL) {
        HDassert(arg2 == NULL);
        HDfprintf(signalfile, "%s\n", arg1);
    } /* end if */
    else {
        HDassert(arg1 == NULL);
        HDassert(arg2 == NULL);
    } /* end else */

    HDfclose(signalfile);

    HDrename(TMP_SIGNAL_FILE, send);
} /* h5_send_message() */

/*-------------------------------------------------------------------------
 * Function:    h5_wait_message
 *
 * Purpose:     Waits for the specified signal.
 *
 *              In terms of this test framework, a signal consists of a file
 *              on disk. Since there are multiple processes that need to
 *              communicate with each other, they do so by writing and
 *              reading signal files on disk, the names and contents of
 *              which are used to inform a process about when it can
 *              proceed and what it should do next.
 *
 *              This function continuously attempts to read the specified
 *              signal file from disk, and only continues once it has
 *              successfully done so (i.e., only after another process has
 *              called the "h5_send_message" function to write the signal file).
 *              This function will then immediately remove the file (i.e.,
 *              to indicate that it has been received and can be reused),
 *              and then exits, allowing the calling function to continue.
 *
 * Return:      void
 *
 * Programmer:  Mike McGreevy
 *              August 18, 2010
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_wait_message(const char *waitfor)
{
    FILE  *returnfile;
    time_t t0, t1;

    /* Start timer. If this function runs for too long (i.e.,
        expected signal is never received), it will
        return failure */
    HDtime(&t0);

    /* Wait for return signal from some other process */
    while ((returnfile = HDfopen(waitfor, "r")) == NULL) {

        /* make note of current time. */
        HDtime(&t1);

        /* If we've been waiting for a signal for too long, then
            it was likely never sent and we should fail rather
            than loop infinitely */
        if (HDdifftime(t1, t0) > MESSAGE_TIMEOUT) {
            HDfprintf(stdout, "Error communicating between processes. Make sure test script is running.\n");
            TEST_ERROR;
        } /* end if */
    }     /* end while */

    HDfclose(returnfile);
    HDunlink(waitfor);

    return SUCCEED;

error:
    return FAIL;
} /* h5_wait_message() */

/* Functions for the dummy VFD class (see below).
 *
 * Useful for testing things like ID handling where we shouldn't mess with the
 * real VFDs.
 */
static H5FD_t *dummy_vfd_open(const char *name, unsigned flags, hid_t fapl_id, haddr_t maxaddr);
static H5FD_t *
dummy_vfd_open(const char H5_ATTR_UNUSED *name, unsigned H5_ATTR_UNUSED flags, hid_t H5_ATTR_UNUSED fapl_id,
               haddr_t H5_ATTR_UNUSED maxaddr)
{
    return NULL;
}

static herr_t dummy_vfd_close(H5FD_t *_file);
static herr_t
dummy_vfd_close(H5FD_t H5_ATTR_UNUSED *_file)
{
    return FAIL;
}

static haddr_t dummy_vfd_get_eoa(const H5FD_t *file, H5FD_mem_t type);
static haddr_t
dummy_vfd_get_eoa(const H5FD_t H5_ATTR_UNUSED *file, H5FD_mem_t H5_ATTR_UNUSED type)
{
    return HADDR_UNDEF;
}

static herr_t dummy_vfd_set_eoa(H5FD_t *_file, H5FD_mem_t type, haddr_t addr);
static herr_t
dummy_vfd_set_eoa(H5FD_t H5_ATTR_UNUSED *_file, H5FD_mem_t H5_ATTR_UNUSED type, haddr_t H5_ATTR_UNUSED addr)
{
    return FAIL;
}

static haddr_t dummy_vfd_get_eof(const H5FD_t *file, H5FD_mem_t type);
static haddr_t
dummy_vfd_get_eof(const H5FD_t H5_ATTR_UNUSED *file, H5FD_mem_t H5_ATTR_UNUSED type)
{
    return HADDR_UNDEF;
}

static herr_t dummy_vfd_read(H5FD_t *_file, H5FD_mem_t type, hid_t fapl_id, haddr_t addr, size_t size,
                             void *buf);
static herr_t
dummy_vfd_read(H5FD_t H5_ATTR_UNUSED *_file, H5FD_mem_t H5_ATTR_UNUSED type, hid_t H5_ATTR_UNUSED fapl_id,
               haddr_t H5_ATTR_UNUSED addr, size_t H5_ATTR_UNUSED size, void H5_ATTR_UNUSED *buf)
{
    return FAIL;
}

static herr_t dummy_vfd_write(H5FD_t *_file, H5FD_mem_t type, hid_t fapl_id, haddr_t addr, size_t size,
                              const void *buf);
static herr_t
dummy_vfd_write(H5FD_t H5_ATTR_UNUSED *_file, H5FD_mem_t H5_ATTR_UNUSED type, hid_t H5_ATTR_UNUSED fapl_id,
                haddr_t H5_ATTR_UNUSED addr, size_t H5_ATTR_UNUSED size, const void H5_ATTR_UNUSED *buf)
{
    return FAIL;
}

/* Dummy VFD with the minimum parameters to make a VFD that can be registered */
#define DUMMY_VFD_VALUE (H5FD_class_value_t)155
static const H5FD_class_t H5FD_dummy_g = {
    H5FD_CLASS_VERSION,  /* struct version  */
    DUMMY_VFD_VALUE,     /* value           */
    "dummy",             /* name            */
    1,                   /* maxaddr         */
    H5F_CLOSE_WEAK,      /* fc_degree       */
    NULL,                /* terminate       */
    NULL,                /* sb_size         */
    NULL,                /* sb_encode       */
    NULL,                /* sb_decode       */
    0,                   /* fapl_size       */
    NULL,                /* fapl_get        */
    NULL,                /* fapl_copy       */
    NULL,                /* fapl_free       */
    0,                   /* dxpl_size       */
    NULL,                /* dxpl_copy       */
    NULL,                /* dxpl_free       */
    dummy_vfd_open,      /* open            */
    dummy_vfd_close,     /* close           */
    NULL,                /* cmp             */
    NULL,                /* query           */
    NULL,                /* get_type_map    */
    NULL,                /* alloc           */
    NULL,                /* free            */
    dummy_vfd_get_eoa,   /* get_eoa         */
    dummy_vfd_set_eoa,   /* set_eoa         */
    dummy_vfd_get_eof,   /* get_eof         */
    NULL,                /* get_handle      */
    dummy_vfd_read,      /* read            */
    dummy_vfd_write,     /* write           */
    NULL,                /* read_vector     */
    NULL,                /* write_vector    */
    NULL,                /* read_selection  */
    NULL,                /* write_selection */
    NULL,                /* flush           */
    NULL,                /* truncate        */
    NULL,                /* lock            */
    NULL,                /* unlock          */
    NULL,                /* del             */
    NULL,                /* ctl             */
    H5FD_FLMAP_DICHOTOMY /* fl_map          */
};

/*-------------------------------------------------------------------------
 * Function:    h5_get_dummy_vfd_class()
 *
 * Purpose:     Returns a disposable, generally non-functional,
 *              VFD class struct.
 *
 *              In some of the test code, we need a disposable VFD but
 *              we don't want to mess with the real VFDs and we also
 *              don't have access to the internals of the real VFDs (which
 *              use static globals and functions) to easily duplicate
 *              them (e.g.: for testing VFD ID handling).
 *
 *              This API call will return a pointer to a VFD class that
 *              can be used to construct a test VFD using H5FDregister().
 *
 * Return:      Success:    A pointer to a VFD class struct
 *              Failure:    NULL
 *
 *-------------------------------------------------------------------------
 */
H5FD_class_t *
h5_get_dummy_vfd_class(void)
{
    H5FD_class_t *vfd_class = NULL; /* Dummy VFD that will be returned */

    /* Create the class and initialize everything to zero/NULL */
    if (NULL == (vfd_class = (H5FD_class_t *)HDmalloc(sizeof(H5FD_class_t))))
        TEST_ERROR;

    /* Copy the dummy VFD */
    HDmemcpy(vfd_class, &H5FD_dummy_g, sizeof(H5FD_class_t));

    return vfd_class;

error:
    if (vfd_class)
        HDfree(vfd_class);
    return NULL;
} /* h5_get_dummy_vfd_class */

/*-------------------------------------------------------------------------
 * Function:    h5_get_dummy_vol_class()
 *
 * Purpose:     Returns a disposable, generally non-functional,
 *              VOL class struct.
 *
 *              In some of the test code, we need a disposable VOL connector
 *              but we don't want to mess with the real VFDs and we also
 *              don't have access to the internals of the real VOL connectors
 *              (which use static globals and functions) to easily duplicate
 *              them (e.g.: for testing VOL connector ID handling).
 *
 *              This API call will return a pointer to a VOL class that
 *              can be used to construct a test VOL using H5VLregister_connector().
 *
 * Return:      Success:    A pointer to a VOL class struct
 *              Failure:    NULL
 *
 *-------------------------------------------------------------------------
 */
H5VL_class_t *
h5_get_dummy_vol_class(void)
{
    H5VL_class_t *vol_class = NULL; /* Dummy VOL class that will be returned */

    /* Create the class and initialize everything to zero/NULL */
    if (NULL == (vol_class = (H5VL_class_t *)HDcalloc((size_t)1, sizeof(H5VL_class_t))))
        TEST_ERROR;

    /* Fill in the minimum parameters to make a VOL connector class that
     * can be registered.
     */
    vol_class->version = H5VL_VERSION;
    vol_class->name    = "dummy";

    return vol_class;

error:
    if (vol_class)
        HDfree(vol_class);
    return NULL;
} /* h5_get_dummy_vol_class */

/*-------------------------------------------------------------------------
 * Function:    h5_get_version_string
 *
 * Purpose:     Get the string that corresponds to the libvery version bound.
 *
 * Return:      The string
 *
 *-------------------------------------------------------------------------
 */
H5_ATTR_PURE const char *
h5_get_version_string(H5F_libver_t libver)
{
    return (LIBVER_NAMES[libver]);
} /* end of h5_get_version_string */

/*-------------------------------------------------------------------------
 * Function:    h5_compare_file_bytes()
 *
 * Purpose:     Helper function to compare two files byte-for-byte.
 *
 * Return:      Success:  0, if files are identical
 *              Failure: -1, if files differ
 *
 * Programmer:  Binh-Minh Ribler
 *              October, 2018
 *-------------------------------------------------------------------------
 */
int
h5_compare_file_bytes(char *f1name, char *f2name)
{
    FILE *f1ptr     = NULL; /* two file pointers */
    FILE *f2ptr     = NULL;
    off_t f1size    = 0; /* size of the files */
    off_t f2size    = 0;
    char  f1char    = 0; /* one char from each file */
    char  f2char    = 0;
    off_t ii        = 0;
    int   ret_value = 0; /* for error handling */

    /* Open files for reading */
    f1ptr = HDfopen(f1name, "rb");
    if (f1ptr == NULL) {
        HDfprintf(stderr, "Unable to fopen() %s\n", f1name);
        ret_value = -1;
        goto done;
    }
    f2ptr = HDfopen(f2name, "rb");
    if (f2ptr == NULL) {
        HDfprintf(stderr, "Unable to fopen() %s\n", f2name);
        ret_value = -1;
        goto done;
    }

    /* Get the file sizes and verify that they equal */
    HDfseek(f1ptr, 0, SEEK_END);
    f1size = HDftell(f1ptr);

    HDfseek(f2ptr, 0, SEEK_END);
    f2size = HDftell(f2ptr);

    if (f1size != f2size) {
        HDfprintf(stderr, "Files differ in size, %" PRIuHSIZE " vs. %" PRIuHSIZE "\n", (hsize_t)f1size,
                  (hsize_t)f2size);
        ret_value = -1;
        goto done;
    }

    /* Compare each byte and fail if a difference is found */
    HDrewind(f1ptr);
    HDrewind(f2ptr);
    for (ii = 0; ii < f1size; ii++) {
        if (HDfread(&f1char, 1, 1, f1ptr) != 1) {
            ret_value = -1;
            goto done;
        }
        if (HDfread(&f2char, 1, 1, f2ptr) != 1) {
            ret_value = -1;
            goto done;
        }
        if (f1char != f2char) {
            HDfprintf(stderr, "Mismatch @ 0x%" PRIXHSIZE ": 0x%X != 0x%X\n", (hsize_t)ii, f1char, f2char);
            ret_value = -1;
            goto done;
        }
    }

done:
    if (f1ptr)
        HDfclose(f1ptr);
    if (f2ptr)
        HDfclose(f2ptr);
    return ret_value;
} /* end h5_compare_file_bytes() */

/*-------------------------------------------------------------------------
 * Function:    H5_get_srcdir_filename
 *
 * Purpose:     Append the test file name to the srcdir path and return the whole string
 *
 * Return:      The string or NULL (errors or not enough space)
 *
 *-------------------------------------------------------------------------
 */
const char *
H5_get_srcdir_filename(const char *filename)
{
    const char *srcdir = H5_get_srcdir();

    /* Check for error */
    if (NULL == srcdir)
        return NULL;

    /* Build path to test file. We're checking the length so suppress
     * the gcc format-truncation warning.
     */
    if ((HDstrlen(srcdir) + HDstrlen(filename) + 1) < sizeof(srcdir_testpath)) {
        H5_GCC_DIAG_OFF("format-truncation")
        HDsnprintf(srcdir_testpath, sizeof(srcdir_testpath), "%s%s", srcdir, filename);
        H5_GCC_DIAG_ON("format-truncation")
        return srcdir_testpath;
    }

    /* If not enough space, just return NULL */
    return NULL;
} /* end H5_get_srcdir_filename() */

/*-------------------------------------------------------------------------
 * Function:    H5_get_srcdir
 *
 * Purpose:     Just return the srcdir path
 *
 * Return:      The string
 *
 *-------------------------------------------------------------------------
 */
const char *
H5_get_srcdir(void)
{
    const char *srcdir = HDgetenv("srcdir");

    /* Check for using the srcdir from configure time */
    if (NULL == srcdir)
        srcdir = config_srcdir;

    /* Build path to all test files */
    if ((HDstrlen(srcdir) + 2) < sizeof(srcdir_path)) {
        HDsnprintf(srcdir_path, sizeof(srcdir_path), "%s/", srcdir);
        return (srcdir_path);
    } /* end if */
    else
        return (NULL);
} /* end H5_get_srcdir() */

/*-------------------------------------------------------------------------
 * Function:    h5_duplicate_file_by_bytes
 *
 * Purpose:     Duplicate a file byte-for-byte at filename/path 'orig'
 *              to a new (or replaced) file at 'dest'.
 *
 * Return:      Success:  0, completed successfully
 *              Failure: -1
 *
 * Programmer:  Jake Smith
 *              24 June 2020
 *
 *-------------------------------------------------------------------------
 */
int
h5_duplicate_file_by_bytes(const char *orig, const char *dest)
{
    FILE   *orig_ptr  = NULL;
    FILE   *dest_ptr  = NULL;
    hsize_t fsize     = 0;
    hsize_t read_size = 0;
    hsize_t max_buf   = 0;
    void   *dup_buf   = NULL;
    int     ret_value = 0;

    max_buf = 4096 * sizeof(char);

    orig_ptr = HDfopen(orig, "rb");
    if (NULL == orig_ptr) {
        ret_value = -1;
        goto done;
    }

    HDfseek(orig_ptr, 0, SEEK_END);
    fsize = (hsize_t)HDftell(orig_ptr);
    HDrewind(orig_ptr);

    dest_ptr = HDfopen(dest, "wb");
    if (NULL == dest_ptr) {
        ret_value = -1;
        goto done;
    }

    read_size = MIN(fsize, max_buf);
    dup_buf   = HDmalloc(read_size);
    if (NULL == dup_buf) {
        ret_value = -1;
        goto done;
    }

    while (read_size > 0) {
        if (HDfread(dup_buf, read_size, 1, orig_ptr) != 1) {
            ret_value = -1;
            goto done;
        }
        HDfwrite(dup_buf, read_size, 1, dest_ptr);
        fsize -= read_size;
        read_size = MIN(fsize, max_buf);
    }

done:
    if (orig_ptr != NULL)
        HDfclose(orig_ptr);
    if (dest_ptr != NULL)
        HDfclose(dest_ptr);
    if (dup_buf != NULL)
        HDfree(dup_buf);
    return ret_value;
} /* end h5_duplicate_file_by_bytes() */

/*-------------------------------------------------------------------------
 * Function:    h5_check_if_file_locking_enabled
 *
 * Purpose:     Checks if file locking is enabled on this file system.
 *
 * Return:      SUCCEED/FAIL
 *              are_enabled will be FALSE if file locking is disabled on
 *              the file system of if there were errors.
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_check_if_file_locking_enabled(hbool_t *is_enabled)
{
    const char *filename = "locking_test_file";
    int         pmode    = O_RDWR | O_CREAT | O_TRUNC;
    int         fd       = -1;

    *is_enabled = TRUE;

    if ((fd = HDopen(filename, pmode, H5_POSIX_CREATE_MODE_RW)) < 0)
        goto error;

    /* Test HDflock() to see if it works */
    if (HDflock(fd, LOCK_EX | LOCK_NB) < 0) {
        if (ENOSYS == errno) {
            /* When errno is set to ENOSYS, the file system does not support
             * locking, so ignore it. This is most frequently used on
             * Lustre. If we also want to check for disabled NFS locks
             * we'll need to check for ENOLCK, too. That isn't done by
             * default here since that could also represent an actual
             * error condition.
             */
            errno       = 0;
            *is_enabled = FALSE;
        }
        else
            goto error;
    }
    if (HDflock(fd, LOCK_UN) < 0)
        goto error;

    if (HDclose(fd) < 0)
        goto error;
    if (HDremove(filename) < 0)
        goto error;

    return SUCCEED;

error:
    *is_enabled = FALSE;
    if (fd > -1) {
        HDclose(fd);
        HDremove(filename);
    }
    return FAIL;
} /* end h5_check_if_file_locking_enabled() */

/*-------------------------------------------------------------------------
 * Function:    h5_using_default_driver
 *
 * Purpose:     Checks if the specified VFD name matches the library's
 *              default VFD. If `drv_name` is NULL, the HDF5_DRIVER
 *              environment is checked instead (if it is set).
 *
 * Return:      TRUE/FALSE
 *
 *-------------------------------------------------------------------------
 */
hbool_t
h5_using_default_driver(const char *drv_name)
{
    hbool_t ret_val = TRUE;

    HDassert(H5_DEFAULT_VFD == H5FD_SEC2);

    if (!drv_name)
        drv_name = HDgetenv(HDF5_DRIVER);

    if (drv_name)
        return (!HDstrcmp(drv_name, "sec2") || !HDstrcmp(drv_name, "nomatch"));

    return ret_val;
}

/*-------------------------------------------------------------------------
 * Function:    h5_using_parallel_driver
 *
 * Purpose:     Checks if the current VFD set on the given FAPL is a
 *              parallel-enabled VFD (The MPI I/O VFD, for example).
 *
 *              This is mostly useful for avoiding tests that use features
 *              which are not currently supported for parallel HDF5, such
 *              as writing of VL or region reference datatypes.
 *
 * Return:      TRUE/FALSE
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_using_parallel_driver(hid_t fapl_id, hbool_t *driver_is_parallel)
{
    unsigned long feat_flags = 0;
    hid_t         driver_id  = H5I_INVALID_HID;
    herr_t        ret_value  = SUCCEED;

    HDassert(fapl_id >= 0);
    HDassert(driver_is_parallel);

    if (fapl_id == H5P_DEFAULT)
        fapl_id = H5P_FILE_ACCESS_DEFAULT;

    if ((driver_id = H5Pget_driver(fapl_id)) < 0)
        return FAIL;

    if (H5FDdriver_query(driver_id, &feat_flags) < 0)
        return FAIL;

    *driver_is_parallel = (feat_flags & H5FD_FEAT_HAS_MPI);

    return ret_value;
}

/*-------------------------------------------------------------------------
 * Function:    h5_driver_is_default_vfd_compatible
 *
 * Purpose:     Checks if the current VFD set on the given FAPL creates a
 *              file that is compatible with the default VFD. Some examples
 *              are the core and MPI I/O drivers. Some counterexamples are
 *              the multi and family drivers, which split the HDF5 file
 *              into several different files.
 *
 *              This routine is helpful for skipping tests that use
 *              pre-generated files. VFDs that create files which aren't
 *              compatible with the default VFD will generally not be able
 *              to open these pre-generated files and those particular
 *              tests will fail.
 *
 * Return:      Non-negative on success/Negative on failure
 *
 *-------------------------------------------------------------------------
 */
herr_t
h5_driver_is_default_vfd_compatible(hid_t fapl_id, hbool_t *default_vfd_compatible)
{
    unsigned long feat_flags = 0;
    hid_t         driver_id  = H5I_INVALID_HID;
    herr_t        ret_value  = SUCCEED;

    HDassert(fapl_id >= 0);
    HDassert(default_vfd_compatible);

    if (fapl_id == H5P_DEFAULT)
        fapl_id = H5P_FILE_ACCESS_DEFAULT;

    if ((driver_id = H5Pget_driver(fapl_id)) < 0)
        return FAIL;

    if (H5FDdriver_query(driver_id, &feat_flags) < 0)
        return FAIL;

    *default_vfd_compatible = (feat_flags & H5FD_FEAT_DEFAULT_VFD_COMPATIBLE);

    return ret_value;
} /* end h5_driver_is_default_vfd_compatible() */

/*-------------------------------------------------------------------------
 * Function:    h5_driver_uses_multiple_files
 *
 * Purpose:     Checks if the specified VFD name matches a driver that
 *              stores data using multiple files.
 *
 *              The following flags can be used to control what types of
 *              drivers are checked for by this routine:
 *
 *              H5_EXCLUDE_MULTIPART_DRIVERS - This flag excludes any
 *                drivers which store data using multiple files which,
 *                together, make up a single logical file. These are
 *                drivers like the split, multi and family drivers.
 *
 *              H5_EXCLUDE_NON_MULTIPART_DRIVERS - This flag excludes any
 *                drivers which store data using multiple files which are
 *                separate logical files. The splitter driver is an example
 *                of this type of driver.
 *
 *              Eventually, this should become a VFD feature flag so this
 *              check is less fragile.
 *
 * Return:      TRUE/FALSE
 *
 *-------------------------------------------------------------------------
 */
hbool_t
h5_driver_uses_multiple_files(const char *drv_name, unsigned flags)
{
    hbool_t ret_val = FALSE;

    if (!drv_name)
        drv_name = HDgetenv(HDF5_DRIVER);

    if (drv_name) {
        if ((flags & H5_EXCLUDE_MULTIPART_DRIVERS) == 0) {
            if (!HDstrcmp(drv_name, "split") || !HDstrcmp(drv_name, "multi") ||
                !HDstrcmp(drv_name, "family") || !HDstrcmp(drv_name, H5FD_SUBFILING_NAME))
                return TRUE;
        }

        if ((flags & H5_EXCLUDE_NON_MULTIPART_DRIVERS) == 0) {
            if (!HDstrcmp(drv_name, "splitter"))
                return TRUE;
        }
    }

    return ret_val;
}
