/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "primpl.h"

#include <string.h>

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)
#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#endif

#ifdef XP_UNIX
#ifdef USE_DLFCN
#include <dlfcn.h>
/* Define these on systems that don't have them. */
#ifndef RTLD_NOW
#define RTLD_NOW 0
#endif
#ifndef RTLD_LAZY
#define RTLD_LAZY RTLD_NOW
#endif
#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif
#ifndef RTLD_LOCAL
#define RTLD_LOCAL 0
#endif
#ifdef AIX
#include <sys/ldr.h>
#ifndef L_IGNOREUNLOAD /* AIX 4.3.3 does not have L_IGNOREUNLOAD. */
#define L_IGNOREUNLOAD 0x10000000
#endif
#endif
#elif defined(USE_HPSHL)
#include <dl.h>
#elif defined(USE_MACH_DYLD)
#include <mach-o/dyld.h>
#endif
#endif /* XP_UNIX */

#define _PR_DEFAULT_LD_FLAGS PR_LD_LAZY

/*
 * On these platforms, symbols have a leading '_'.
 */
#if (defined(DARWIN) && defined(USE_MACH_DYLD)) \
    || defined(XP_OS2) \
    || ((defined(OPENBSD) || defined(NETBSD)) && !defined(__ELF__))
#define NEED_LEADING_UNDERSCORE
#endif

#define PR_LD_PATHW 0x8000  /* for PR_LibSpec_PathnameU */

/************************************************************************/

struct PRLibrary {
    char*                       name;  /* Our own copy of the name string */
    PRLibrary*                  next;
    int                         refCount;
    const PRStaticLinkTable*    staticTable;

#ifdef XP_PC
#ifdef XP_OS2
    HMODULE                     dlh;
#else
    HINSTANCE                   dlh;
#endif
#endif

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)
    CFragConnectionID           connection;
    CFBundleRef                 bundle;
    Ptr                         main;
    CFMutableDictionaryRef      wrappers;
    const struct mach_header*   image;
#endif

#ifdef XP_UNIX
#if defined(USE_HPSHL)
    shl_t                       dlh;
#elif defined(USE_MACH_DYLD)
    NSModule                    dlh;
#else
    void*                       dlh;
#endif
#endif

};

static PRLibrary *pr_loadmap;
static PRLibrary *pr_exe_loadmap;
static PRMonitor *pr_linker_lock;
static char* _pr_currentLibPath = NULL;

static PRLibrary *pr_LoadLibraryByPathname(const char *name, PRIntn flags);

/************************************************************************/

#if !defined(USE_DLFCN) && !defined(HAVE_STRERROR)
#define ERR_STR_BUF_LENGTH    20
#endif

static void DLLErrorInternal(PRIntn oserr)
/*
** This whole function, and most of the code in this file, are run
** with a big hairy lock wrapped around it. Not the best of situations,
** but will eventually come up with the right answer.
*/
{
    const char *error = NULL;
#ifdef USE_DLFCN
    error = dlerror();  /* $$$ That'll be wrong some of the time - AOF */
#elif defined(HAVE_STRERROR)
    error = strerror(oserr);  /* this should be okay */
#else
    char errStrBuf[ERR_STR_BUF_LENGTH];
    PR_snprintf(errStrBuf, sizeof(errStrBuf), "error %d", oserr);
    error = errStrBuf;
#endif
    if (NULL != error) {
        PR_SetErrorText(strlen(error), error);
    }
}  /* DLLErrorInternal */

void _PR_InitLinker(void)
{
    PRLibrary *lm = NULL;
#if defined(XP_UNIX)
    void *h;
#endif

    if (!pr_linker_lock) {
        pr_linker_lock = PR_NewNamedMonitor("linker-lock");
    }
    PR_EnterMonitor(pr_linker_lock);

#if defined(XP_PC)
    lm = PR_NEWZAP(PRLibrary);
    lm->name = strdup("Executable");
#if defined(XP_OS2)
    lm->dlh = NULLHANDLE;
#else
    /* A module handle for the executable. */
    lm->dlh = GetModuleHandle(NULL);
#endif /* ! XP_OS2 */

    lm->refCount    = 1;
    lm->staticTable = NULL;
    pr_exe_loadmap  = lm;
    pr_loadmap      = lm;

#elif defined(XP_UNIX)
#ifdef HAVE_DLL
#if defined(USE_DLFCN) && !defined(NO_DLOPEN_NULL)
    h = dlopen(0, RTLD_LAZY);
    if (!h) {
        char *error;

        DLLErrorInternal(_MD_ERRNO());
        error = (char*)PR_MALLOC(PR_GetErrorTextLength());
        (void) PR_GetErrorText(error);
        fprintf(stderr, "failed to initialize shared libraries [%s]\n",
                error);
        PR_DELETE(error);
        abort();/* XXX */
    }
#elif defined(USE_HPSHL)
    h = NULL;
    /* don't abort with this NULL */
#elif defined(USE_MACH_DYLD) || defined(NO_DLOPEN_NULL)
    h = NULL; /* XXXX  toshok */ /* XXXX  vlad */
#else
#error no dll strategy
#endif /* USE_DLFCN */

    lm = PR_NEWZAP(PRLibrary);
    if (lm) {
        lm->name = strdup("a.out");
        lm->refCount = 1;
        lm->dlh = h;
        lm->staticTable = NULL;
    }
    pr_exe_loadmap = lm;
    pr_loadmap = lm;
#endif /* HAVE_DLL */
#endif /* XP_UNIX */

    if (lm) {
        PR_LOG(_pr_linker_lm, PR_LOG_MIN,
               ("Loaded library %s (init)", lm->name));
    }

    PR_ExitMonitor(pr_linker_lock);
}

