/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <sal/config.h>

#include <sal/log.hxx>
#include <sal/types.h>
#include <osl/module.h>
#include <osl/thread.h>
#include <osl/process.h>
#include <osl/file.h>
#include <rtl/string.hxx>
#include <rtl/ustring.hxx>
#include <assert.h>
#include "system.hxx"
#include "file_url.hxx"

#ifdef AIX
#include <sys/ldr.h>
#endif

static bool getModulePathFromAddress(void * address, rtl_String ** path)
{
    bool result = false;
#if defined(AIX)
    int size = 4 * 1024;
    char *buf, *filename=NULL;
    struct ld_info *lp;

    if ((buf = (char*)malloc(size)) == NULL)
        return false;

    //figure out how big a buffer we need
    while (loadquery(L_GETINFO, buf, size) == -1 && errno == ENOMEM)
    {
        size += 4 * 1024;
        free(buf);
        if ((buf = (char*)malloc(size)) == NULL)
            return false;
    }

    lp = (struct ld_info*) buf;
    while (lp)
    {
        unsigned long start = (unsigned long)lp->ldinfo_dataorg;
        unsigned long end = start + lp->ldinfo_datasize;
        if (start <= (unsigned long)address && end > (unsigned long)address)
        {
            filename = lp->ldinfo_filename;
            break;
        }
        if (!lp->ldinfo_next)
            break;
        lp = (struct ld_info*) ((char *) lp + lp->ldinfo_next);
    }

    if (filename)
    {
        rtl_string_newFromStr(path, filename);
        result = sal_True;
    }
    else
    {
        result = sal_False;
    }

    free(buf);
#else
#if HAVE_UNIX_DLAPI
    Dl_info dl_info;

    result = dladdr(address, &dl_info) != 0;

    if (result)
    {
        rtl_string_newFromStr(path, dl_info.dli_fname);
    }
#else
    (void) address;
    (void) path;
#endif
#endif
    return result;
}

#ifndef DISABLE_DYNLOADING

/*****************************************************************************/
/* osl_loadModule */
/*****************************************************************************/

oslModule SAL_CALL osl_loadModule(rtl_uString *ustrModuleName, sal_Int32 nRtldMode)
{
    oslModule pModule=nullptr;
    rtl_uString* ustrTmp = nullptr;

    SAL_WARN_IF(ustrModuleName == nullptr, "sal.osl", "string is not valid");

    /* ensure ustrTmp hold valid string */
    if (osl_getSystemPathFromFileURL(ustrModuleName, &ustrTmp) != osl_File_E_None)
        rtl_uString_assign(&ustrTmp, ustrModuleName);

    if (ustrTmp)
    {
        char buffer[PATH_MAX];

        if (UnicodeToText(buffer, PATH_MAX, ustrTmp->buffer, ustrTmp->length))
            pModule = osl_loadModuleAscii(buffer, nRtldMode);
        rtl_uString_release(ustrTmp);
    }

    return pModule;
}

/*****************************************************************************/
/* osl_loadModuleAscii */
/*****************************************************************************/

oslModule SAL_CALL osl_loadModuleAscii(const char *pModuleName, sal_Int32 nRtldMode)
{
#if HAVE_UNIX_DLAPI
    SAL_WARN_IF(
        ((nRtldMode & SAL_LOADMODULE_LAZY) != 0
         && (nRtldMode & SAL_LOADMODULE_NOW) != 0),
        "sal.osl", "only either LAZY or NOW");
    if (pModuleName)
    {
        int rtld_mode =
            ((nRtldMode & SAL_LOADMODULE_NOW) ? RTLD_NOW : RTLD_LAZY) |
            ((nRtldMode & SAL_LOADMODULE_GLOBAL) ? RTLD_GLOBAL : RTLD_LOCAL);
        void* pLib = dlopen(pModuleName, rtld_mode);

        SAL_WARN_IF(
            pLib == nullptr, "sal.osl",
            "dlopen(" << pModuleName << ", " << rtld_mode << "): "
                << dlerror());
        return pLib;
    }
#else
    (void) pModuleName;
    (void) nRtldMode;
#endif
    return nullptr;
}

oslModule osl_loadModuleRelativeAscii(
    oslGenericFunction baseModule, char const * relativePath, sal_Int32 mode)
{
    assert(relativePath && "illegal argument");
    if (relativePath[0] == '/') {
        return osl_loadModuleAscii(relativePath, mode);
    }
    rtl_String * path = nullptr;
    rtl_String * suffix = nullptr;
    oslModule module;
    if (!getModulePathFromAddress(
            reinterpret_cast< void * >(baseModule), &path))
    {
        return nullptr;
    }
    rtl_string_newFromStr_WithLength(
        &path, path->buffer,
        (rtl_str_lastIndexOfChar_WithLength(path->buffer, path->length, '/')
         + 1));
        /* cut off everything after the last slash; should the original path
           contain no slash, the resulting path is the empty string */
    rtl_string_newFromStr(&suffix, relativePath);
    rtl_string_newConcat(&path, path, suffix);
    rtl_string_release(suffix);
    module = osl_loadModuleAscii(path->buffer, mode);
    rtl_string_release(path);
    return module;
}

