/* $Id: kLdrDyldMod.c 33 2009-07-01 21:22:37Z bird $ */
/** @file
 * kLdr - The Dynamic Loader, Dyld module methods.
 */

/*
 * Copyright (c) 2006-2007 Knut St. Osmundsen <bird-kStuff-spamix@anduin.net>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <k/kLdr.h>
#include "kLdrInternal.h"


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** @def KLDRDYLDMOD_STRICT
 * Define KLDRDYLDMOD_STRICT to enabled strict checks in kLdrDyld. */
#define KLDRDYLDMOD_STRICT 1

/** @def KLDRDYLDMOD_ASSERT
 * Assert that an expression is true when KLDRDYLD_STRICT is defined.
 */
#ifdef KLDRDYLDMOD_STRICT
# define KLDRDYLDMOD_ASSERT(expr)  kHlpAssert(expr)
#else
# define KLDRDYLDMOD_ASSERT(expr)  do {} while (0)
#endif

/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static void kldrDyldModUnlink(PKLDRDYLDMOD pMod);



/**
 * Creates a module from the specified file provider instance.
 *
 * @returns 0 on success and *ppMod pointing to the new instance.
 *          On failure a non-zero kLdr status code is returned.
 * @param   pRdr    The file provider instance.
 * @param   fFlags  Load/search flags.
 * @param   ppMod   Where to put the pointer to the new module on success.
 */
int kldrDyldModCreate(PKRDR pRdr, KU32 fFlags, PPKLDRDYLDMOD ppMod)
{
    PKLDRDYLDMOD pMod;
    PKLDRMOD pRawMod;
    int rc;

    *ppMod = NULL;

/** @todo deal with fFlags (exec/dll) */
/** @todo Check up the cpu architecture. */

    /*
     * Try open an module interpreter.
     */
    rc = kLdrModOpenFromRdr(pRdr, 0 /*fFlags*/, KCPUARCH_UNKNOWN, &pRawMod);
    if (rc)
        return kldrDyldFailure(rc, "%s: %rc", kRdrName(pRdr), rc);

    /*
     * Match the module aginst the load flags.
     */
    switch (pRawMod->enmType)
    {
        case KLDRTYPE_EXECUTABLE_FIXED:
        case KLDRTYPE_EXECUTABLE_RELOCATABLE:
        case KLDRTYPE_EXECUTABLE_PIC:
            if (!(fFlags & KLDRDYLD_LOAD_FLAGS_EXECUTABLE))
            {
                kLdrModClose(pRawMod);
                return KLDR_ERR_NOT_EXE;
            }
            break;

        case KLDRTYPE_OBJECT: /* We can handle these as DLLs. */
        case KLDRTYPE_SHARED_LIBRARY_FIXED:
        case KLDRTYPE_SHARED_LIBRARY_RELOCATABLE:
        case KLDRTYPE_SHARED_LIBRARY_PIC:
        case KLDRTYPE_FORWARDER_DLL:
            if (fFlags & KLDRDYLD_LOAD_FLAGS_EXECUTABLE)
            {
                kLdrModClose(pRawMod);
                return KLDR_ERR_NOT_DLL;
            }
            break;

        default:
            KLDRDYLDMOD_ASSERT(!"Bad enmType!");
        case KLDRTYPE_CORE:
            return fFlags & KLDRDYLD_LOAD_FLAGS_EXECUTABLE ? KLDR_ERR_NOT_EXE : KLDR_ERR_NOT_DLL;
    }

    /*
     * Allocate a new dyld module.
     */
    pMod = (PKLDRDYLDMOD)kHlpAlloc(sizeof(*pMod));
    if (pMod)
    {
        pMod->enmState = KLDRSTATE_OPEN;
        pMod->pMod = pRawMod;
        pMod->hMod = pMod;
        pMod->cDepRefs = pMod->cDynRefs = pMod->cRefs = 0;
        switch (pRawMod->enmType)
        {
            case KLDRTYPE_EXECUTABLE_FIXED:
            case KLDRTYPE_EXECUTABLE_RELOCATABLE:
            case KLDRTYPE_EXECUTABLE_PIC:
                pMod->fExecutable = 1;
                break;
            default:
                pMod->fExecutable = 0;
                break;
        }
        pMod->fGlobalOrSpecific = 0;
        pMod->fBindable = 0;
        pMod->fInitList = 0;
        pMod->fAlreadySeen = 0;
        pMod->fMapped = 0;
        pMod->fAllocatedTLS = 0;
        pMod->f25Reserved = 0;
        pMod->InitTerm.pNext = NULL;
        pMod->InitTerm.pPrev = NULL;
        pMod->Bind.pNext = NULL;
        pMod->Bind.pPrev = NULL;
        pMod->cPrereqs = 0;
        pMod->papPrereqs = NULL;
        pMod->u32MagicHead = KLDRDYMOD_MAGIC;
        pMod->u32MagicTail = KLDRDYMOD_MAGIC;

        /* it. */
        pMod->Load.pNext = NULL;
        pMod->Load.pPrev = kLdrDyldTail;
        if (kLdrDyldTail)
            kLdrDyldTail->Load.pNext = pMod;
        else
            kLdrDyldHead = pMod;
        kLdrDyldTail = pMod;

        /* deal with the remaining flags. */
        if (fFlags & KLDRYDLD_LOAD_FLAGS_SPECIFIC_MODULE)
            kldrDyldModMarkSpecific(pMod);
        else
            kldrDyldModMarkGlobal(pMod);

        if (fFlags & KLDRYDLD_LOAD_FLAGS_GLOBAL_SYMBOLS)
            kldrDyldModSetBindable(pMod, 0 /* not deep binable */);
        else
            kldrDyldModClearBindable(pMod);

        /*
         * We're good.
         */
        *ppMod = pMod;
        rc = 0;
    }
    else
    {
        kLdrModClose(pRawMod);
        rc = KERR_NO_MEMORY;
    }
    return rc;
}