/*
 * _PR_ShutdownLinker does not unload the dlls loaded by the application
 * via calls to PR_LoadLibrary.  Any dlls that still remain on the
 * pr_loadmap list when NSPR shuts down are application programming errors.
 * The only exception is pr_exe_loadmap, which was added to the list by
 * NSPR and hence should be cleaned up by NSPR.
 */
void _PR_ShutdownLinker(void)
{
    /* FIXME: pr_exe_loadmap should be destroyed. */

    PR_DestroyMonitor(pr_linker_lock);
    pr_linker_lock = NULL;

    if (_pr_currentLibPath) {
        free(_pr_currentLibPath);
        _pr_currentLibPath = NULL;
    }
}

/******************************************************************************/

PR_IMPLEMENT(PRStatus) PR_SetLibraryPath(const char *path)
{
    PRStatus rv = PR_SUCCESS;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    PR_EnterMonitor(pr_linker_lock);
    if (_pr_currentLibPath) {
        free(_pr_currentLibPath);
    }
    if (path) {
        _pr_currentLibPath = strdup(path);
        if (!_pr_currentLibPath) {
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            rv = PR_FAILURE;
        }
    } else {
        _pr_currentLibPath = 0;
    }
    PR_ExitMonitor(pr_linker_lock);
    return rv;
}

/*
** Return the library path for finding shared libraries.
*/
PR_IMPLEMENT(char *)
PR_GetLibraryPath(void)
{
    char *ev;
    char *copy = NULL;  /* a copy of _pr_currentLibPath */

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    PR_EnterMonitor(pr_linker_lock);
    if (_pr_currentLibPath != NULL) {
        goto exit;
    }

    /* initialize pr_currentLibPath */

#ifdef XP_PC
    ev = getenv("LD_LIBRARY_PATH");
    if (!ev) {
        ev = ".;\\lib";
    }
    ev = strdup(ev);
#endif

#if defined(XP_UNIX)
#if defined(USE_DLFCN) || defined(USE_MACH_DYLD)
    {
        char *p=NULL;
        int len;

        ev = getenv("LD_LIBRARY_PATH");
        if (!ev) {
            ev = "/usr/lib:/lib";
        }
        len = strlen(ev) + 1;        /* +1 for the null */

        p = (char*) malloc(len);
        if (p) {
            strcpy(p, ev);
        }   /* if (p)  */
        ev = p;
        PR_LOG(_pr_io_lm, PR_LOG_NOTICE, ("linker path '%s'", ev));

    }
#else
    /* AFAIK there isn't a library path with the HP SHL interface --Rob */
    ev = strdup("");
#endif
#endif

    /*
     * If ev is NULL, we have run out of memory
     */
    _pr_currentLibPath = ev;

exit:
    if (_pr_currentLibPath) {
        copy = strdup(_pr_currentLibPath);
    }
    PR_ExitMonitor(pr_linker_lock);
    if (!copy) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    }
    return copy;
}

/*
** Build library name from path, lib and extensions
*/
PR_IMPLEMENT(char*)
PR_GetLibraryName(const char *path, const char *lib)
{
    char *fullname;

#ifdef XP_PC
    if (strstr(lib, PR_DLL_SUFFIX) == NULL)
    {
        if (path) {
            fullname = PR_smprintf("%s\\%s%s", path, lib, PR_DLL_SUFFIX);
        } else {
            fullname = PR_smprintf("%s%s", lib, PR_DLL_SUFFIX);
        }
    } else {
        if (path) {
            fullname = PR_smprintf("%s\\%s", path, lib);
        } else {
            fullname = PR_smprintf("%s", lib);
        }
    }
#endif /* XP_PC */
#if defined(XP_UNIX)
    if (strstr(lib, PR_DLL_SUFFIX) == NULL)
    {
        if (path) {
            fullname = PR_smprintf("%s/lib%s%s", path, lib, PR_DLL_SUFFIX);
        } else {
            fullname = PR_smprintf("lib%s%s", lib, PR_DLL_SUFFIX);
        }
    } else {
        if (path) {
            fullname = PR_smprintf("%s/%s", path, lib);
        } else {
            fullname = PR_smprintf("%s", lib);
        }
    }
#endif /* XP_UNIX */
    return fullname;
}

/*
** Free the memory allocated, for the caller, by PR_GetLibraryName
*/
PR_IMPLEMENT(void)
PR_FreeLibraryName(char *mem)
{
    PR_smprintf_free(mem);
}

static PRLibrary*
pr_UnlockedFindLibrary(const char *name)
{
    PRLibrary* lm = pr_loadmap;
    const char* np = strrchr(name, PR_DIRECTORY_SEPARATOR);
    np = np ? np + 1 : name;
    while (lm) {
        const char* cp = strrchr(lm->name, PR_DIRECTORY_SEPARATOR);
        cp = cp ? cp + 1 : lm->name;
#ifdef WIN32
        /* Windows DLL names are case insensitive... */
        if (strcmpi(np, cp) == 0)
#elif defined(XP_OS2)
        if (stricmp(np, cp) == 0)
#else
        if (strcmp(np, cp)  == 0)
#endif
        {
            /* found */
            lm->refCount++;
            PR_LOG(_pr_linker_lm, PR_LOG_MIN,
                   ("%s incr => %d (find lib)",
                    lm->name, lm->refCount));
            return lm;
        }
        lm = lm->next;
    }
    return NULL;
}