#endif // !DISABLE_DYNLOADING

/*****************************************************************************/
/* osl_getModuleHandle */
/*****************************************************************************/

sal_Bool SAL_CALL
osl_getModuleHandle(rtl_uString *, oslModule *pResult)
{
#if HAVE_UNIX_DLAPI
    *pResult = static_cast<oslModule>(RTLD_DEFAULT);
    return true;
#else
    *pResult = nullptr;
    return false;
#endif
}

/*****************************************************************************/
/* osl_unloadModule */
/*****************************************************************************/
void SAL_CALL osl_unloadModule(oslModule hModule)
{
#if !defined(DISABLE_DYNLOADING) && HAVE_UNIX_DLAPI
    if (hModule)
    {
        int nRet = dlclose(hModule);
        SAL_INFO_IF(
            nRet != 0, "sal.osl", "dlclose(" << hModule << "): " << dlerror());
    }
#else
    (void) hModule;
#endif
}

namespace {

void * getSymbol(oslModule module, char const * symbol)
{
    assert(symbol != nullptr);
#if HAVE_UNIX_DLAPI
    // We do want to use dlsym() also in the DISABLE_DYNLOADING case
    // just to look up symbols in the static executable, I think:
    void * p = dlsym(module, symbol);
    SAL_INFO_IF(
        p == nullptr, "sal.osl",
        "dlsym(" << module << ", " << symbol << "): " << dlerror());
#else
    (void) module;
    (void) symbol;
    void *p = nullptr;
#endif
    return p;
}

}

/*****************************************************************************/
/* osl_getSymbol */
/*****************************************************************************/
void* SAL_CALL
osl_getSymbol(oslModule Module, rtl_uString* pSymbolName)
{
    // Arbitrarily using UTF-8:
    OString s;
    if (!OUString::unacquired(&pSymbolName).convertToString(
            &s, RTL_TEXTENCODING_UTF8,
            (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
             RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
    {
        SAL_INFO(
            "sal.osl", "cannot convert \"" << OUString::unacquired(&pSymbolName)
                << "\" to UTF-8");
        return nullptr;
    }
    if (s.indexOf('\0') != -1) {
        SAL_INFO("sal.osl", "\"" << s << "\" contains embedded NUL");
        return nullptr;
    }
    return getSymbol(Module, s.getStr());
}

/*****************************************************************************/
/* osl_getAsciiFunctionSymbol */
/*****************************************************************************/
oslGenericFunction SAL_CALL
osl_getAsciiFunctionSymbol(oslModule Module, const char *pSymbol)
{
    return reinterpret_cast<oslGenericFunction>(getSymbol(Module, pSymbol));
        // requires conditionally-supported conversion from void * to function
        // pointer
}

/*****************************************************************************/
/* osl_getFunctionSymbol */
/*****************************************************************************/
oslGenericFunction SAL_CALL
osl_getFunctionSymbol(oslModule module, rtl_uString *puFunctionSymbolName)
{
    return reinterpret_cast<oslGenericFunction>(
        osl_getSymbol(module, puFunctionSymbolName));
        // requires conditionally-supported conversion from void * to function
        // pointer
}

/*****************************************************************************/
/* osl_getModuleURLFromAddress */
/*****************************************************************************/
sal_Bool SAL_CALL osl_getModuleURLFromAddress(void * addr, rtl_uString ** ppLibraryUrl)
{
    bool result = false;
    rtl_String * path = nullptr;
    if (getModulePathFromAddress(addr, &path))
    {
        rtl_string2UString(ppLibraryUrl,
                           path->buffer,
                           path->length,
                           osl_getThreadTextEncoding(),
                           OSTRING_TO_OUSTRING_CVTFLAGS);

        SAL_WARN_IF(
            *ppLibraryUrl == nullptr, "sal.osl", "rtl_string2UString failed");
        auto const e = osl_getFileURLFromSystemPath(*ppLibraryUrl, ppLibraryUrl);
        if (e == osl_File_E_None)
        {
            SAL_INFO("sal.osl", "osl_getModuleURLFromAddress(" << addr << ") => " << OUString(*ppLibraryUrl));

            result = true;
        }
        else
        {
            SAL_WARN(
                "sal.osl",
                "osl_getModuleURLFromAddress(" << addr << "), osl_getFileURLFromSystemPath("
                    << OUString::unacquired(ppLibraryUrl) << ") failed with " << e);
            result = false;
        }
        rtl_string_release(path);
    }
    return result;
}

/*****************************************************************************/
/* osl_getModuleURLFromFunctionAddress */
/*****************************************************************************/
sal_Bool SAL_CALL osl_getModuleURLFromFunctionAddress(oslGenericFunction addr, rtl_uString ** ppLibraryUrl)
{
    return osl_getModuleURLFromAddress(
        reinterpret_cast<void*>(addr), ppLibraryUrl);
        // requires conditionally-supported conversion from function pointer to
        // void *
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */