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

/*
 * Purpose: Code to implement a path table which stores plugin search paths.
 *
 *          The path table is implemented as a dynamic, global array which
 *          will grow as new paths are inserted. The capacity of the path
 *          table never shrinks (though given the low number of paths
 *          expected and the low likelihood of paths being removed, this
 *          seems unlikely to be a problem). Inserts and removals rework
 *          the array so that there are no 'holes' in the in-use part
 *          of the array.
 *
 *          Note that it's basically up to the user to manage the indexes
 *          when a complicated series of insert, overwrite, and, remove
 *          operations take place.
 */

/****************/
/* Module Setup */
/****************/

#include "H5PLmodule.h" /* This source code file is part of the H5PL module */

/***********/
/* Headers */
/***********/
#include "H5private.h"   /* Generic Functions            */
#include "H5Eprivate.h"  /* Error handling               */
#include "H5MMprivate.h" /* Memory management            */
#include "H5PLpkg.h"     /* Plugin                       */

/****************/
/* Local Macros */
/****************/

/* Initial capacity of the path table */
#define H5PL_INITIAL_PATH_CAPACITY 16

/* The amount to add to the capacity when the table is full */
#define H5PL_PATH_CAPACITY_ADD 16

/******************/
/* Local Typedefs */
/******************/

/********************/
/* Local Prototypes */
/********************/

static herr_t H5PL__insert_at(const char *path, unsigned int idx);
static herr_t H5PL__make_space_at(unsigned int idx);
static herr_t H5PL__replace_at(const char *path, unsigned int idx);
static herr_t H5PL__expand_path_table(void);
static herr_t H5PL__path_table_iterate_process_path(const char *plugin_path, H5PL_iterate_type_t iter_type,
                                                    H5PL_iterate_t iter_op, void *op_data);
static herr_t H5PL__find_plugin_in_path(const H5PL_search_params_t *search_params, bool *found,
                                        const char *dir, const void **plugin_info);

/*********************/
/* Package Variables */
/*********************/

/*****************************/
/* Library Private Variables */
/*****************************/

/*******************/
/* Local Variables */
/*******************/

/* Stored plugin paths to search */
static char **H5PL_paths_g = NULL;

/* The number of stored paths */
static unsigned H5PL_num_paths_g = 0;

/* The capacity of the path table */
static unsigned H5PL_path_capacity_g = H5PL_INITIAL_PATH_CAPACITY;

/*-------------------------------------------------------------------------
 * Function:    H5PL__insert_at()
 *
 * Purpose:     Insert a path at a particular index in the path table.
 *              Does not clobber! Will move existing paths up to make
 *              room. Use H5PL__replace_at(index) if you want to clobber.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5PL__insert_at(const char *path, unsigned int idx)
{
    char  *path_copy = NULL;    /* copy of path string (for storing) */
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));

    /* Expand the table if it is full */
    if (H5PL_num_paths_g == H5PL_path_capacity_g)
        if (H5PL__expand_path_table() < 0)
            HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't expand path table");

    /* Copy the path for storage so the caller can dispose of theirs */
    if (NULL == (path_copy = H5MM_strdup(path)))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't make internal copy of path");

#ifdef H5_HAVE_WIN32_API
    /* Clean up Microsoft Windows environment variables in the path string */
    if (H5_expand_windows_env_vars(&path_copy))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTCONVERT, FAIL, "can't expand environment variable string");
#endif /* H5_HAVE_WIN32_API */

    /* If the table entry is in use, make some space */
    if (H5PL_paths_g[idx])
        if (H5PL__make_space_at(idx) < 0)
            HGOTO_ERROR(H5E_PLUGIN, H5E_NOSPACE, FAIL, "unable to make space in the table for the new entry");

    /* Insert the copy of the search path into the table at the specified index */
    H5PL_paths_g[idx] = path_copy;
    H5PL_num_paths_g++;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__insert_at() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__make_space_at()
 *
 * Purpose:     Free up a slot in the path table, moving existing path
 *              entries as necessary.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5PL__make_space_at(unsigned int idx)
{
    unsigned u;                   /* iterator */
    herr_t   ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE_NOERR

    /* Check args - Just assert on package functions */
    assert(idx < H5PL_path_capacity_g);

    /* Copy the paths back to make a space  */
    for (u = H5PL_num_paths_g; u > idx; u--)
        H5PL_paths_g[u] = H5PL_paths_g[u - 1];

    H5PL_paths_g[idx] = NULL;

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__make_space_at() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__replace_at()
 *
 * Purpose:     Replace a path at a particular index in the path table.
 *              The path in the table must exist and will be freed by this
 *              function.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5PL__replace_at(const char *path, unsigned int idx)
{
    char  *path_copy = NULL;    /* copy of path string (for storing) */
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));

    /* Check that the table entry is in use */
    if (!H5PL_paths_g[idx])
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTFREE, FAIL, "path entry at index %u in the table is NULL", idx);

    /* Copy the path for storage so the caller can dispose of theirs */
    if (NULL == (path_copy = H5MM_strdup(path)))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't make internal copy of path");

#ifdef H5_HAVE_WIN32_API
    /* Clean up Microsoft Windows environment variables in the path string */
    if (H5_expand_windows_env_vars(&path_copy))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTCONVERT, FAIL, "can't expand environment variable string");
#endif /* H5_HAVE_WIN32_API */

    /* Free the existing path entry */
    H5PL_paths_g[idx] = (char *)H5MM_xfree(H5PL_paths_g[idx]);

    /* Copy the search path into the table at the specified index */
    H5PL_paths_g[idx] = path_copy;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__replace_at() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__create_path_table
 *
 * Purpose:     Create the collection of paths that will be searched
 *              when loading plugins.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__create_path_table(void)
{
    char *env_var = NULL;       /* Path string from environment variable */
    char *paths   = NULL;       /* Delimited paths string. Either from the
                                 * environment variable or the default.
                                 */
    char  *next_path = NULL;    /* A path tokenized from the paths string */
    char  *lasts     = NULL;    /* Context pointer for strtok_r() call */
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Allocate memory for the path table */
    H5PL_num_paths_g     = 0;
    H5PL_path_capacity_g = H5PL_INITIAL_PATH_CAPACITY;
    if (NULL == (H5PL_paths_g = (char **)H5MM_calloc((size_t)H5PL_path_capacity_g * sizeof(char *))))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't allocate memory for path table");

    /* Retrieve paths from HDF5_PLUGIN_PATH if the user sets it
     * or from the default paths if it isn't set.
     */
    env_var = getenv(HDF5_PLUGIN_PATH);
    if (NULL == env_var)
        paths = H5MM_strdup(H5PL_DEFAULT_PATH);
    else
        paths = H5MM_strdup(env_var);

    if (NULL == paths)
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't allocate memory for path copy");

    /* Separate the paths and store them */
    next_path = HDstrtok_r(paths, H5PL_PATH_SEPARATOR, &lasts);
    while (next_path) {

        /* Insert the path into the table */
        if (H5PL__append_path(next_path) < 0)
            HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't insert path: %s", next_path);

        /* Get the next path from the environment string */
        next_path = HDstrtok_r(NULL, H5PL_PATH_SEPARATOR, &lasts);
    } /* end while */

done:
    if (paths)
        paths = (char *)H5MM_xfree(paths);

    /* Try to clean up on errors */
    if (FAIL == ret_value) {
        if (H5PL_paths_g)
            H5PL_paths_g = (char **)H5MM_xfree(H5PL_paths_g);
        H5PL_path_capacity_g = 0;
    }

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__create_path_table() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__close_path_table
 *
 * Purpose:     Close the collection of paths that will be searched
 *              when loading plugins.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__close_path_table(void)
{
    unsigned u;                   /* iterator */
    herr_t   ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE_NOERR

    /* Free paths */
    for (u = 0; u < H5PL_num_paths_g; u++)
        if (H5PL_paths_g[u])
            H5PL_paths_g[u] = (char *)H5MM_xfree(H5PL_paths_g[u]);

    /* Free path table */
    H5PL_paths_g = (char **)H5MM_xfree(H5PL_paths_g);

    /* Reset values */
    H5PL_num_paths_g = 0;

    FUNC_LEAVE_NOAPI(ret_value)

} /* end H5PL__close_path_table() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__get_num_paths
 *
 * Purpose:     Gets the number of plugin paths that have been stored.
 *
 * Return:      Success:    The number of paths
 *              Failure:   Can't fail
 *-------------------------------------------------------------------------
 */
unsigned
H5PL__get_num_paths(void)
{
    FUNC_ENTER_PACKAGE_NOERR

    FUNC_LEAVE_NOAPI(H5PL_num_paths_g)

} /* end H5PL__get_num_paths() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__expand_path_table
 *
 * Purpose:     Expand the path table when it's full.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
H5PL__expand_path_table(void)
{
    herr_t ret_value = SUCCEED;

    FUNC_ENTER_PACKAGE

    /* Update the capacity */
    H5PL_path_capacity_g += H5PL_PATH_CAPACITY_ADD;

    /* Resize the array */
    if (NULL ==
        (H5PL_paths_g = (char **)H5MM_realloc(H5PL_paths_g, (size_t)H5PL_path_capacity_g * sizeof(char *))))
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "allocating additional memory for path table failed");

    /* Initialize the new memory */
    memset(H5PL_paths_g + H5PL_num_paths_g, 0, (size_t)H5PL_PATH_CAPACITY_ADD * sizeof(char *));