PR_IMPLEMENT(PRLibrary*)
PR_LoadLibraryWithFlags(PRLibSpec libSpec, PRIntn flags)
{
    if (flags == 0) {
        flags = _PR_DEFAULT_LD_FLAGS;
    }
    switch (libSpec.type) {
        case PR_LibSpec_Pathname:
            return pr_LoadLibraryByPathname(libSpec.value.pathname, flags);
#ifdef WIN32
        case PR_LibSpec_PathnameU:
            /*
             * cast to |char *| and set PR_LD_PATHW flag so that
             * it can be cast back to PRUnichar* in the callee.
             */
            return pr_LoadLibraryByPathname((const char*)
                                            libSpec.value.pathname_u,
                                            flags | PR_LD_PATHW);
#endif
        default:
            PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
            return NULL;
    }
}

PR_IMPLEMENT(PRLibrary*)
PR_LoadLibrary(const char *name)
{
    PRLibSpec libSpec;

    libSpec.type = PR_LibSpec_Pathname;
    libSpec.value.pathname = name;
    return PR_LoadLibraryWithFlags(libSpec, 0);
}

#if defined(USE_MACH_DYLD)
static NSModule
pr_LoadMachDyldModule(const char *name)
{
    NSObjectFileImage ofi;
    NSModule h = NULL;
    if (NSCreateObjectFileImageFromFile(name, &ofi)
        == NSObjectFileImageSuccess) {
        h = NSLinkModule(ofi, name, NSLINKMODULE_OPTION_PRIVATE
                         | NSLINKMODULE_OPTION_RETURN_ON_ERROR);
        if (h == NULL) {
            NSLinkEditErrors linkEditError;
            int errorNum;
            const char *fileName;
            const char *errorString;
            NSLinkEditError(&linkEditError, &errorNum, &fileName, &errorString);
            PR_LOG(_pr_linker_lm, PR_LOG_MIN,
                   ("LoadMachDyldModule error %d:%d for file %s:\n%s",
                    linkEditError, errorNum, fileName, errorString));
        }
        if (NSDestroyObjectFileImage(ofi) == FALSE) {
            if (h) {
                (void)NSUnLinkModule(h, NSUNLINKMODULE_OPTION_NONE);
                h = NULL;
            }
        }
    }
    return h;
}
#endif

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)

/*
** macLibraryLoadProc is a function definition for a Mac shared library
** loading method. The "name" param is the same full or partial pathname
** that was passed to pr_LoadLibraryByPathName. The function must fill
** in the fields of "lm" which apply to its library type. Returns
** PR_SUCCESS if successful.
*/

typedef PRStatus (*macLibraryLoadProc)(const char *name, PRLibrary *lm);

#ifdef __ppc__

/*
** CFM and its TVectors only exist on PowerPC.  Other OS X architectures
** only use Mach-O as a native binary format.
*/

static void* TV2FP(CFMutableDictionaryRef dict, const char* name, void *tvp)
{
    static uint32 glue[6] = { 0x3D800000, 0x618C0000, 0x800C0000, 0x804C0004, 0x7C0903A6, 0x4E800420 };
    uint32* newGlue = NULL;

    if (tvp != NULL) {
        CFStringRef nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingASCII);
        if (nameRef) {
            CFMutableDataRef glueData = (CFMutableDataRef) CFDictionaryGetValue(dict, nameRef);
            if (glueData == NULL) {
                glueData = CFDataCreateMutable(NULL, sizeof(glue));
                if (glueData != NULL) {
                    newGlue = (uint32*) CFDataGetMutableBytePtr(glueData);
                    memcpy(newGlue, glue, sizeof(glue));
                    newGlue[0] |= ((UInt32)tvp >> 16);
                    newGlue[1] |= ((UInt32)tvp & 0xFFFF);
                    MakeDataExecutable(newGlue, sizeof(glue));
                    CFDictionaryAddValue(dict, nameRef, glueData);
                    CFRelease(glueData);

                    PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("TV2FP: created wrapper for CFM function %s().", name));
                }
            } else {
                PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("TV2FP: found wrapper for CFM function %s().", name));

                newGlue = (uint32*) CFDataGetMutableBytePtr(glueData);
            }
            CFRelease(nameRef);
        }
    }

    return newGlue;
}

static PRStatus
pr_LoadViaCFM(const char *name, PRLibrary *lm)
{
    OSErr err;
    Str255 errName;
    FSRef ref;
    FSSpec fileSpec;
    Boolean tempUnusedBool;

    /*
     * Make an FSSpec from the path name and call GetDiskFragment.
     */

    /* Use direct conversion of POSIX path to FSRef to FSSpec. */
    err = FSPathMakeRef((const UInt8*)name, &ref, NULL);
    if (err != noErr) {
        return PR_FAILURE;
    }
    err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL,
                           &fileSpec, NULL);
    if (err != noErr) {
        return PR_FAILURE;
    }

    /* Resolve an alias if this was one */
    err = ResolveAliasFile(&fileSpec, true, &tempUnusedBool,
                           &tempUnusedBool);
    if (err != noErr) {
        return PR_FAILURE;
    }

    /* Finally, try to load the library */
    err = GetDiskFragment(&fileSpec, 0, kCFragGoesToEOF, fileSpec.name,
                          kLoadCFrag, &lm->connection, &lm->main, errName);

    if (err == noErr && lm->connection) {
        /*
         * if we're a mach-o binary, need to wrap all CFM function
         * pointers. need a hash-table of already seen function
         * pointers, etc.
         */
        lm->wrappers = CFDictionaryCreateMutable(NULL, 16,
                       &kCFTypeDictionaryKeyCallBacks,
                       &kCFTypeDictionaryValueCallBacks);
        if (lm->wrappers) {
            lm->main = TV2FP(lm->wrappers, "main", lm->main);
        } else {
            err = memFullErr;
        }
    }
    return (err == noErr) ? PR_SUCCESS : PR_FAILURE;
}
#endif /* __ppc__ */

/*
** Creates a CFBundleRef if the pathname refers to a Mac OS X bundle
** directory. The caller is responsible for calling CFRelease() to
** deallocate.
*/