/**
 * Creates a module for a native module.
 *
 * @returns 0 on success and *ppMod pointing to the new instance.
 *          On failure a non-zero kLdr status code is returned.
 * @param   hNativeModule   The native handle.
 * @param   ppMod           Where to put the pointer to the new module on success.
 * @remark  This function ain't finalized yet.
 */
int kldrDyldModCreateNative(KUPTR hNativeModule)
{
#if 0
    /*
     * Check if this module is already loaded by the native OS loader.
     */
    rc = kld
    {
#if K_OS == K_OS_OS2
    HMODULE hmod = NULLHANDLE;
    APIRET rc = DosQueryModuleHandle(kRdrName(pRdr), &hmod);
    if (!rc)

#elif K_OS == K_OS_WINDOWS
    HMODULE hmod = NULL;
    if (GetModuleHandle(kRdrName(pRdr))

#else
# error "Port me"
#endif
    }
#endif
    return -1;
}


/**
 * Destroys a module pending destruction.
 *
 * @param   pMod        The module in question.
 */
void kldrDyldModDestroy(PKLDRDYLDMOD pMod)
{
    int rc;

    /*
     * Validate the state.
     */
    switch (pMod->enmState)
    {
        case KLDRSTATE_PENDING_DESTROY:
        case KLDRSTATE_GC:
            break;
        default:
            KLDRDYLDMOD_ASSERT(!"Invalid state");
            break;
    }
    KLDRDYLDMOD_ASSERT(!pMod->fInitList);
    KLDRDYLDMOD_ASSERT(!pMod->cDynRefs);
    KLDRDYLDMOD_ASSERT(!pMod->cDepRefs);

    /*
     * Ensure that the module is unmapped.
     */
    if (pMod->fAllocatedTLS)
    {
        kLdrModFreeTLS(pMod->pMod);
        pMod->fAllocatedTLS = 0;
    }
    if (pMod->fMapped)
    {
        rc = kLdrModUnmap(pMod->pMod); KLDRDYLDMOD_ASSERT(!rc);
        pMod->fMapped = 0;
    }

    /*
     * Ensure it's unlinked from all chains.
     */
    if (pMod->enmState < KLDRSTATE_PENDING_DESTROY)
        kldrDyldModUnlink(pMod);

    /*
     * Free everything associated with the module.
     */
    /* the prerequisite array. */
    if (pMod->papPrereqs)
    {
        KU32 i = pMod->cPrereqs;
        while (i-- > 0)
        {
            KLDRDYLDMOD_ASSERT(pMod->papPrereqs[i] == NULL);
            pMod->papPrereqs[i] = NULL;
        }

        kHlpFree(pMod->papPrereqs);
        pMod->papPrereqs = NULL;
        pMod->cPrereqs = 0;
    }

    /* the module interpreter.  */
    if (pMod->pMod)
    {
        rc = kLdrModClose(pMod->pMod); KLDRDYLDMOD_ASSERT(!rc);
        pMod->pMod = NULL;
    }


    /*
     * Finally, change the module state and free the module if
     * there are not more references to it. If somebody is still
     * referencing it, postpone the freeing to Deref.
     */
    pMod->enmState = KLDRSTATE_DESTROYED;
    if (!pMod->cRefs)
    {
        pMod->u32MagicHead = 1;
        pMod->u32MagicTail = 2;
        kHlpFree(pMod);
    }
}


/**
 * Unlinks the module from any list it might be in.
 * It is assumed that the module is at least linked into the load list.
 *
 * @param   pMod    The moduel.
 */
static void kldrDyldModUnlink(PKLDRDYLDMOD pMod)
{
    /* load list */
    if (pMod->Load.pNext)
        pMod->Load.pNext->Load.pPrev = pMod->Load.pPrev;
    else
        kLdrDyldTail = pMod->Load.pPrev;
    if (pMod->Load.pPrev)
        pMod->Load.pPrev->Load.pNext = pMod->Load.pNext;
    else
        kLdrDyldHead = pMod->Load.pNext;

    /* bind list */
    if (pMod->fBindable)
        kldrDyldModClearBindable(pMod);

    /* init term */
    if (pMod->fInitList)
    {
        KLDRDYLDMOD_ASSERT(pMod->enmState < KLDRSTATE_INITIALIZATION_FAILED);
        pMod->fInitList = 0;
        if (pMod->InitTerm.pNext)
            pMod->InitTerm.pNext->InitTerm.pPrev = pMod->InitTerm.pPrev;
        else
            g_pkLdrDyldInitTail = pMod->InitTerm.pPrev;
        if (pMod->InitTerm.pPrev)
            pMod->InitTerm.pPrev->InitTerm.pNext = pMod->InitTerm.pNext;
        else
            g_pkLdrDyldInitHead = pMod->InitTerm.pNext;
    }
    else if (pMod->enmState > KLDRSTATE_INITIALIZATION_FAILED)
    {
        KLDRDYLDMOD_ASSERT(pMod->enmState >= KLDRSTATE_GOOD);
        if (pMod->InitTerm.pNext)
            pMod->InitTerm.pNext->InitTerm.pPrev = pMod->InitTerm.pPrev;
        else
            g_pkLdrDyldTermTail = pMod->InitTerm.pPrev;
        if (pMod->InitTerm.pPrev)
            pMod->InitTerm.pPrev->InitTerm.pNext = pMod->InitTerm.pNext;
        else
            g_pkLdrDyldTermHead = pMod->InitTerm.pNext;
    }
    pMod->InitTerm.pNext = NULL;
    pMod->InitTerm.pPrev = NULL;
}


/**
 * Marks a module as bindable, i.e. it'll be considered when
 * resolving names the unix way.
 *
 * @param   pMod    The module.
 * @param   fDeep   When set the module will be inserted at the head of the
 *                  module list used to resolve symbols. This means that the
 *                  symbols in this module will be prefered of all the other
 *                  modules.
 */
void kldrDyldModSetBindable(PKLDRDYLDMOD pMod, unsigned fDeep)
{
    KLDRDYLDMOD_ASSERT(pMod->enmState >= KLDRSTATE_OPEN && pMod->enmState < KLDRSTATE_PENDING_GC);
    if (!pMod->fBindable)
    {
        pMod->fBindable = 1;
        if (!fDeep)
        {
            pMod->Bind.pNext = NULL;
            pMod->Bind.pPrev = g_pkLdrDyldBindTail;
            if (g_pkLdrDyldBindTail)
                g_pkLdrDyldBindTail->Bind.pNext = pMod;
            else
                g_pkLdrDyldBindHead = pMod;
            g_pkLdrDyldBindTail = pMod;
        }
        else
        {
            pMod->Bind.pPrev = NULL;
            pMod->Bind.pNext = g_pkLdrDyldBindHead;
            if (g_pkLdrDyldBindHead)
                g_pkLdrDyldBindHead->Bind.pPrev = pMod;
            else
                g_pkLdrDyldBindTail = pMod;
            g_pkLdrDyldBindHead = pMod;
        }
    }
}


/**
 * Marks a module as not bindable, i.e. it will not be considered when
 * resolving names the unix way.
 *
 * @param   pMod    The module.
 */
void kldrDyldModClearBindable(PKLDRDYLDMOD pMod)
{
    KLDRDYLDMOD_ASSERT(pMod->enmState >= KLDRSTATE_OPEN && pMod->enmState < KLDRSTATE_PENDING_DESTROY);
    if (pMod->fBindable)
    {
        pMod->fBindable = 0;
        if (pMod->Bind.pPrev)
            pMod->Bind.pPrev->Bind.pNext = pMod->Bind.pNext;
        else
            g_pkLdrDyldBindHead = pMod->Bind.pNext;
        if (pMod->Bind.pNext)
            pMod->Bind.pNext->Bind.pPrev = pMod->Bind.pPrev;
        else
            g_pkLdrDyldBindTail = pMod->Bind.pPrev;
        pMod->Bind.pNext = NULL;
        pMod->Bind.pPrev = NULL;
    }
}


/**
 * Marks the module as global instead of being specific.
 *
 * A global module can be a matching result when the request
 * doesn't specify a path. A specific module will not match
 * unless the path also matches.
 *
 * @param   pMod    The module.
 */
void kldrDyldModMarkGlobal(PKLDRDYLDMOD pMod)
{
    pMod->fGlobalOrSpecific = 1;
}


/**
 * Marks the module as specific instead of global.
 *
 * See kldrDyldModMarkGlobal for an explanation of the two terms.
 *
 * @param   pMod    The module.
 */
void kldrDyldModMarkSpecific(PKLDRDYLDMOD pMod)
{
    pMod->fGlobalOrSpecific = 0;
}


/**
 * Adds a reference to the module making sure it won't be freed just yet.
 *
 * @param   pMod    The module.
 */
void kldrDyldModAddRef(PKLDRDYLDMOD pMod)
{
    pMod->cRefs++;
}


/**
 * Dereference a module.
 *
 * @param   pMod
 */
void kldrDyldModDeref(PKLDRDYLDMOD pMod)
{
    /* validate input */
    KLDRDYLDMOD_ASSERT(pMod->cRefs > 0);
    KLDRDYLDMOD_ASSERT(pMod->cRefs >= pMod->cDepRefs + pMod->cDynRefs);
    KLDRDYLDMOD_ASSERT(pMod->enmState > KLDRSTATE_INVALID && pMod->enmState <= KLDRSTATE_END);

    /* decrement. */
    if (pMod->cRefs > 0)
        pMod->cRefs--;

    /* execute delayed freeing. */
    if (    pMod->enmState == KLDRSTATE_DESTROYED
        &&  !pMod->cRefs)
    {
        pMod->u32MagicHead = 1;
        pMod->u32MagicTail = 2;
        kHlpFree(pMod);
    }
}


/**
 * Increment the count of modules depending on this module.
 *
 * @param   pMod    The module.
 * @param   pDep    The module which depends on us.
 */
void kldrDyldModAddDep(PKLDRDYLDMOD pMod, PKLDRDYLDMOD pDep)
{
    (void)pDep;

    /* validate state */
    switch (pMod->enmState)
    {
        case KLDRSTATE_MAPPED:
        case KLDRSTATE_RELOADED:
        case KLDRSTATE_LOADED_PREREQUISITES:
        case KLDRSTATE_RELOADED_LOADED_PREREQUISITES:
        case KLDRSTATE_PENDING_INITIALIZATION:
        case KLDRSTATE_INITIALIZING:
        case KLDRSTATE_GOOD:
            break;
        default:
            KLDRDYLDMOD_ASSERT(!"invalid state");
            break;

    }
    KLDRDYLDMOD_ASSERT(pMod->enmState > KLDRSTATE_INVALID && pMod->enmState <= KLDRSTATE_END);
    pMod->cRefs++;
    pMod->cDepRefs++;
}


/**
 * Drop a dependency.
 *
 * @param   pMod    The module.
 * @param   pDep    The module which depends on us.
 */
void kldrDyldModRemoveDep(PKLDRDYLDMOD pMod, PKLDRDYLDMOD pDep)
{
    KLDRDYLDMOD_ASSERT(pMod->cDepRefs > 0);
    if (pMod->cDepRefs == 0)
        return;
    KLDRDYLDMOD_ASSERT(pMod->cDepRefs <= pMod->cRefs);
    KLDRDYLDMOD_ASSERT(pMod->enmState >= KLDRSTATE_MAPPED && pMod->enmState <= KLDRSTATE_PENDING_DESTROY);

    pMod->cRefs--;
    pMod->cDepRefs--;
    if (    pMod->cDepRefs > 0
        ||  pMod->cDynRefs > 0)
        return;

    /*
     * The module should be unloaded.
     */
    kldrDyldModUnloadPrerequisites(pMod);
}


/**
 * Increment the dynamic load count.
 *
 * @returns 0
 * @param   pMod    The module.
 */
int kldrDyldModDynamicLoad(PKLDRDYLDMOD pMod)
{
    KLDRDYLDMOD_ASSERT(     pMod->enmState == KLDRSTATE_GOOD
                       ||   pMod->enmState == KLDRSTATE_PENDING_INITIALIZATION
                       ||   pMod->enmState == KLDRSTATE_INITIALIZING);
    pMod->cRefs++;
    pMod->cDynRefs++;
    return 0;
}


/**
 * Decrement the dynamic load count of the module and unload the module
 * if the total reference count reaches zero.
 *
 * This may cause a cascade of unloading to occure. See kldrDyldModUnloadPrerequisites().
 *
 * @returns status code.
 * @retval  0 on success.
 * @retval  KLDR_ERR_NOT_LOADED_DYNAMICALLY if the module wasn't loaded dynamically.
 * @param   pMod        The module to unload.
 */
int kldrDyldModDynamicUnload(PKLDRDYLDMOD pMod)
{
    if (pMod->cDynRefs == 0)
        return KLDR_ERR_NOT_LOADED_DYNAMICALLY;
    KLDRDYLDMOD_ASSERT(pMod->cDynRefs <= pMod->cRefs);
    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_GOOD);

    pMod->cRefs--;
    pMod->cDynRefs--;
    if (    pMod->cDynRefs > 0
        ||  pMod->cDepRefs > 0)
        return 0;

    /*
     * The module should be unloaded.
     */
    kldrDyldModUnloadPrerequisites(pMod);
    return 0;
}


