/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ctype.h>
#include <stdio.h>

#include "apu_config.h"
#include "apu.h"

#include "apr_pools.h"
#include "apr_tables.h"
#include "apr_dso.h"
#include "apr_strings.h"
#include "apr_hash.h"
#include "apr_file_io.h"
#include "apr_env.h"

#include "apu_internal.h"
#include "apu_version.h"


#if APR_HAS_THREADS
static apr_thread_mutex_t* mutex = NULL;
#endif
static apr_hash_t *dsos = NULL;


#if APR_HAS_THREADS && APU_DSO_BUILD
apr_status_t apu_dso_mutex_lock()
{
    return apr_thread_mutex_lock(mutex);
}
apr_status_t apu_dso_mutex_unlock()
{
    return apr_thread_mutex_unlock(mutex);
}
#else
apr_status_t apu_dso_mutex_lock() {
    return APR_SUCCESS;
}
apr_status_t apu_dso_mutex_unlock() {
    return APR_SUCCESS;
}
#endif

#define CLEANUP_CAST (apr_status_t (*)(void*))


static apr_status_t apu_dso_term(void *ptr)
{
    /* set statics to NULL so init can work again */
    dsos = NULL;
#if APR_HAS_THREADS
    mutex = NULL;
#endif

    /* Everything else we need is handled by cleanups registered
     * when we created mutexes and loaded DSOs
     */
    return APR_SUCCESS;
}

apr_status_t apu_dso_init(apr_pool_t *pool)
{
    apr_status_t ret = APR_SUCCESS;
#if APU_DSO_BUILD
    apr_pool_t *global;
    apr_pool_t *parent;

    if (dsos != NULL) {
        return APR_SUCCESS;
    }

    /* Top level pool scope, need process-scope lifetime */
    for (parent = global = pool; parent; parent = apr_pool_parent_get(global))
         global = parent;

    dsos = apr_hash_make(global);

#if APR_HAS_THREADS
    ret = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, global);
    /* This already registers a pool cleanup */
#endif

    apr_pool_cleanup_register(global, NULL, apu_dso_term,
                              apr_pool_cleanup_null);

#endif /* APU_DSO_BUILD */
    return ret;
}

#if APR_HAS_DSO
apr_status_t apu_dso_load(apr_dso_handle_sym_t *dsoptr, const char *module,
                          const char *modsym, apr_pool_t *pool)
{
#if !APU_DSO_BUILD
    return APR_ENOTIMPL;
#else
    apr_dso_handle_t *dlhandle = NULL;
    char *pathlist;
    char path[APR_PATH_MAX + 1];
    apr_array_header_t *paths;
    apr_pool_t *global;
    apr_status_t rv = APR_EDSOOPEN;
    char *eos = NULL;
    int i;

    *dsoptr = apr_hash_get(dsos, module, APR_HASH_KEY_STRING);
    if (*dsoptr) {
        return APR_EINIT;
    }

    /* The driver DSO must have exactly the same lifetime as the
     * drivers hash table; ignore the passed-in pool */
    global = apr_hash_pool_get(dsos);

    /* Retrieve our path search list or prepare for a single search */
    if ((apr_env_get(&pathlist, APR_DSOPATH, pool) != APR_SUCCESS)
          || (apr_filepath_list_split(&paths, pathlist, pool) != APR_SUCCESS))
        paths = apr_array_make(pool, 1, sizeof(char*));

#if defined(APU_DSO_LIBDIR)
    /* Always search our prefix path, but on some platforms such as
     * win32 this may be left undefined
     */
    (*((char **)apr_array_push(paths))) = APU_DSO_LIBDIR;
#endif

    for (i = 0; i < paths->nelts; ++i)
    {
#if defined(WIN32)
        /* Use win32 dso search semantics and attempt to
         * load the relative lib on the first pass.
         */
        if (!eos) {
            eos = path;
            --i;
        }
        else
#endif
        {
            eos = apr_cpystrn(path, ((char**)paths->elts)[i], sizeof(path));
            if ((eos > path) && (eos - path < sizeof(path) - 1))
                *(eos++) = '/';
        }
        apr_cpystrn(eos, module, sizeof(path) - (eos - path));

        rv = apr_dso_load(&dlhandle, path, global);
        if (rv == APR_SUCCESS) { /* APR_EDSOOPEN */
            break;
        }
#if defined(APU_DSO_LIBDIR)
        else if (i < paths->nelts - 1) {
#else
        else {   /* No APU_DSO_LIBDIR to skip */
#endif
             /* try with apr-util-APU_MAJOR_VERSION appended */
            eos = apr_cpystrn(eos,
                              "apr-util-" APU_STRINGIFY(APU_MAJOR_VERSION) "/",
                              sizeof(path) - (eos - path));

            apr_cpystrn(eos, module, sizeof(path) - (eos - path));

            rv = apr_dso_load(&dlhandle, path, global);
            if (rv == APR_SUCCESS) { /* APR_EDSOOPEN */
                break;
            }
        }
    }

    if (rv != APR_SUCCESS) /* APR_ESYMNOTFOUND */
        return rv;

    rv = apr_dso_sym(dsoptr, dlhandle, modsym);
    if (rv != APR_SUCCESS) { /* APR_ESYMNOTFOUND */
        apr_dso_unload(dlhandle);
    }
    else {
        module = apr_pstrdup(global, module);
        apr_hash_set(dsos, module, APR_HASH_KEY_STRING, *dsoptr);
    }
    return rv;
#endif /* APU_DSO_BUILD */
}
#endif /* APR_HAS_DSO */