static PRStatus
pr_LoadCFBundle(const char *name, PRLibrary *lm)
{
    CFURLRef bundleURL;
    CFBundleRef bundle = NULL;
    char pathBuf[PATH_MAX];
    const char *resolvedPath;
    CFStringRef pathRef;

    /* Takes care of relative paths and symlinks */
    resolvedPath = realpath(name, pathBuf);
    if (!resolvedPath) {
        return PR_FAILURE;
    }

    pathRef = CFStringCreateWithCString(NULL, pathBuf, kCFStringEncodingUTF8);
    if (pathRef) {
        bundleURL = CFURLCreateWithFileSystemPath(NULL, pathRef,
                    kCFURLPOSIXPathStyle, true);
        if (bundleURL) {
            bundle = CFBundleCreate(NULL, bundleURL);
            CFRelease(bundleURL);
        }
        CFRelease(pathRef);
    }

    lm->bundle = bundle;
    return (bundle != NULL) ? PR_SUCCESS : PR_FAILURE;
}

static PRStatus
pr_LoadViaDyld(const char *name, PRLibrary *lm)
{
    lm->dlh = pr_LoadMachDyldModule(name);
    if (lm->dlh == NULL) {
        lm->image = NSAddImage(name, NSADDIMAGE_OPTION_RETURN_ON_ERROR
                               | NSADDIMAGE_OPTION_WITH_SEARCHING);
        if (lm->image == NULL) {
            NSLinkEditErrors linkEditError;
            int errorNum;
            const char *fileName;
            const char *errorString;
            NSLinkEditError(&linkEditError, &errorNum, &fileName, &errorString);
            PR_LOG(_pr_linker_lm, PR_LOG_MIN,
                   ("LoadMachDyldModule error %d:%d for file %s:\n%s",
                    linkEditError, errorNum, fileName, errorString));
        }
    }
    return (lm->dlh != NULL || lm->image != NULL) ? PR_SUCCESS : PR_FAILURE;
}

#endif /* XP_MACOSX && USE_MACH_DYLD */

/*
** Dynamically load a library. Only load libraries once, so scan the load
** map first.
*/
static PRLibrary*
pr_LoadLibraryByPathname(const char *name, PRIntn flags)
{
    PRLibrary *lm;
    PRLibrary* result = NULL;
    PRInt32 oserr;
#ifdef WIN32
    char utf8name_stack[MAX_PATH];
    char *utf8name_malloc = NULL;
    char *utf8name = utf8name_stack;
    PRUnichar wname_stack[MAX_PATH];
    PRUnichar *wname_malloc = NULL;
    PRUnichar *wname = wname_stack;
    int len;
#endif

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    /* See if library is already loaded */
    PR_EnterMonitor(pr_linker_lock);

#ifdef WIN32
    if (flags & PR_LD_PATHW) {
        /* cast back what's cast to |char *| for the argument passing. */
        wname = (LPWSTR) name;
    } else {
        int wlen = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0);
        if (wlen > MAX_PATH) {
            wname = wname_malloc = PR_Malloc(wlen * sizeof(PRUnichar));
        }
        if (wname == NULL ||
            !MultiByteToWideChar(CP_ACP, 0,  name, -1, wname, wlen)) {
            oserr = _MD_ERRNO();
            goto unlock;
        }
    }
    len = WideCharToMultiByte(CP_UTF8, 0, wname, -1, NULL, 0, NULL, NULL);
    if (len > MAX_PATH) {
        utf8name = utf8name_malloc = PR_Malloc(len);
    }
    if (utf8name == NULL ||
        !WideCharToMultiByte(CP_UTF8, 0, wname, -1,
                             utf8name, len, NULL, NULL)) {
        oserr = _MD_ERRNO();
        goto unlock;
    }
    /* the list of loaded library names are always kept in UTF-8
     * on Win32 platforms */
    result = pr_UnlockedFindLibrary(utf8name);
#else
    result = pr_UnlockedFindLibrary(name);
#endif

    if (result != NULL) {
        goto unlock;
    }

    lm = PR_NEWZAP(PRLibrary);
    if (lm == NULL) {
        oserr = _MD_ERRNO();
        goto unlock;
    }
    lm->staticTable = NULL;

#ifdef XP_OS2  /* Why isn't all this stuff in MD code?! */
    {
        HMODULE h;
        UCHAR pszError[_MAX_PATH];
        ULONG ulRc = NO_ERROR;

        ulRc = DosLoadModule(pszError, _MAX_PATH, (PSZ) name, &h);
        if (ulRc != NO_ERROR) {
            oserr = ulRc;
            PR_DELETE(lm);
            goto unlock;
        }
        lm->name = strdup(name);
        lm->dlh  = h;
        lm->next = pr_loadmap;
        pr_loadmap = lm;
    }
#endif /* XP_OS2 */

#ifdef WIN32
    {
        HINSTANCE h;

        h = LoadLibraryExW(wname, NULL,
                           (flags & PR_LD_ALT_SEARCH_PATH) ?
                           LOAD_WITH_ALTERED_SEARCH_PATH : 0);
        if (h == NULL) {
            oserr = _MD_ERRNO();
            PR_DELETE(lm);
            goto unlock;
        }
        lm->name = strdup(utf8name);
        lm->dlh = h;
        lm->next = pr_loadmap;
        pr_loadmap = lm;
    }
#endif /* WIN32 */

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)
    {
        int     i;
        PRStatus status;

        static const macLibraryLoadProc loadProcs[] = {
#ifdef __ppc__
            pr_LoadViaDyld, pr_LoadCFBundle, pr_LoadViaCFM
#else  /* __ppc__ */
            pr_LoadViaDyld, pr_LoadCFBundle
#endif /* __ppc__ */
        };

        for (i = 0; i < sizeof(loadProcs) / sizeof(loadProcs[0]); i++) {
            if ((status = loadProcs[i](name, lm)) == PR_SUCCESS) {
                break;
            }
        }
        if (status != PR_SUCCESS) {
            oserr = cfragNoLibraryErr;
            PR_DELETE(lm);
            goto unlock;
        }
        lm->name = strdup(name);
        lm->next = pr_loadmap;
        pr_loadmap = lm;
    }
#endif

#if defined(XP_UNIX) && !(defined(XP_MACOSX) && defined(USE_MACH_DYLD))
#ifdef HAVE_DLL
    {
#if defined(USE_DLFCN)
#ifdef NTO
        /* Neutrino needs RTLD_GROUP to load Netscape plugins. (bug 71179) */
        int dl_flags = RTLD_GROUP;
#elif defined(AIX)
        /* AIX needs RTLD_MEMBER to load an archive member.  (bug 228899) */
        int dl_flags = RTLD_MEMBER;
#else
        int dl_flags = 0;
#endif
        void *h = NULL;
#if defined(DARWIN)
        PRBool okToLoad = PR_FALSE;
#endif

        if (flags & PR_LD_LAZY) {
            dl_flags |= RTLD_LAZY;
        }
        if (flags & PR_LD_NOW) {
            dl_flags |= RTLD_NOW;
        }
        if (flags & PR_LD_GLOBAL) {
            dl_flags |= RTLD_GLOBAL;
        }
        if (flags & PR_LD_LOCAL) {
            dl_flags |= RTLD_LOCAL;
        }
#if defined(DARWIN)
        /* If the file contains an absolute or relative path (slash)
         * and the path doesn't look like a System path, then require
         * the file exists.
         * The reason is that DARWIN's dlopen ignores the provided path
         * and checks for the plain filename in DYLD_LIBRARY_PATH,
         * which could load an unexpected version of a library. */
        if (strchr(name, PR_DIRECTORY_SEPARATOR) == NULL) {
          /* no slash, allow to load from any location */
          okToLoad = PR_TRUE;
        } else {
          const char systemPrefix1[] = "/System/";
          const size_t systemPrefixLen1 = strlen(systemPrefix1);
          const char systemPrefix2[] = "/usr/lib/";
          const size_t systemPrefixLen2 = strlen(systemPrefix2);
          const name_len = strlen(name);
          if (((name_len > systemPrefixLen1) &&
               (strncmp(name, systemPrefix1, systemPrefixLen1) == 0)) ||
             ((name_len > systemPrefixLen2) &&
               (strncmp(name, systemPrefix2, systemPrefixLen2) == 0))) {
            /* found at beginning, it's a system library.
             * Skip filesystem check (required for macOS 11),
             * allow loading from any location */
            okToLoad = PR_TRUE;
          } else if (PR_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) {
            /* file exists, allow to load */
            okToLoad = PR_TRUE;
          }
        }
        if (okToLoad) {
          h = dlopen(name, dl_flags);
        }
#else
        h = dlopen(name, dl_flags);
#endif
#elif defined(USE_HPSHL)
        int shl_flags = 0;
        shl_t h;

        /*
         * Use the DYNAMIC_PATH flag only if 'name' is a plain file
         * name (containing no directory) to match the behavior of
         * dlopen().
         */
        if (strchr(name, PR_DIRECTORY_SEPARATOR) == NULL) {
            shl_flags |= DYNAMIC_PATH;
        }
        if (flags & PR_LD_LAZY) {
            shl_flags |= BIND_DEFERRED;
        }
        if (flags & PR_LD_NOW) {
            shl_flags |= BIND_IMMEDIATE;
        }
        /* No equivalent of PR_LD_GLOBAL and PR_LD_LOCAL. */
        h = shl_load(name, shl_flags, 0L);
#elif defined(USE_MACH_DYLD)
        NSModule h = pr_LoadMachDyldModule(name);
#else
#error Configuration error
#endif
        if (!h) {
            oserr = _MD_ERRNO();
            PR_DELETE(lm);
            goto unlock;
        }
        lm->name = strdup(name);
        lm->dlh = h;
        lm->next = pr_loadmap;
        pr_loadmap = lm;
    }
#endif /* HAVE_DLL */
#endif /* XP_UNIX && !(XP_MACOSX && USE_MACH_DYLD) */

    lm->refCount = 1;

    result = lm;    /* success */
    PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("Loaded library %s (load lib)", lm->name));

unlock:
    if (result == NULL) {
        PR_SetError(PR_LOAD_LIBRARY_ERROR, oserr);
        DLLErrorInternal(oserr);  /* sets error text */
    }
#ifdef WIN32
    if (utf8name_malloc) {
        PR_Free(utf8name_malloc);
    }
    if (wname_malloc) {
        PR_Free(wname_malloc);
    }
#endif
    PR_ExitMonitor(pr_linker_lock);
    return result;
}

/*
** Unload a shared library which was loaded via PR_LoadLibrary
*/
PR_IMPLEMENT(PRStatus)
PR_UnloadLibrary(PRLibrary *lib)
{
    int result = 0;
    PRStatus status = PR_SUCCESS;

    if (lib == 0) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }

    PR_EnterMonitor(pr_linker_lock);

    if (lib->refCount <= 0) {
        PR_ExitMonitor(pr_linker_lock);
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }

    if (--lib->refCount > 0) {
        PR_LOG(_pr_linker_lm, PR_LOG_MIN,
               ("%s decr => %d",
                lib->name, lib->refCount));
        goto done;
    }

#ifdef XP_UNIX
#ifdef HAVE_DLL
#ifdef USE_DLFCN
    result = dlclose(lib->dlh);
#elif defined(USE_HPSHL)
    result = shl_unload(lib->dlh);
#elif defined(USE_MACH_DYLD)
    if (lib->dlh) {
        result = NSUnLinkModule(lib->dlh, NSUNLINKMODULE_OPTION_NONE) ? 0 : -1;
    }
#else
#error Configuration error
#endif
#endif /* HAVE_DLL */
#endif /* XP_UNIX */
#ifdef XP_PC
    if (lib->dlh) {
        FreeLibrary((HINSTANCE)(lib->dlh));
        lib->dlh = (HINSTANCE)NULL;
    }
#endif  /* XP_PC */

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)
    /* Close the connection */
    if (lib->connection) {
        CloseConnection(&(lib->connection));
    }
    if (lib->bundle) {
        CFRelease(lib->bundle);
    }
    if (lib->wrappers) {
        CFRelease(lib->wrappers);
    }
    /* No way to unload an image (lib->image) */
#endif

    /* unlink from library search list */
    if (pr_loadmap == lib) {
        pr_loadmap = pr_loadmap->next;
    }
    else if (pr_loadmap != NULL) {
        PRLibrary* prev = pr_loadmap;
        PRLibrary* next = pr_loadmap->next;
        while (next != NULL) {
            if (next == lib) {
                prev->next = next->next;
                goto freeLib;
            }
            prev = next;
            next = next->next;
        }
        /*
         * fail (the library is not on the _pr_loadmap list),
         * but don't wipe out an error from dlclose/shl_unload.
         */
        PR_NOT_REACHED("_pr_loadmap and lib->refCount inconsistent");
        if (result == 0) {
            PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
            status = PR_FAILURE;
        }
    }
    /*
     * We free the PRLibrary structure whether dlclose/shl_unload
     * succeeds or not.
     */

freeLib:
    PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("Unloaded library %s", lib->name));
    free(lib->name);
    lib->name = NULL;
    PR_DELETE(lib);
    if (result != 0) {
        PR_SetError(PR_UNLOAD_LIBRARY_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        status = PR_FAILURE;
    }

done:
    PR_ExitMonitor(pr_linker_lock);
    return status;
}

static void*
pr_FindSymbolInLib(PRLibrary *lm, const char *name)
{
    void *f = NULL;
#ifdef XP_OS2
    int rc;
#endif

    if (lm->staticTable != NULL) {
        const PRStaticLinkTable* tp;
        for (tp = lm->staticTable; tp->name; tp++) {
            if (strcmp(name, tp->name) == 0) {
                return (void*) tp->fp;
            }
        }
        /*
        ** If the symbol was not found in the static table then check if
        ** the symbol was exported in the DLL... Win16 only!!
        */
#if !defined(WIN16)
        PR_SetError(PR_FIND_SYMBOL_ERROR, 0);
        return (void*)NULL;
#endif
    }

#ifdef XP_OS2
    rc = DosQueryProcAddr(lm->dlh, 0, (PSZ) name, (PFN *) &f);
#if defined(NEED_LEADING_UNDERSCORE)
    /*
     * Older plugins (not built using GCC) will have symbols that are not
     * underscore prefixed.  We check for that here.
     */
    if (rc != NO_ERROR) {
        name++;
        DosQueryProcAddr(lm->dlh, 0, (PSZ) name, (PFN *) &f);
    }
#endif
#endif  /* XP_OS2 */

#ifdef WIN32
    f = GetProcAddress(lm->dlh, name);
#endif  /* WIN32 */

#if defined(XP_MACOSX) && defined(USE_MACH_DYLD)
    /* add this offset to skip the leading underscore in name */
#define SYM_OFFSET 1
    if (lm->bundle) {
        CFStringRef nameRef = CFStringCreateWithCString(NULL, name + SYM_OFFSET, kCFStringEncodingASCII);
        if (nameRef) {
            f = CFBundleGetFunctionPointerForName(lm->bundle, nameRef);
            CFRelease(nameRef);
        }
    }
    if (lm->connection) {
        Ptr                 symAddr;
        CFragSymbolClass    symClass;
        Str255              pName;

        PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("Looking up symbol: %s", name + SYM_OFFSET));

        c2pstrcpy(pName, name + SYM_OFFSET);

        f = (FindSymbol(lm->connection, pName, &symAddr, &symClass) == noErr) ? symAddr : NULL;

#ifdef __ppc__
        /* callers expect mach-o function pointers, so must wrap tvectors with glue. */
        if (f && symClass == kTVectorCFragSymbol) {
            f = TV2FP(lm->wrappers, name + SYM_OFFSET, f);
        }
#endif /* __ppc__ */

        if (f == NULL && strcmp(name + SYM_OFFSET, "main") == 0) {
            f = lm->main;
        }
    }
    if (lm->image) {
        NSSymbol symbol;
        symbol = NSLookupSymbolInImage(lm->image, name,
                                       NSLOOKUPSYMBOLINIMAGE_OPTION_BIND
                                       | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR);
        if (symbol != NULL) {
            f = NSAddressOfSymbol(symbol);
        }
        else {
            f = NULL;
        }
    }
#undef SYM_OFFSET
#endif /* XP_MACOSX && USE_MACH_DYLD */

#ifdef XP_UNIX
#ifdef HAVE_DLL
#ifdef USE_DLFCN
    f = dlsym(lm->dlh, name);
#elif defined(USE_HPSHL)
    if (shl_findsym(&lm->dlh, name, TYPE_PROCEDURE, &f) == -1) {
        f = NULL;
    }
#elif defined(USE_MACH_DYLD)
    if (lm->dlh) {
        NSSymbol symbol;
        symbol = NSLookupSymbolInModule(lm->dlh, name);
        if (symbol != NULL) {
            f = NSAddressOfSymbol(symbol);
        }
        else {
            f = NULL;
        }
    }
#endif
#endif /* HAVE_DLL */
#endif /* XP_UNIX */
    if (f == NULL) {
        PR_SetError(PR_FIND_SYMBOL_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
    }
    return f;
}

/*
** Called by class loader to resolve missing native's
*/
PR_IMPLEMENT(void*)
PR_FindSymbol(PRLibrary *lib, const char *raw_name)
{
    void *f = NULL;
#if defined(NEED_LEADING_UNDERSCORE)
    char *name;
#else
    const char *name;
#endif
    /*
    ** Mangle the raw symbol name in any way that is platform specific.
    */
#if defined(NEED_LEADING_UNDERSCORE)
    /* Need a leading _ */
    name = PR_smprintf("_%s", raw_name);
#elif defined(AIX)
    /*
    ** AIX with the normal linker put's a "." in front of the symbol
    ** name.  When use "svcc" and "svld" then the "." disappears. Go
    ** figure.
    */
    name = raw_name;
#else
    name = raw_name;
#endif

    PR_EnterMonitor(pr_linker_lock);
    PR_ASSERT(lib != NULL);
    f = pr_FindSymbolInLib(lib, name);

#if defined(NEED_LEADING_UNDERSCORE)
    PR_smprintf_free(name);
#endif

    PR_ExitMonitor(pr_linker_lock);
    return f;
}

/*
** Return the address of the function 'raw_name' in the library 'lib'
*/
PR_IMPLEMENT(PRFuncPtr)
PR_FindFunctionSymbol(PRLibrary *lib, const char *raw_name)
{
    return ((PRFuncPtr) PR_FindSymbol(lib, raw_name));
}

PR_IMPLEMENT(void*)
PR_FindSymbolAndLibrary(const char *raw_name, PRLibrary* *lib)
{
    void *f = NULL;
#if defined(NEED_LEADING_UNDERSCORE)
    char *name;
#else
    const char *name;
#endif
    PRLibrary* lm;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    /*
    ** Mangle the raw symbol name in any way that is platform specific.
    */
#if defined(NEED_LEADING_UNDERSCORE)
    /* Need a leading _ */
    name = PR_smprintf("_%s", raw_name);
#elif defined(AIX)
    /*
    ** AIX with the normal linker put's a "." in front of the symbol
    ** name.  When use "svcc" and "svld" then the "." disappears. Go
    ** figure.
    */
    name = raw_name;
#else
    name = raw_name;
#endif

    PR_EnterMonitor(pr_linker_lock);

    /* search all libraries */
    for (lm = pr_loadmap; lm != NULL; lm = lm->next) {
        f = pr_FindSymbolInLib(lm, name);
        if (f != NULL) {
            *lib = lm;
            lm->refCount++;
            PR_LOG(_pr_linker_lm, PR_LOG_MIN,
                   ("%s incr => %d (for %s)",
                    lm->name, lm->refCount, name));
            break;
        }
    }
#if defined(NEED_LEADING_UNDERSCORE)
    PR_smprintf_free(name);
#endif

    PR_ExitMonitor(pr_linker_lock);
    return f;
}

PR_IMPLEMENT(PRFuncPtr)
PR_FindFunctionSymbolAndLibrary(const char *raw_name, PRLibrary* *lib)
{
    return ((PRFuncPtr) PR_FindSymbolAndLibrary(raw_name, lib));
}

/*
** Add a static library to the list of loaded libraries. If LoadLibrary
** is called with the name then we will pretend it was already loaded
*/
PR_IMPLEMENT(PRLibrary*)
PR_LoadStaticLibrary(const char *name, const PRStaticLinkTable *slt)
{
    PRLibrary *lm=NULL;
    PRLibrary* result = NULL;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    /* See if library is already loaded */
    PR_EnterMonitor(pr_linker_lock);

    /* If the lbrary is already loaded, then add the static table information... */
    result = pr_UnlockedFindLibrary(name);
    if (result != NULL) {
        PR_ASSERT( (result->staticTable == NULL) || (result->staticTable == slt) );
        result->staticTable = slt;
        goto unlock;
    }

    /* Add library to list...Mark it static */
    lm = PR_NEWZAP(PRLibrary);
    if (lm == NULL) {
        goto unlock;
    }

    lm->name = strdup(name);
    lm->refCount    = 1;
    lm->dlh         = pr_exe_loadmap ? pr_exe_loadmap->dlh : 0;
    lm->staticTable = slt;
    lm->next        = pr_loadmap;
    pr_loadmap      = lm;

    result = lm;    /* success */
    PR_ASSERT(lm->refCount == 1);
    PR_LOG(_pr_linker_lm, PR_LOG_MIN, ("Loaded library %s (static lib)", lm->name));
unlock:
    PR_ExitMonitor(pr_linker_lock);
    return result;
}

PR_IMPLEMENT(char *)
PR_GetLibraryFilePathname(const char *name, PRFuncPtr addr)
{
#if defined(USE_DLFCN) && defined(HAVE_DLADDR)
    Dl_info dli;
    char *result;

    if (dladdr((void *)addr, &dli) == 0) {
        PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        return NULL;
    }
    result = PR_Malloc(strlen(dli.dli_fname)+1);
    if (result != NULL) {
        strcpy(result, dli.dli_fname);
    }
    return result;
#elif defined(USE_MACH_DYLD)
    char *result;
    const char *image_name;
    int i, count = _dyld_image_count();

    for (i = 0; i < count; i++) {
        image_name = _dyld_get_image_name(i);
        if (strstr(image_name, name) != NULL) {
            result = PR_Malloc(strlen(image_name)+1);
            if (result != NULL) {
                strcpy(result, image_name);
            }
            return result;
        }
    }
    PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, 0);
    return NULL;