/**
 * Worker for kldrDyldModUnloadPrerequisites.
 *
 * @returns The number of modules that now can be unloaded.
 * @param   pMod    The module in  question.
 */
static KU32 kldrDyldModUnloadPrerequisitesOne(PKLDRDYLDMOD pMod)
{
    PKLDRDYLDMOD    pMod2;
    KU32            cToUnload = 0;
    KU32            i;

    KLDRDYLDMOD_ASSERT(pMod->papPrereqs || !pMod->cPrereqs);

    /*
     * Release the one in this module.
     */
    for (i = 0; i < pMod->cPrereqs; i++)
    {
        pMod2 = pMod->papPrereqs[i];
        if (pMod2)
        {
            pMod->papPrereqs[i] = NULL;

            /* do the derefering ourselves or we'll end up in a recursive loop here. */
            KLDRDYLDMOD_ASSERT(pMod2->cDepRefs > 0);
            KLDRDYLDMOD_ASSERT(pMod2->cRefs >= pMod2->cDepRefs);
            pMod2->cDepRefs--;
            pMod2->cRefs--;
            cToUnload += !pMod2->cDepRefs && !pMod2->cDynRefs;
        }
    }

    /*
     * Change the state
     */
    switch (pMod->enmState)
    {
        case KLDRSTATE_LOADED_PREREQUISITES:
        case KLDRSTATE_FIXED_UP:
            pMod->enmState = KLDRSTATE_PENDING_DESTROY;
            kldrDyldModUnlink(pMod);
            break;

        case KLDRSTATE_PENDING_INITIALIZATION:
            pMod->enmState = KLDRSTATE_PENDING_GC;
            break;

        case KLDRSTATE_RELOADED_FIXED_UP:
        case KLDRSTATE_RELOADED_LOADED_PREREQUISITES:
        case KLDRSTATE_GOOD:
            pMod->enmState = KLDRSTATE_PENDING_TERMINATION;
            break;

        case KLDRSTATE_INITIALIZATION_FAILED:
            break;

        default:
            KLDRDYLDMOD_ASSERT(!"invalid state");
            break;
    }

    return cToUnload;
}