done:
    /* Set the path capacity back if there were problems */
    if (FAIL == ret_value)
        H5PL_path_capacity_g -= H5PL_PATH_CAPACITY_ADD;

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__expand_path_table() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__append_path
 *
 * Purpose:     Insert a path at the end of the table.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__append_path(const char *path)
{
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));

    /* Insert the path at the end of the table */
    if (H5PL__insert_at(path, H5PL_num_paths_g) < 0)
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTINSERT, FAIL, "unable to append search path");

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__append_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__prepend_path
 *
 * Purpose:     Insert a path at the beginning of the table.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__prepend_path(const char *path)
{
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));

    /* Insert the path at the beginning of the table */
    if (H5PL__insert_at(path, 0) < 0)
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTINSERT, FAIL, "unable to prepend search path");

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__prepend_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__replace_path
 *
 * Purpose:     Replace a path at particular index in the table.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__replace_path(const char *path, unsigned int idx)
{
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));
    assert(idx < H5PL_path_capacity_g);

    /* Insert the path at the requested index */
    if (H5PL__replace_at(path, idx) < 0)
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTINSERT, FAIL, "unable to replace search path");

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__replace_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__insert_path
 *
 * Purpose:     Insert a path at particular index in the table, moving
 *              any existing paths back to make space.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__insert_path(const char *path, unsigned int idx)
{
    herr_t ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(path);
    assert(strlen(path));
    assert(idx < H5PL_path_capacity_g);

    /* Insert the path at the requested index */
    if (H5PL__insert_at(path, idx) < 0)
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTINSERT, FAIL, "unable to insert search path");

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__insert_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__remove_path
 *
 * Purpose:     Remove a path at particular index in the table, freeing
 *              the path string and moving the paths down to close the gap.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__remove_path(unsigned int idx)
{
    unsigned u;                   /* iterator */
    herr_t   ret_value = SUCCEED; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(idx < H5PL_path_capacity_g);

    /* Check if the path at that index is set */
    if (!H5PL_paths_g[idx])
        HGOTO_ERROR(H5E_PLUGIN, H5E_CANTDELETE, FAIL, "search path at index %u is NULL", idx);

    /* Delete the path */
    H5PL_num_paths_g--;
    H5PL_paths_g[idx] = (char *)H5MM_xfree(H5PL_paths_g[idx]);

    /* Shift the paths down to close the gap */
    for (u = idx; u < H5PL_num_paths_g; u++)
        H5PL_paths_g[u] = H5PL_paths_g[u + 1];

    /* Set the (former) last path to NULL */
    H5PL_paths_g[H5PL_num_paths_g] = NULL;

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__remove_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__get_path
 *
 * Purpose:     Get a pointer to a path at particular index in the table.
 *
 * Return:      Success:    A pointer to a path string stored in the table
 *              Failure:    NULL
 *
 *-------------------------------------------------------------------------
 */
const char *
H5PL__get_path(unsigned int idx)
{
    char *ret_value = NULL; /* Return value */

    FUNC_ENTER_PACKAGE

    /* Get the path at the requested index */
    if (idx >= H5PL_num_paths_g)
        HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, NULL, "path index %u is out of range in table", idx);

    return H5PL_paths_g[idx];
done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__get_path() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__path_table_iterate
 *
 * Purpose:     Iterates over all the plugins in the plugin path table and
 *              calls the specified callback function on each plugin found.
 *
 * Return:      H5_ITER_CONT if all plugins are processed successfully
 *              H5_ITER_STOP if short-circuit success occurs while
 *                  processing plugins
 *              H5_ITER_ERROR if an error occurs while processing plugins
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__path_table_iterate(H5PL_iterate_type_t iter_type, H5PL_iterate_t iter_op, void *op_data)
{
    unsigned int u;
    herr_t       ret_value = H5_ITER_CONT;

    FUNC_ENTER_PACKAGE

    for (u = 0; (u < H5PL_num_paths_g) && (ret_value == H5_ITER_CONT); u++) {
        if ((ret_value =
                 H5PL__path_table_iterate_process_path(H5PL_paths_g[u], iter_type, iter_op, op_data)) < 0)
            HGOTO_ERROR(H5E_PLUGIN, H5E_BADITER, H5_ITER_ERROR,
                        "can't iterate over plugins in plugin path '%s'", H5PL_paths_g[u]);
    }

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__path_table_iterate() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__path_table_iterate_process_path
 *
 * Purpose:     Iterates over all the plugins within a single plugin path
 *              entry in the plugin path table and calls the specified
 *              callback function on each plugin found. Two function
 *              definitions are for Unix and Windows.
 *
 * Return:      H5_ITER_CONT if all plugins are processed successfully
 *              H5_ITER_STOP if short-circuit success occurs while
 *                  processing plugins
 *              H5_ITER_ERROR if an error occurs while processing plugins
 *
 *-------------------------------------------------------------------------
 */
#ifndef H5_HAVE_WIN32_API
static herr_t
H5PL__path_table_iterate_process_path(const char *plugin_path, H5PL_iterate_type_t iter_type,
                                      H5PL_iterate_t iter_op, void *op_data)
{
    H5PL_type_t    plugin_type;
    const void    *plugin_info = NULL;
    bool           plugin_loaded;
    char          *path      = NULL;
    DIR           *dirp      = NULL; /* Directory stream */
    struct dirent *dp        = NULL; /* Directory entry */
    herr_t         ret_value = H5_ITER_CONT;

    FUNC_ENTER_PACKAGE

    assert(plugin_path);
    assert(iter_op);

    /* Open the directory - skip the path if the directory can't be opened */
    if (!(dirp = HDopendir(plugin_path)))
        HGOTO_DONE(H5_ITER_CONT);

    /* Iterate through all entries in the directory */
    while (NULL != (dp = HDreaddir(dirp))) {
        /* The library we are looking for should be called libxxx.so... on Unix
         * or libxxx.xxx.dylib on Mac.
         */
#ifndef __CYGWIN__
        if (!strncmp(dp->d_name, "lib", (size_t)3) &&
            (strstr(dp->d_name, ".so") || strstr(dp->d_name, ".dylib"))) {
#else
        if (!strncmp(dp->d_name, "cyg", (size_t)3) && strstr(dp->d_name, ".dll")) {
#endif

            bool      plugin_matches;
            h5_stat_t my_stat;
            size_t    len;

            /* Allocate & initialize the path name */
            len = strlen(plugin_path) + strlen(H5PL_PATH_SEPARATOR) + strlen(dp->d_name) + 1 /*\0*/ +
                  4; /* Extra "+4" to quiet GCC warning - 2019/07/05, QAK */

            if (NULL == (path = (char *)H5MM_calloc(len)))
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, H5_ITER_ERROR, "can't allocate memory for path");

            snprintf(path, len, "%s/%s", plugin_path, dp->d_name);

            /* Get info for directory entry */
            memset(&my_stat, 0, sizeof(h5_stat_t));
            if (HDstat(path, &my_stat) == -1)
                HGOTO_ERROR(H5E_FILE, H5E_CANTGET, H5_ITER_ERROR, "can't stat file %s -- error was: %s", path,
                            strerror(errno));

            /* If it is a directory, skip it */
            if (S_ISDIR(my_stat.st_mode))
                continue;

            /* Attempt to open the dynamic library */
            plugin_type   = H5PL_TYPE_ERROR;
            plugin_info   = NULL;
            plugin_loaded = false;
            if (H5PL__open(path, H5PL_TYPE_NONE, NULL, &plugin_loaded, &plugin_type, &plugin_info) < 0)
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTGET, H5_ITER_ERROR, "failed to open plugin '%s'", path);

            /* Determine if we should process this plugin */
            plugin_matches = (iter_type == H5PL_ITER_TYPE_ALL) ||
                             ((iter_type == H5PL_ITER_TYPE_FILTER) && (plugin_type == H5PL_TYPE_FILTER)) ||
                             ((iter_type == H5PL_ITER_TYPE_VOL) && (plugin_type == H5PL_TYPE_VOL)) ||
                             ((iter_type == H5PL_ITER_TYPE_VFD) && (plugin_type == H5PL_TYPE_VFD));

            /* If the plugin was successfully loaded, call supplied callback function on plugin */
            if (plugin_loaded && plugin_matches && (ret_value = iter_op(plugin_type, plugin_info, op_data)))
                break;

            path = (char *)H5MM_xfree(path);
        } /* end if */
    }     /* end while */

    if (ret_value < 0)
        HERROR(H5E_PLUGIN, H5E_CALLBACK, "callback operator function returned failure");

done:
    if (dirp)
        if (HDclosedir(dirp) < 0)
            HDONE_ERROR(H5E_FILE, H5E_CLOSEERROR, H5_ITER_ERROR, "can't close directory: %s",
                        strerror(errno));

    path = (char *)H5MM_xfree(path);

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__path_table_iterate_process_path() */
#else  /* H5_HAVE_WIN32_API */
static herr_t
H5PL__path_table_iterate_process_path(const char *plugin_path, H5PL_iterate_type_t iter_type,
                                      H5PL_iterate_t iter_op, void *op_data)
{
    WIN32_FIND_DATAA fdFile;
    HANDLE           hFind = INVALID_HANDLE_VALUE;
    H5PL_type_t      plugin_type;
    const void      *plugin_info = NULL;
    bool             plugin_loaded;
    char            *path = NULL;
    char             service[2048];
    herr_t           ret_value = H5_ITER_CONT;

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(plugin_path);
    assert(iter_op);

    /* Specify a file mask. *.* = We want everything! -
     * skip the path if the directory can't be opened */
    snprintf(service, sizeof(service), "%s\\*.dll", plugin_path);
    if ((hFind = FindFirstFileA(service, &fdFile)) == INVALID_HANDLE_VALUE)
        HGOTO_DONE(H5_ITER_CONT);

    /* Loop over all the files */
    do {
        /* Ignore '.' and '..' */
        if (strcmp(fdFile.cFileName, ".") != 0 && strcmp(fdFile.cFileName, "..") != 0) {
            bool   plugin_matches;
            size_t len;

            /* Allocate & initialize the path name */
            len = strlen(plugin_path) + strlen(H5PL_PATH_SEPARATOR) + strlen(fdFile.cFileName) + 1;

            if (NULL == (path = (char *)H5MM_calloc(len)))
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, H5_ITER_ERROR, "can't allocate memory for path");

            snprintf(path, len, "%s\\%s", plugin_path, fdFile.cFileName);

            /* Ignore directories */
            if (fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                continue;

            /* Attempt to open the dynamic library */
            plugin_type   = H5PL_TYPE_ERROR;
            plugin_info   = NULL;
            plugin_loaded = false;
            if (H5PL__open(path, H5PL_TYPE_NONE, NULL, &plugin_loaded, &plugin_type, &plugin_info) < 0)
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTGET, H5_ITER_ERROR, "failed to open plugin '%s'", path);

            /* Determine if we should process this plugin */
            plugin_matches = (iter_type == H5PL_ITER_TYPE_ALL) ||
                             ((iter_type == H5PL_ITER_TYPE_FILTER) && (plugin_type == H5PL_TYPE_FILTER)) ||
                             ((iter_type == H5PL_ITER_TYPE_VOL) && (plugin_type == H5PL_TYPE_VOL)) ||
                             ((iter_type == H5PL_ITER_TYPE_VFD) && (plugin_type == H5PL_TYPE_VFD));

            /* If the plugin was successfully loaded, call supplied callback function on plugin */
            if (plugin_loaded && plugin_matches && (ret_value = iter_op(plugin_type, plugin_info, op_data)))
                break;

            path = (char *)H5MM_xfree(path);
        }
    } while (FindNextFileA(hFind, &fdFile));

    if (ret_value < 0)
        HERROR(H5E_PLUGIN, H5E_CALLBACK, "callback operator function returned failure");

done:
    if (hFind != INVALID_HANDLE_VALUE)
        FindClose(hFind);

    path = (char *)H5MM_xfree(path);

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__path_table_iterate_process_path() */
#endif /* H5_HAVE_WIN32_API */

/*-------------------------------------------------------------------------
 * Function:    H5PL__find_plugin_in_path_table
 *
 * Purpose:     Attempts to find a matching plugin in the file system
 *              using the paths stored in the path table.
 *.
 *              The 'found' parameter will be set appropriately.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5PL__find_plugin_in_path_table(const H5PL_search_params_t *search_params, bool *found,
                                const void **plugin_info)
{
    unsigned int u; /* iterator */
    herr_t       ret_value = SUCCEED;

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(search_params);
    assert(found);
    assert(plugin_info);

    /* Initialize output parameters */
    *found       = false;
    *plugin_info = NULL;

    /* Loop over the paths in the table, checking for an appropriate plugin */
    for (u = 0; u < H5PL_num_paths_g; u++) {

        /* Search for the plugin in this path */
        if (H5PL__find_plugin_in_path(search_params, found, H5PL_paths_g[u], plugin_info) < 0)
            HERROR(H5E_PLUGIN, H5E_CANTGET, "search in path %s encountered an error", H5PL_paths_g[u]);

        /* Break out if found */
        if (*found) {
            if (!plugin_info)
                HGOTO_ERROR(H5E_PLUGIN, H5E_BADVALUE, FAIL, "plugin info should not be NULL");
            break;
        }
    }

done:
    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__find_plugin_in_path_table() */

/*-------------------------------------------------------------------------
 * Function:    H5PL__find_plugin_in_path
 *
 * Purpose:     Given a path, this function opens the directory and invokes
 *              another function to go through all files to find the right
 *              plugin library. Two function definitions are for Unix and
 *              Windows.
 *
 *              The found parameter will be set to true and the info
 *              parameter will be filled in on success.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
#ifndef H5_HAVE_WIN32_API
static herr_t
H5PL__find_plugin_in_path(const H5PL_search_params_t *search_params, bool *found, const char *dir,
                          const void **plugin_info)
{
    char          *path      = NULL;
    DIR           *dirp      = NULL; /* Directory stream */
    struct dirent *dp        = NULL; /* Directory entry */
    herr_t         ret_value = SUCCEED;

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(search_params);
    assert(found);
    assert(dir);
    assert(plugin_info);

    /* Initialize the found parameter */
    *found = false;

    /* Open the directory */
    if (!(dirp = HDopendir(dir)))
        HGOTO_ERROR(H5E_PLUGIN, H5E_OPENERROR, FAIL, "can't open directory (%s). Please verify its existence",
                    dir);

    /* Iterate through all entries in the directory */
    while (NULL != (dp = HDreaddir(dirp))) {

        /* The library we are looking for should be called libxxx.so... on Unix
         * or libxxx.xxx.dylib on Mac.
         */
#ifndef __CYGWIN__
        if (!strncmp(dp->d_name, "lib", (size_t)3) &&
            (strstr(dp->d_name, ".so") || strstr(dp->d_name, ".dylib"))) {
#else
        if (!strncmp(dp->d_name, "cyg", (size_t)3) && strstr(dp->d_name, ".dll")) {
#endif

            h5_stat_t my_stat;
            size_t    len;

            /* Allocate & initialize the path name */
            len = strlen(dir) + strlen(H5PL_PATH_SEPARATOR) + strlen(dp->d_name) + 1 /*\0*/ +
                  4; /* Extra "+4" to quiet GCC warning - 2019/07/05, QAK */

            if (NULL == (path = (char *)H5MM_calloc(len)))
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't allocate memory for path");

            snprintf(path, len, "%s/%s", dir, dp->d_name);

            /* Get info for directory entry */
            memset(&my_stat, 0, sizeof(h5_stat_t));
            if (HDstat(path, &my_stat) == -1)
                HGOTO_ERROR(H5E_FILE, H5E_CANTGET, FAIL, "can't stat file %s -- error was: %s", path,
                            strerror(errno));

            /* If it is a directory, skip it */
            if (S_ISDIR(my_stat.st_mode)) {
                path = (char *)H5MM_xfree(path);
                continue;
            }

            /* attempt to open the dynamic library */
            if (H5PL__open(path, search_params->type, search_params->key, found, NULL, plugin_info) < 0)
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTGET, FAIL, "search in directory failed");
            if (*found)
                HGOTO_DONE(SUCCEED);

            path = (char *)H5MM_xfree(path);
        } /* end if */
    }     /* end while */

done:
    if (dirp)
        if (HDclosedir(dirp) < 0)
            HDONE_ERROR(H5E_FILE, H5E_CLOSEERROR, FAIL, "can't close directory: %s", strerror(errno));

    path = (char *)H5MM_xfree(path);

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__find_plugin_in_path() */
#else  /* H5_HAVE_WIN32_API */
static herr_t
H5PL__find_plugin_in_path(const H5PL_search_params_t *search_params, bool *found, const char *dir,
                          const void **plugin_info)
{
    WIN32_FIND_DATAA fdFile;
    HANDLE           hFind = INVALID_HANDLE_VALUE;
    char            *path  = NULL;
    char             service[2048];
    herr_t           ret_value = SUCCEED;

    FUNC_ENTER_PACKAGE

    /* Check args - Just assert on package functions */
    assert(search_params);
    assert(found);
    assert(dir);
    assert(plugin_info);

    /* Initialize the found parameter */
    *found = false;

    /* Specify a file mask. *.* = We want everything! */
    snprintf(service, sizeof(service), "%s\\*.dll", dir);
    if ((hFind = FindFirstFileA(service, &fdFile)) == INVALID_HANDLE_VALUE)
        HGOTO_ERROR(H5E_PLUGIN, H5E_OPENERROR, FAIL, "can't open directory");

    /* Loop over all the files */
    do {
        /* Ignore '.' and '..' */
        if (strcmp(fdFile.cFileName, ".") != 0 && strcmp(fdFile.cFileName, "..") != 0) {

            /* XXX: Probably just continue here and move the code below over one tab */

            size_t len;

            /* Allocate & initialize the path name */
            len = strlen(dir) + strlen(H5PL_PATH_SEPARATOR) + strlen(fdFile.cFileName) + 1;

            if (NULL == (path = (char *)H5MM_calloc(len)))
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTALLOC, FAIL, "can't allocate memory for path");

            snprintf(path, len, "%s\\%s", dir, fdFile.cFileName);

            /* Ignore directories */
            if (fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                continue;

            /* attempt to open the dynamic library */
            if (H5PL__open(path, search_params->type, search_params->key, found, NULL, plugin_info) < 0)
                HGOTO_ERROR(H5E_PLUGIN, H5E_CANTGET, FAIL, "search in directory failed");
            if (*found)
                HGOTO_DONE(SUCCEED);

            path = (char *)H5MM_xfree(path);
        }
    } while (FindNextFileA(hFind, &fdFile));

done:
    if (hFind != INVALID_HANDLE_VALUE)
        FindClose(hFind);
    if (path)
        path = (char *)H5MM_xfree(path);

    FUNC_LEAVE_NOAPI(ret_value)
} /* end H5PL__find_plugin_in_path() */
#endif /* H5_HAVE_WIN32_API */