#elif defined(AIX)
    char *result;
#define LD_INFO_INCREMENT 64
    struct ld_info *info;
    unsigned int info_length = LD_INFO_INCREMENT * sizeof(struct ld_info);
    struct ld_info *infop;
    int loadflags = L_GETINFO | L_IGNOREUNLOAD;

    for (;;) {
        info = PR_Malloc(info_length);
        if (info == NULL) {
            return NULL;
        }
        /* If buffer is too small, loadquery fails with ENOMEM. */
        if (loadquery(loadflags, info, info_length) != -1) {
            break;
        }
        /*
         * Calling loadquery when compiled for 64-bit with the
         * L_IGNOREUNLOAD flag can cause an invalid argument error
         * on AIX 5.1. Detect this error the first time that
         * loadquery is called, and try calling it again without
         * this flag set.
         */
        if (errno == EINVAL && (loadflags & L_IGNOREUNLOAD)) {
            loadflags &= ~L_IGNOREUNLOAD;
            if (loadquery(loadflags, info, info_length) != -1) {
                break;
            }
        }
        PR_Free(info);
        if (errno != ENOMEM) {
            /* should not happen */
            _PR_MD_MAP_DEFAULT_ERROR(_MD_ERRNO());
            return NULL;
        }
        /* retry with a larger buffer */
        info_length += LD_INFO_INCREMENT * sizeof(struct ld_info);
    }

    for (infop = info;
         ;
         infop = (struct ld_info *)((char *)infop + infop->ldinfo_next)) {
        unsigned long start = (unsigned long)infop->ldinfo_dataorg;
        unsigned long end = start + infop->ldinfo_datasize;
        if (start <= (unsigned long)addr && end > (unsigned long)addr) {
            result = PR_Malloc(strlen(infop->ldinfo_filename)+1);
            if (result != NULL) {
                strcpy(result, infop->ldinfo_filename);
            }
            break;
        }
        if (!infop->ldinfo_next) {
            PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, 0);
            result = NULL;
            break;
        }
    }
    PR_Free(info);
    return result;
#elif defined(HPUX) && defined(USE_HPSHL)
    int index;
    struct shl_descriptor desc;
    char *result;

    for (index = 0; shl_get_r(index, &desc) == 0; index++) {
        if (strstr(desc.filename, name) != NULL) {
            result = PR_Malloc(strlen(desc.filename)+1);
            if (result != NULL) {
                strcpy(result, desc.filename);
            }
            return result;
        }
    }
    /*
     * Since the index value of a library is decremented if
     * a library preceding it in the shared library search
     * list was unloaded, it is possible that we missed some
     * libraries as we went up the list.  So we should go
     * down the list to be sure that we not miss anything.
     */
    for (index--; index >= 0; index--) {
        if ((shl_get_r(index, &desc) == 0)
            && (strstr(desc.filename, name) != NULL)) {
            result = PR_Malloc(strlen(desc.filename)+1);
            if (result != NULL) {
                strcpy(result, desc.filename);
            }
            return result;
        }
    }
    PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, 0);
    return NULL;
#elif defined(HPUX) && defined(USE_DLFCN)
    struct load_module_desc desc;
    char *result;
    const char *module_name;

    if (dlmodinfo((unsigned long)addr, &desc, sizeof desc, NULL, 0, 0) == 0) {
        PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        return NULL;
    }
    module_name = dlgetname(&desc, sizeof desc, NULL, 0, 0);
    if (module_name == NULL) {
        /* should not happen */
        _PR_MD_MAP_DEFAULT_ERROR(_MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        return NULL;
    }
    result = PR_Malloc(strlen(module_name)+1);
    if (result != NULL) {
        strcpy(result, module_name);
    }
    return result;
#elif defined(WIN32)
    PRUnichar wname[MAX_PATH];
    HMODULE handle = NULL;
    PRUnichar module_name[MAX_PATH];
    int len;
    char *result;

    if (MultiByteToWideChar(CP_ACP, 0, name, -1, wname, MAX_PATH)) {
        handle = GetModuleHandleW(wname);
    }
    if (handle == NULL) {
        PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        return NULL;
    }
    if (GetModuleFileNameW(handle, module_name, MAX_PATH) == 0) {
        /* should not happen */
        _PR_MD_MAP_DEFAULT_ERROR(_MD_ERRNO());
        return NULL;
    }
    len = WideCharToMultiByte(CP_ACP, 0, module_name, -1,
                              NULL, 0, NULL, NULL);
    if (len == 0) {
        _PR_MD_MAP_DEFAULT_ERROR(_MD_ERRNO());
        return NULL;
    }
    result = PR_Malloc(len * sizeof(PRUnichar));
    if (result != NULL) {
        WideCharToMultiByte(CP_ACP, 0, module_name, -1,
                            result, len, NULL, NULL);
    }
    return result;
#elif defined(XP_OS2)
    HMODULE module = NULL;
    char module_name[_MAX_PATH];
    char *result;
    APIRET ulrc = DosQueryModFromEIP(&module, NULL, 0, NULL, NULL, (ULONG) addr);
    if ((NO_ERROR != ulrc) || (NULL == module) ) {
        PR_SetError(PR_LIBRARY_NOT_LOADED_ERROR, _MD_ERRNO());
        DLLErrorInternal(_MD_ERRNO());
        return NULL;
    }
    ulrc = DosQueryModuleName(module, sizeof module_name, module_name);
    if (NO_ERROR != ulrc) {
        /* should not happen */
        _PR_MD_MAP_DEFAULT_ERROR(_MD_ERRNO());
        return NULL;
    }
    result = PR_Malloc(strlen(module_name)+1);
    if (result != NULL) {
        strcpy(result, module_name);
    }
    return result;
#else
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return NULL;
#endif
}