/**
 * This is the heart of the unload code.
 *
 * It will recursivly (using the load list) initiate module unloading
 * of all affected modules.
 *
 * This function will cause a state transition to PENDING_DESTROY, PENDING_GC
 * or PENDING_TERMINATION depending on the module state. There is one exception
 * to this, and that's INITIALIZATION_FAILED, where the state will not be changed.
 *
 * @param   pMod        The module which prerequisites should be unloaded.
 */
void kldrDyldModUnloadPrerequisites(PKLDRDYLDMOD pMod)
{
    KU32            cToUnload;

    /* sanity */
#ifdef KLDRDYLD_STRICT
    {
    PKLDRDYLDMOD pMod2;
    for (pMod2 = kLdrDyldHead; pMod2; pMod2 = pMod2->Load.pNext)
        KLDRDYLDMOD_ASSERT(pMod2->enmState != KLDRSTATE_GOOD || pMod2->cRefs);
    }
#endif
    KLDRDYLDMOD_ASSERT(pMod->papPrereqs);

    /*
     * Unload prereqs of the module we're called on first.
     */
    cToUnload = kldrDyldModUnloadPrerequisitesOne(pMod);

    /*
     * Iterate the load list in a cyclic manner until there are no more
     * modules that can be pushed on into unloading.
     */
    while (cToUnload)
    {
        cToUnload = 0;
        for (pMod = kLdrDyldHead; pMod; pMod = pMod->Load.pNext)
        {
            if (    pMod->cDepRefs
                ||  pMod->cDynRefs
                ||  pMod->enmState >= KLDRSTATE_PENDING_TERMINATION
                ||  pMod->enmState < KLDRSTATE_LOADED_PREREQUISITES)
                continue;
            cToUnload += kldrDyldModUnloadPrerequisitesOne(pMod);
        }
    }
}


/**
 * Loads the prerequisite modules this module depends on.
 *
 * To find each of the prerequisite modules this method calls
 * kldrDyldGetPrerequisite() and it will make sure the modules
 * are added to the load stack frame.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 *          The state is changed to LOADED_PREREQUISITES or RELOADED_LOADED_PREREQUISITES.
 * @param   pMod            The module.
 * @param   pszPrefix       Prefix to use when searching.
 * @param   pszSuffix       Suffix to use when searching.
 * @param   enmSearch       Method to use when locating the module and any modules it may depend on.
 * @param   fFlags          Flags, a combintation of the KLDRYDLD_LOAD_FLAGS_* \#defines.
 */
int kldrDyldModLoadPrerequisites(PKLDRDYLDMOD pMod, const char *pszPrefix, const char *pszSuffix,
                                 KLDRDYLDSEARCH enmSearch, unsigned fFlags)
{
    KI32    cPrereqs;
    KU32    i;
    int     rc = 0;

    /* sanity */
    switch (pMod->enmState)
    {
        case KLDRSTATE_MAPPED:
        case KLDRSTATE_RELOADED:
            break;
        default:
            KLDRDYLDMOD_ASSERT(!"invalid state");
            return -1;
    }

    /*
     * Query number of prerequiste modules and allocate the array.
     */
    cPrereqs = kLdrModNumberOfImports(pMod->pMod, NULL);
    kHlpAssert(cPrereqs >= 0);
    if (pMod->cPrereqs != cPrereqs)
    {
        KLDRDYLDMOD_ASSERT(!pMod->papPrereqs);
        pMod->papPrereqs = (PPKLDRDYLDMOD)kHlpAllocZ(sizeof(pMod->papPrereqs[0]) * cPrereqs);
        if (!pMod->papPrereqs)
            return KERR_NO_MEMORY;
        pMod->cPrereqs = cPrereqs;
    }
    else
        KLDRDYLDMOD_ASSERT(pMod->papPrereqs || !pMod->cPrereqs);

    /*
     * Iterate the prerequisites and load them.
     */
    for (i = 0; i < pMod->cPrereqs; i++)
    {
        static char s_szPrereq[260];
        PKLDRDYLDMOD pPrereqMod;

        KLDRDYLDMOD_ASSERT(pMod->papPrereqs[i] == NULL);
        rc = kLdrModGetImport(pMod->pMod, NULL, i, s_szPrereq, sizeof(s_szPrereq));
        if (rc)
            break;
        rc = kldrDyldGetPrerequisite(s_szPrereq, pszPrefix, pszSuffix, enmSearch, fFlags, pMod, &pPrereqMod);
        if (rc)
            break;
        pMod->papPrereqs[i] = pPrereqMod;
    }

    /* change the state regardless of what happend. */
    if (pMod->enmState == KLDRSTATE_MAPPED)
        pMod->enmState = KLDRSTATE_LOADED_PREREQUISITES;
    else
        pMod->enmState = KLDRSTATE_RELOADED_LOADED_PREREQUISITES;
    return rc;
}


/**
 * Maps an open module.
 *
 * On success the module will be in the MAPPED state.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 * @param   pMod    The module which needs to be unmapped and set pending for destruction.
 */
int kldrDyldModMap(PKLDRDYLDMOD pMod)
{
    int rc;

    /* sanity */
    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_OPEN);
    KLDRDYLDMOD_ASSERT(!pMod->fMapped);
    if (pMod->fMapped)
        return 0;

    /* do the job. */
    rc = kLdrModMap(pMod->pMod);
    if (!rc)
    {
        rc = kLdrModAllocTLS(pMod->pMod);
        if (!rc)
        {
            /** @todo TLS */
            pMod->fMapped = 1;
            pMod->enmState = KLDRSTATE_MAPPED;
        }
        else
            kLdrModUnmap(pMod->pMod);
    }
    return rc;
}


/**
 * Unmaps the module, unlinks it from everywhere marks it PENDING_DESTROY.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 * @param   pMod    The module which needs to be unmapped and set pending for destruction.
 */
int kldrDyldModUnmap(PKLDRDYLDMOD pMod)
{
    int rc;

    /* sanity */
    KLDRDYLDMOD_ASSERT(pMod->cRefs > 0);
    KLDRDYLDMOD_ASSERT(pMod->fMapped);
    switch (pMod->enmState)
    {
        case KLDRSTATE_MAPPED:
        case KLDRSTATE_GC:
        case KLDRSTATE_PENDING_DESTROY:
            break;
        default:
            KLDRDYLDMOD_ASSERT(!"invalid state");
            return -1;
    }

    /* do the job. */
    if (pMod->fAllocatedTLS)
    {
        kLdrModFreeTLS(pMod->pMod);
        pMod->fAllocatedTLS = 0;
    }
    rc = kLdrModUnmap(pMod->pMod);
    if (!rc)
    {
        pMod->fMapped = 0;
        if (pMod->enmState < KLDRSTATE_PENDING_DESTROY)
        {
            pMod->enmState = KLDRSTATE_PENDING_DESTROY;
            kldrDyldModUnlink(pMod);
        }
    }

    return rc;
}


/**
 * Reloads the module.
 *
 * Reloading means that all modified pages are restored to their original
 * state. Whether this includes the code segments depends on whether the fixups
 * depend on the addend in the place they are fixing up - so it's format specific.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 * @param   pMod    The module which needs to be unmapped and set pending for destruction.
 */
int kldrDyldModReload(PKLDRDYLDMOD pMod)
{
    int rc;

    /* sanity */
    KLDRDYLDMOD_ASSERT(pMod->cRefs > 0);
    KLDRDYLDMOD_ASSERT(pMod->fMapped);

    switch (pMod->enmState)
    {
        case KLDRSTATE_MAPPED:
        case KLDRSTATE_GC:
        case KLDRSTATE_PENDING_DESTROY:
            break;
        default:
            KLDRDYLDMOD_ASSERT(!"invalid state");
            return -1;
    }

    /* Free TLS before reloading. */
    if (pMod->fAllocatedTLS)
    {
        kLdrModFreeTLS(pMod->pMod);
        pMod->fAllocatedTLS = 0;
    }

    /* Let the module interpreter do the reloading of the mapping. */
    rc = kLdrModReload(pMod->pMod);
    if (!rc)
    {
        rc = kLdrModAllocTLS(pMod->pMod);
        if (!rc)
        {
            pMod->fAllocatedTLS = 1;
            pMod->enmState = KLDRSTATE_RELOADED;
        }
    }
    return rc;
}


/**
 * @copydoc FNKLDRMODGETIMPORT
 * pvUser points to the KLDRDYLDMOD.
 */
static int kldrDyldModFixupGetImportCallback(PKLDRMOD pMod, KU32 iImport, KU32 iSymbol,
                                             const char *pchSymbol, KSIZE cchSymbol, const char *pszVersion,
                                             PKLDRADDR puValue, KU32 *pfKind, void *pvUser)
{
    static int s_cRecursiveCalls = 0;
    PKLDRDYLDMOD pDyldMod = (PKLDRDYLDMOD)pvUser;
    int rc;

    /* guard against too deep forwarder recursion. */
    if (s_cRecursiveCalls >= 5)
        return KLDR_ERR_TOO_LONG_FORWARDER_CHAIN;
    s_cRecursiveCalls++;

    if (iImport != NIL_KLDRMOD_IMPORT)
    {
        /* specific import module search. */
        PKLDRDYLDMOD pPrereqMod;

        KLDRDYLDMOD_ASSERT(iImport < pDyldMod->cPrereqs);
        pPrereqMod = pDyldMod->papPrereqs[iImport];

        KLDRDYLDMOD_ASSERT(pPrereqMod);
        KLDRDYLDMOD_ASSERT(pPrereqMod->u32MagicHead == KLDRDYMOD_MAGIC);
        KLDRDYLDMOD_ASSERT(pPrereqMod->u32MagicTail == KLDRDYMOD_MAGIC);
        KLDRDYLDMOD_ASSERT(pPrereqMod->enmState < KLDRSTATE_TERMINATING);

        rc = kLdrModQuerySymbol(pPrereqMod->pMod, NULL, KLDRMOD_BASEADDRESS_MAP,
                                iSymbol, pchSymbol, cchSymbol, pszVersion,
                                kldrDyldModFixupGetImportCallback, pPrereqMod, puValue, pfKind);
        if (rc)
        {
            if (pchSymbol)
                kldrDyldFailure(rc, "%s[%d]->%s.%.*s%s", pDyldMod->pMod->pszName, iImport,
                                pPrereqMod->pMod->pszName, cchSymbol, pchSymbol, pszVersion ? pszVersion : "");
            else
                kldrDyldFailure(rc, "%s[%d]->%s.%d%s", pDyldMod->pMod->pszName, iImport,
                                pPrereqMod->pMod->pszName, iSymbol, pszVersion ? pszVersion : "");
        }
    }
    else
    {
        /* bind list search. */
        unsigned fFound = 0;
        PKLDRDYLDMOD pBindMod = g_pkLdrDyldBindHead;
        rc = 0;
        while (pBindMod)
        {
            KU32 fKind;
            KLDRADDR uValue;
            rc = kLdrModQuerySymbol(pBindMod->pMod, NULL, KLDRMOD_BASEADDRESS_MAP,
                                    iSymbol, pchSymbol, cchSymbol, pszVersion,
                                    kldrDyldModFixupGetImportCallback, pBindMod, &uValue, &fKind);
            if (    !rc
                &&  (   !fFound
                     || !(fKind & KLDRSYMKIND_WEAK)
                    )
               )
            {
                *pfKind = fKind;
                *puValue = uValue;
                fFound = 1;
                if (!(fKind & KLDRSYMKIND_WEAK))
                    break;
            }

            /* next */
            pBindMod = pBindMod->Bind.pNext;
        }
        rc = fFound ? 0 : KLDR_ERR_SYMBOL_NOT_FOUND;
        if (!fFound)
        {
            if (pchSymbol)
                kldrDyldFailure(rc, "%s->%.*s%s", pDyldMod->pMod->pszName, cchSymbol, pchSymbol, pszVersion ? pszVersion : "");
            else
                kldrDyldFailure(rc, "%s->%d%s", pDyldMod->pMod->pszName, iSymbol, pszVersion ? pszVersion : "");
        }
    }

    s_cRecursiveCalls--;
    return rc;
}


/**
 * Applies fixups to a module which prerequisistes has been
 * successfully loaded.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 * @param   pMod    The module which needs to be unmapped and set pending for destruction.
 */
int kldrDyldModFixup(PKLDRDYLDMOD pMod)
{
    int rc;

    /* sanity */
    KLDRDYLDMOD_ASSERT(pMod->cRefs > 0);
    KLDRDYLDMOD_ASSERT(     pMod->enmState == KLDRSTATE_LOADED_PREREQUISITES
                       ||   pMod->enmState == KLDRSTATE_RELOADED_LOADED_PREREQUISITES);

    /* do the job */
    rc = kLdrModFixupMapping(pMod->pMod, kldrDyldModFixupGetImportCallback, pMod);/** @todo fixme. */
    if (!rc)
        pMod->enmState = KLDRSTATE_FIXED_UP;
    return rc;
}


/**
 * Calls the module initialization entry point if any.
 *
 * This is considered to be a module specific thing and leave if
 * to the module interpreter. They will have to deal with different
 * module init practices between platforms should there be any.
 *
 * @returns 0 and state changed to GOOD on success.
 *          Non-zero OS or kLdr status code and status changed to INITIALIZATION_FAILED on failure.
 * @param   pMod        The module that should be initialized.
 */
int kldrDyldModCallInit(PKLDRDYLDMOD pMod)
{
    int rc;

    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_PENDING_INITIALIZATION);
    KLDRDYLDMOD_ASSERT(!pMod->fInitList);

    pMod->enmState = KLDRSTATE_INITIALIZING;
    rc = kLdrModCallInit(pMod->pMod, (KUPTR)pMod->hMod);
    if (!rc)
    {
        pMod->enmState = KLDRSTATE_GOOD;
        /* push it onto the termination list.*/
        pMod->InitTerm.pPrev = NULL;
        pMod->InitTerm.pNext = g_pkLdrDyldTermHead;
        if (g_pkLdrDyldTermHead)
            g_pkLdrDyldTermHead->InitTerm.pPrev = pMod;
        else
            g_pkLdrDyldTermTail = pMod;
        g_pkLdrDyldTermHead = pMod;
    }
    else
        pMod->enmState = KLDRSTATE_INITIALIZATION_FAILED;

    return rc;
}


/**
 * Calls the module termination entry point if any.
 *
 * This'll change the module status to PENDING_GC.
 *
 * @param   pMod        The module that should be initialized.
 */
void kldrDyldModCallTerm(PKLDRDYLDMOD pMod)
{
    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_PENDING_TERMINATION);

    pMod->enmState = KLDRSTATE_TERMINATING;
    kLdrModCallTerm(pMod->pMod, (KUPTR)pMod->hMod);
    pMod->enmState = KLDRSTATE_PENDING_GC;
    /* unlinking on destruction. */
}


/**
 * Calls the thread attach entry point if any.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   pMod        The module.
 */
int kldrDyldModAttachThread(PKLDRDYLDMOD pMod)
{
    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_GOOD);

    return kLdrModCallThread(pMod->pMod, (KUPTR)pMod->hMod, 1 /* attach */);
}


/**
 * Calls the thread detach entry point if any.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   pMod        The module.
 */
void kldrDyldModDetachThread(PKLDRDYLDMOD pMod)
{
    KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_GOOD);

    kLdrModCallThread(pMod->pMod, (KUPTR)pMod->hMod, 0 /* detach */);
}


/**
 * Gets the main stack, allocate it if necessary.
 *
 * @returns 0 on success, non-zero native OS or kLdr status code on failure.
 * @param   pMod        The module.
 * @param   ppvStack    Where to store the address of the stack (lowest address).
 * @param   pcbStack    Where to store the size of the stack.
 */
int kldrDyldModGetMainStack(PKLDRDYLDMOD pMod, void **ppvStack, KSIZE *pcbStack)
{
    int rc = 0;
    KLDRSTACKINFO StackInfo;
    KLDRDYLDMOD_ASSERT(pMod->fExecutable);

    /*
     * Since we might have to allocate the stack ourselves, and there will only
     * ever be one main stack, we'll be keeping the main stack info in globals.
     */
    if (!g_fkLdrDyldDoneMainStack)
    {
        rc = kLdrModGetStackInfo(pMod->pMod, NULL, KLDRMOD_BASEADDRESS_MAP, &StackInfo);
        if (!rc)
        {
            /* check if there is a stack size override/default. */
            KSIZE cbDefOverride;
            if (kHlpGetEnvUZ("KLDR_MAIN_STACK_SIZE", &cbDefOverride))
                cbDefOverride = 0;


            /* needs allocating? */
            if (    StackInfo.LinkAddress == NIL_KLDRADDR
                ||  StackInfo.cbStack < cbDefOverride)
            {
                KSIZE cbStack = (KSIZE)K_MAX(StackInfo.cbStack, cbDefOverride);

                g_pvkLdrDyldMainStack = kldrDyldOSAllocStack(cbStack);
                if (g_pvkLdrDyldMainStack)
                {
                    g_cbkLdrDyldMainStack = cbStack;
                    g_fkLdrDyldMainStackAllocated = 1;
                }
                else
                    rc = KLDR_ERR_MAIN_STACK_ALLOC_FAILED;
            }
            else
            {
                KLDRDYLDMOD_ASSERT(StackInfo.Address != NIL_KLDRADDR);
                KLDRDYLDMOD_ASSERT(StackInfo.cbStack > 0);

                g_fkLdrDyldMainStackAllocated = 0;
                g_pvkLdrDyldMainStack = (void *)(KUPTR)StackInfo.Address;
                KLDRDYLDMOD_ASSERT((KUPTR)g_pvkLdrDyldMainStack == StackInfo.Address);

                g_cbkLdrDyldMainStack = (KSIZE)StackInfo.cbStack;
                KLDRDYLDMOD_ASSERT(StackInfo.cbStack == g_cbkLdrDyldMainStack);
            }
        }
        if (!rc)
            g_fkLdrDyldDoneMainStack = 1;
    }

    if (!rc)
    {
        if (ppvStack)
            *ppvStack = g_pvkLdrDyldMainStack;
        if (pcbStack)
            *pcbStack = g_cbkLdrDyldMainStack;
    }

    return rc;
}


/**
 * This starts the executable module.
 *
 * @returns non-zero OS or kLdr status code on failure.
 *          (won't return on success.)
 * @param   pMod    The executable module.
 */
int kldrDyldModStartExe(PKLDRDYLDMOD pMod)
{
    int         rc;
    KLDRADDR    MainEPAddress;
    void       *pvStack;
    KSIZE      cbStack;
    KLDRDYLDMOD_ASSERT(pMod->fExecutable);

    rc = kLdrModQueryMainEntrypoint(pMod->pMod, NULL, KLDRMOD_BASEADDRESS_MAP, &MainEPAddress);
    if (rc)
        return rc;
    rc = kldrDyldModGetMainStack(pMod, &pvStack, &cbStack);
    if (rc)
        return rc;
    return kldrDyldOSStartExe((KUPTR)MainEPAddress, pvStack, cbStack);
}


/**
 * Gets the module name.
 *
 * @returns 0 on success, KERR_BUFFER_OVERFLOW on failure.
 * @param   pMod            The module.
 * @param   pszName         Where to store the name.
 * @param   cchName         The size of the name buffer.
 */
int kldrDyldModGetName(PKLDRDYLDMOD pMod, char *pszName, KSIZE cchName)
{
    KSIZE cch = K_MIN(cchName, pMod->pMod->cchName + 1);
    if (cch)
    {
        kHlpMemCopy(pszName, pMod->pMod->pszName, cch - 1);
        pszName[cch - 1] = '\0';
    }
    return cchName <= pMod->pMod->cchName ? KERR_BUFFER_OVERFLOW : 0;
}


/**
 * Gets the module filename.
 *
 * @returns 0 on success, KERR_BUFFER_OVERFLOW on failure.
 * @param   pMod            The module.
 * @param   pszFilename     Where to store the filename.
 * @param   cchFilename     The size of the filename buffer.
 */
int kldrDyldModGetFilename(PKLDRDYLDMOD pMod, char *pszFilename, KSIZE cchFilename)
{
    KSIZE cch = K_MIN(cchFilename, pMod->pMod->cchFilename + 1);
    if (cch)
    {
        kHlpMemCopy(pszFilename, pMod->pMod->pszFilename, cch - 1);
        pszFilename[cch - 1] = '\0';
    }
    return cchFilename <= pMod->pMod->cchFilename ? KERR_BUFFER_OVERFLOW : 0;
}


/**
 * Gets the address/value of a symbol in the specified module.
 *
 * @returns 0 on success, KLDR_ERR_SYMBOL_NOT_FOUND on failure.
 * @param   pMod            The module.
 * @param   uSymbolOrdinal  The symbol ordinal 0. This is ignored if the name is non-zero.
 * @param   pszSymbolName   The symbol name. Can be NULL.
 * @param   puValue         Where to store the value. optional.
 * @param   pfKind          Where to store the symbol kind. optional.
 */
int kldrDyldModQuerySymbol(PKLDRDYLDMOD pMod, KU32 uSymbolOrdinal, const char *pszSymbolName,
                           KUPTR *puValue, KU32 *pfKind)
{
    int         rc;
    KLDRADDR    uValue = 0;
    KU32        fKind = 0;

    rc = kLdrModQuerySymbol(pMod->pMod, NULL, KLDRMOD_BASEADDRESS_MAP,
                            uSymbolOrdinal, pszSymbolName, kHlpStrLen(pszSymbolName), NULL,
                            kldrDyldModFixupGetImportCallback, pMod,
                            &uValue, &fKind);
    if (!rc)
    {
        if (puValue)
        {
            *puValue = (KUPTR)uValue;
            KLDRDYLDMOD_ASSERT(*puValue == uValue);
        }
        if (pfKind)
            *pfKind = fKind;
    }

    return rc;
}

