#define SCCSID  "@(#)ldrgc.c    6.26 92/03/25"

/*
 * OS/2 Module freeing and garbage collection routines
 *
 *      Copyright (c) 1983, 1984, 1991, Microsoft Corp., IBM Corp.
 *
 *      This module contains the routines required for freeing
 *      module table entries and the segments or objects associated
 *      with the module table entry.
 *
 *      The program and library module structure is setup as a graph of
 *      modules used by a process or dynamicaly linked to by another module.
 *
 *      A handle is kept in the process's PTDA for the program module
 *      the process is executing.
 *
 *      All of the modules loaded in the system are kept in a NULL
 *      terminated linked list.  The link is made using the linear
 *      address of the MTE.
 *
 *      Each process owns a (possibly NULL) list of runtime library
 *      loads, known as the ldrdld (ldr demand load data) records.
 *      These associate a process and a module, and keep track of the
 *      number of times a process has referenced a module.
 *
 *  Change History
 *  Activity      Date    BY   Reason
 *  --------      ----    ---  -------
 *  79048       03/02/94  TPL  Clear pointer to proc name when freeing task
 *
 *  137076  12/8/95  Change Team - change clean-up of nonresident names table
 *                                 to be cleaned-up during garbage collection.
 */

#define FNO_NUM FNO_LDRGC_C
#include <fnomem.h>
#define INCL_TYPES

#include <basemac.h>
#include <basedef.h>
#include <osconst.h>
#include <ostypes.h>
#include <osmac.h>
#include <mi.h>
#define INCL_DOSERRORS
#include <bseerr.h>
#include <vmowner.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <pgexport.h>
#include <vmexport.h>
#include <selxport.h>
#include <tkexport.h>
#include <infoseg.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <screen.h>
#include <sem.h>
#include <tkptda.h>
#include <ldrxport.h>
#define FOR_EXEHDR      1
#include <newexe.h>
#include <exe386.h>
#include <kmode.h>
#include <ldrvars.h>
#include <seldesc.h>
#include <sft.h>
#include <mvdmexp.h>
#include <dynxport.h>
#include <string.h>



extern UCHAR DDInitTime;         // TRUE if at DDInitTime


/***LD  pldrdldHeadKernel - Kernel Demand Load reference list head.
 *
 *      pldrdldHeadKernel is a list of kernel demand load data elements.
 *      This list contains references from the kernel into the MTE
 *      graph to prevent garbage collection on those modules.  This
 *      is used to keep the VDD modules around, even though they
 *      are not referenced by any process in the system.
 *
 *      To reference this list, call ldrIncDLDRecord, ldrDecDLDRecord,
 *      ldrWasLoadModuled, or ldrMarkTaskInGraph with pptda = NULL.
 *
 */

// Moved to sdata.asm for System Anchor Block - f122427
extern ldrdld_t *pldrdldHeadKernel;





/***LD  ldrDoGCollect - Garbage Collection Required flag.
 *
 *      ldrDoGCollect is set whenever it is determined that garbage
 *      collection may be required.
 *
 *      ldrDoGCollect is checked in LDRClearSem.
 *
 */

UCHAR ldrDoGCollect = 0;





/***LD  dllterm - DLL termination data
 *
 *      The following variables are used in DLL termination setup.
 *      Hold the loader semaphore whilst using these global variables.
 */

ulong_t           cDLLTerm;   /* Count of DLLs getting termination */
static dllterm_t *pDLLTerm;   /* Pointer to data buffer */





/***LD  ldrcDLLDetach - count of DLLs being detached from a process
 *
 *      The following variable is used to count the number of
 *      DLLs that will be detached as a result of a DosFreeModule.
 *      You must own the loader semaphore while using this variable.
 */

ulong_t ldrcDLLDetach;                  /* Count of DLLs being detached */


/***LD  ldrSysTbl - count of DLLs being detached from a process
 *      Names of DLLs that are considered as system to speed up loading of
 *      a new task.  ldrFreeMTE must free the mte pointer in the table
 *      if the DLL being freed is listed in the table.
 */

extern SysTblEnt_t  ldrSysTbl[];


/*
 *  Beginning of swappable code
 */

#pragma code_seg("SWAP32_13","CODE")




/***LP  ldrIncDLDRecord - Create/Update a runtime attachment to a module.
 *
 *      ldrIncDLDRecord scans the ldr demand load data list for the process
 *      searching for an existing runtime attachment to this module.
 *
 *      If found, the reference count for that module is incremented.
 *      If not found, a new ldrdld record will be created and attached
 *      to this process.
 *
 *      An error will be returned if the reference count overflows,
 *      or the kernel heap cannot allocate the memory.
 *
 *      If pptda == NULL, the kernel demand load data list will be
 *      modified.
 *
 *      ENTRY   hmte                    - handle to mte to attach
 *              pptda                   - pointer to PTDA to attach to
 *
 *      EXIT    ULONG
 *              NO_ERROR
 *                      Attachment was successful
 *              ERROR_TOO_MANY_MODULES
 *                      Not enough memory exists for the attachment
 *
 */

ULONG ldrIncDLDRecord( VMHOB hmte,
                       PPTDA pptda )
{
    register ldrdld_t *pldrdld;
    ldrdld_t          *ptemp;

    if (pptda)
        pldrdld = pptda->ptda_pldrdldHead;
    else
        pldrdld = pldrdldHeadKernel;

    // Find the ldrdld record
    while (pldrdld != NULL)
    {
        if (pldrdld-> ldrdld_hmte == hmte)
            break;
        pldrdld = pldrdld-> ldrdld_pldrdldNext;
    }

    // If no record was found, allocate and initialize a
    // new one, and link it into the head of the list.
    if (!pldrdld)
    {
        int rc;

        if ( rc = VMAllocKHB( VM_HKH_PUB_RESRW,
                              sizeof(ldrdld_t),
                              (VMHOB) LDRDLDOWNER,
                              NA,
                              SSToDS(&ptemp) ) )
        {

#ifdef LDRSTRICT
            panic("ldrIncDLDRecord");
#endif

            return (rc);
        }

        pldrdld = ptemp;
        pldrdld->ldrdld_hmte = hmte;
        pldrdld->ldrdld_cRef = 0;    // useage count incremented below

        // place the newly added module at the front of the chain
        if (pptda)
        {
            pldrdld->ldrdld_pldrdldNext = pptda->ptda_pldrdldHead;
            pptda->ptda_pldrdldHead = pldrdld;
        }
        else
        {
            pldrdld->ldrdld_pldrdldNext = pldrdldHeadKernel;
            pldrdldHeadKernel = pldrdld;
        }
    }

    // Increment the runtime load reference count.
    // If it overflows, restore it and return an error.
    if (++pldrdld->ldrdld_cRef == 0)
    {
        --pldrdld->ldrdld_cRef;
        return(ERROR_TOO_MANY_MODULES);
    }

    return (NO_ERROR);
}


#pragma code_seg()
#pragma code_seg("SWAP32_4","CODE")


/***LP  ldrClearGraph - Reset module graph.
 *
 *      ldrClearGraph clears the USED and INGRAPH bits for each
 *      module in the module graph.
 *
 *      ENTRY   NONE
 *      EXIT    NONE
 *
 * WARNING !! This functionality is duplicated in ldrSetLoaded for performance.
 *            Do NOT change ClearGraph without also changing SetLoaded.
 *
 */

void ldrClearGraph()
{
    register ldrmte_t *pmte;

    for ( pmte = mte_h; pmte != NULL; pmte = pmte->mte_link )
    {
        pmte->mte_flags1 &= ~(USED|INGRAPH);
    }
}



#pragma code_seg()
#pragma code_seg("SWAP32_27","CODE")


/***EP  LDRFreeTask - free task's memory, LDT and referenced MTEs
 *
 *      Frees a task's LDT and all memory referenced through it.
 *
 *      If the program module handle kept in the process's PTDA is
 *      non-zero, the module's use count will be decremented.  If
 *      the module use count decrements to zero, garbage collection
 *      is invoked.
 *
 *      Note: ldrDoGCollect could be set to TRUE on entry to this routine.
 *
 *      ENTRY   pptda                   - a pointer to a PTDA
 *
 *      EXIT    NONE
 */

void LDRFreeTask( register PPTDA pptda )
{
    VMHOB   hobmte;

    // Special case the end of SysInit
    if (DDInitTime)
    {
        pptda->ptda_module = 0;
        // hobmte = 0;
    }

    //  Free up user memory from task and delete the LDT.
    VMFreeTask(pptda);

    // Get and remove ptda_module from the specified PTDA.
    hobmte = pptda->ptda_module;
    pptda->ptda_module = 0;
    pptda->ptda_pszproc = 0;  /* prevent access to proc name */      /* 79048 */


    // Decrement the use count for the ptda's module.
    // If it goes to zero, we need to call ldrGarbageCollect.
    ldrDoGCollect |=    (   (hobmte)
                     && ( --( ldrpMTEFromHandle(hobmte)->mte_usecnt) ) == 0 );

    // ldrBlastDLDRecord detaches the runtime links from this PTDA.
    // If any were detached, we will call ldrGarbageCollect.
    ldrBlastDLDRecord( pptda );

    if (ldrDoGCollect)
        ldrGarbageCollect();
}




/***LP  ldrBlastDLDRecord - Free all runtime references to MTEs for a PTDA
 *
 *      ldrBlastDLDRecord is used during process termination to eliminate
 *      the entire ldrdld list.
 *
 *      ldrBlastDLDRecord sets the ldrDoGCollect flag if any modules
 *      were detached from the demand load data list.
 *
 *      ldrBlastDLDRecord does not support the kernel demand load data list.
 *
 *      ENTRY   pptda                   - pointer to PTDA to detach from
 *
 *      EXIT    NONE
 *
 */

void ldrBlastDLDRecord( PPTDA pptda )
{
    register ldrdld_t *pldrdldNext;

    // nothing to do - just return
    if ( (pldrdldNext = pptda->ptda_pldrdldHead) == NULL )
        return;

    // Detach the whole chain from the ptda.
    pptda->ptda_pldrdldHead = NULL;

    // Free the memory used by the chain.
    while (pldrdldNext)
    {
       register ldrdld_t *pldrdldFree;

       pldrdldFree = pldrdldNext;
       pldrdldNext = pldrdldNext->ldrdld_pldrdldNext;

       VMFreeKHB( VM_HKH_PUB_RESRW, pldrdldFree );
    }

    ldrDoGCollect = 1;

}


// end of code that used to be resident


#pragma  code_seg()
#pragma code_seg("LDRSWAP_TEXT", "CODE")


/***EP  LDRIsMTEDetaching - Return error if an MTE is being detached
 *
 *      LDRIsMTEDetaching returns ERROR_INVALID_ACCESS if the USED
 *      bit is set and INGRAPH bit is clear for an MTE which does
 *      not request DLL termination notification.  This is an
 *      exported function used for Exitlist verification.
 *
 *      LDRIsMTEDetaching does not modify the MTE.
 *
 *      ENTRY   hmte                    MTE handle
 *
 *      EXIT    ULONG
 *              NO_ERROR                MTE is not detaching or gets
 *                                      termination notification.
 *              ERROR_INVALID_ACCESS    MTE is detaching and does not
 *                                      get termination notification.
 */

ULONG LDRIsMTEDetaching( VMHOB hmte )
{
    register ldrmte_t *pmte;

    pmte = ldrpMTEFromHandle( hmte );

    if ( (pmte->mte_flags1 & (USED|INGRAPH)) == USED )
        return(ERROR_INVALID_ACCESS);

    return(NO_ERROR);
}



/***LP  ldrGarbageCollect - Clean up unused modules
 *
 *      ldrGarbageCollect cleans up any unused modules in the system.
 *      It does this by first marking all the modules which are
 *      referenced anywhere in the system as USED, and then freeing
 *      all modules which are not USED.
 *
 *      If the loader semaphore is owned when ldrGarbageCollect is
 *      called, ldrGarbageCollect will set the ldrDoGCollect flag,
 *      and exit.  ldrDoGCollect is checked at process termination
 *      and during LDRClearSem, so that ldrGarbageCollect will be
 *      called again later.
 *
 *      ENTRY   NONE
 *      EXIT    NONE
 *
 *      EFFECTS :
 *              Frees modules from the module graph
 *              Clears the USED and INGRAPH bits of remaining modules
 *              ldrDoGCollect flag set only if successful.
 *
 */

void ldrGarbageCollect()
{
    ldrmte_t          *pmteFreeMe;
    register ldrmte_t *pmteNext;
    register struct nonres_s *pnr;       //137076
    register struct nonres_s *pnrprev;   //137076

    if (LDRGetSem())
        return;

#ifdef LDRSTRICT
    // Mark all modules used by every process in the system as INGRAPH.
    if ( TKScanTasks( TK_SF_DYING | TK_TG_ALL,
                      0,
                      ldrMarkTaskInGraph,
                      NULL ) )
        panic("ldrGCollect: TKScanTasks");
#else
    // Mark all modules used by every process in the system as INGRAPH.
    TKScanTasks( TK_SF_DYING | TK_TG_ALL,
                 0,
                 ldrMarkTaskInGraph,
                 NULL );
#endif

    // Mark all modules used by the kernel.
    ldrMarkTaskInGraph( NULL, NULL );

    // Now find the set of USED modules from the set of INGRAPH modules.
    ldrFindGraph(TRUE);

    TKTCYield();

    /*
     * If the MTE is not marked USED at this point, we must free
     * the module's allocated memory for segments or objects.
     * Remove the module from the linked list of MTEs, and free
     * the memory used for the MTE.
     *
     * We never free the DOSCALLS mte (DOSMOD).
     * We never free the VDM mte (MVDMMOD).
     * We never free DDs (DEVDRVMOD) - DDs are freed by ldrFreeModule.
     */

    for ( pmteNext = mte_h; pmteNext != NULL; )
    {

// #74177  Virginia - CHANGE TEAM
/*      if ( ( pmteNext->mte_flags1 & (USED|DOSMOD|MVDMMOD|VDDMOD|DEVDRVMOD|FSDMOD|FSHMOD) ) ||   75809 */
/*           ( pmteNext->mte_flags2 & MTEDLLONEXTLST ) )                                          75809 */
        if (pmteNext->mte_flags1 & (USED|DOSMOD|MVDMMOD|VDDMOD|DEVDRVMOD|FSDMOD|FSHMOD) )
        {
            // Reset USED and INGRAPH bits for referenced MTEs.
            pmteNext->mte_flags1 &= ~(USED|INGRAPH);
            pmteNext = pmteNext->mte_link;
        }
        else
        {
            // This MTE is neither USED, DOSMOD nor MVDMMOD.  Free it.

//137076 Start ***********************************************************
            KSEMRequestMutex( hNonResNamTbl, -1 );

            //  If no one using non-resident name tables, then walk
            //  the list and free the one for our module.

            pnr = pNonResNamTbl;
            pnrprev = pnr;
            while ( pnr )
            {
                if (pnr->nr_mte == pmteNext->mte_handle)
                {
                    if (pnr == pnrprev)                  // first time through
                    {
                        pNonResNamTbl = pnr->nr_link;   // reset head pointer
                    }
                    else
                    {
                        pnrprev->nr_link = pnr->nr_link;// reset link pointer
                    }
                    VMFreeKHB( VM_HKH_PUB_SWAPRW, pnr );
                    break;
                }
                pnrprev = pnr;
                pnr = pnr->nr_link;

            }

            KSEMReleaseMutex( hNonResNamTbl );
//137076 End **************************************************************
            pmteFreeMe = pmteNext;
            pmteNext = pmteNext->mte_link;
            ldrUnlinkMTE(pmteFreeMe);
            ldrFreeMTE(pmteFreeMe);

        }

    }  // end of for every MTE in the system

    ldrDoGCollect = 0;
    KSEMReleaseMutex( (HKSEM)&LdrSem );

    return;
}




/***LP  ldrLibDetach - Detach a module subgraph from the current process.
 *
 *      ldrLibDetach removes all the segments belonging to any module
 *      marked with the USED bit but not with the INGRAPH bit, and
 *      then will garbage collect those modules, if any.
 *
 *      When ldrLibDetach is complete, the USED and INGRAPH bits
 *      will be clear for all modules.
 *
 *      The caller should call ldrFindDetach to find the modules which
 *      are to be detached prior to calling ldrLibDetach.
 *
 *      ENTRY   USED bit set and INGRAPH bit clear for all modules
 *              to be detached.
 *
 *      EXIT    int             - error code (NO_ERROR if successful)
 */

void ldrLibDetach()
{
    // For all modules which have the USED bit set and the INGRAPH
    // bit clear, (the detach candidates) call ldrLibDetachSub to
    // detach memory associated with that module.
    // Also, leave with all the USED and INGRAPH bits clear.
    ldrRunGraph( TRUE, USED, ldrLibDetachSub );

    // We might not have found anything to free up. This can happen for
    // the case where we freemodule a DLL that we had also imported. In
    // this case ldrcDLLDetach==0 and ldrDoGCollect==FALSE. In this case
    // we just return, all finished.

    if (ldrDoGCollect)
        ldrGarbageCollect();
}




/***LP  ldrLibDetachSub - free segment from a previously loaded library module
 *
 *      This is a worker routine which frees the segment in the given
 *      module from the LDT for the current (or child) process.
 *
 *      If the module being freed was actually a loadable device driver
 *      or has had some shared segments explicitly freed, then the segment
 *      handles will have been zeroed and the selector will have been freed.
 *      We cannot zero the selector field because that would prevent
 *      garbage_collect from calling LDTFreeShared.
 *
 *      ENTRY   pmte                    - pointer to mte
 *
 *      EXIT    NO_ERROR
 *
 *      Pseudocode :
 *
 *      for (each object in *pmte) {
 *              if ((object handle nonzero) and
 *                  ((not instance data) or
 *                   (segment owner == current (or child) ptda handle) {
 *                      object info = VMObjectInfo (handle);
 *                      VMFree (object info);
 *              }
 *      }
 *
 *      ldrEachObjEntry(0, pmte, ldrFreeLDTGate, 0);
 *      return (NO_ERROR)
 */

ulong_t ldrLibDetachSub( ldrmte_t *pmte )
{
    register ULONG     i;
    register ldrote_t *pote;
    register ldrste_t *pste;
    ldrsmte_t         *psmte;
    VMHOB              hob;
    ULONG              laddr;

    // Check that there is something to detach.
    ldrAssert( ldrcDLLDetach );

    // Decrement count, even if it is the kernel. Note that if the kernel is
    // on the free list, we must decrement the count so as not to mess up the
    // math, but we will not actually free it.
    ldrcDLLDetach--;

    // Do not free the DLL known as DOSCALLS.
    // Changed MTE structure, mte_modname is now 8 bytes, not length
    // prefixed string
    if ( !strncmp( "DOSCALLS", (char *)&pmte->mte_modname, 8 ) )
        return( NO_ERROR );

    psmte = pmte->mte_swapmte;

    // For DosDebug, note the unload event
    TKLibiRecordUnload( pmte->mte_handle );

    for ( i = 0; i < psmte->smte_objcnt; i++ )
    {

        if (ldrIsNE(pmte))
            pste = &((ldrste_t *)(psmte->smte_objtab))[i];
        else
            pote = &((ldrote_t *)(psmte->smte_objtab))[i];

        /*
         * For device drivers or FSDs do not free any objects allocated
         * with GDT selectors, it will done in ldrFreeMTE.
         */
        if (pmte->mte_flags1 & (DEVDRVMOD | FSDMOD))
            return( NO_ERROR );

        /*
         * Get linear address of the object to be freed
         */
        if (ldrIsNE(pmte))
        {
            if ( (laddr = SELVirtToLin( 0, pste->ste_selector ) ) == -1 )
                continue;
        }
        else
        {
            if ( (laddr = pote->ote_base) == 0)
                continue;
        }

        /*
         * We do not use the object handle in the OTE/STE because if it
         * is an instance data object and a cache is present, then the
         * handle is for the cache. Moreover if there is no cache (which
         * is the common case) for the instance data object, then it does
         * not have a handle anyway. We try to get a handle to the object
         * by calling VMGetHandle. If it fails, then we do not have an
         * attachment to that object, so we skip. This will happen in the
         * loader error path
         */
        if ( VMGetHandle( laddr,
                          pPTDACur->ptda_handle,
                          SSToDS(&hob) ) != NO_ERROR )
            continue;

        /*
         * At this point we are detaching this DLL from this process.
         * We need to free a resource that may have it reference count
         * greater than 1, also check to see if the resource is being
         * used by anyone else.  If not zero the handle field.
         * Call VM with a flag to indicate that the interfaces LDRIsResource
         * and LDRFreeObject doesn't have to be called to free the object
         * from this context.
         */
        if ( ldrCheckForResource( i+1,
                                  pmte,
                                  pPTDACur->ptda_rsrclist,
                                  pPTDACur,
                                  VMFM_TASK_TERM ) == NO_ERROR )
        {
            if (ldrRsrcFindGlobal( i+1,
                                   pmte,
                                   pPTDACur->ptda_rsrclist ) == NO_ERROR )
            {
                if (ldrIsNE(pmte))
                {
                    pste->ste_selector = 0;
                    pste->ste_seghdl = 0;
                }
                else
                {
                    pote->ote_base = 0L;
                    pote->ote_handle = 0;
                    pote->ote_selector = 0;
                    pote->ote_flags &= ~OBJALLOC;
                }
            }

#ifdef LDRSTRICT
            ldrAssert( !(VMFreeMem( laddr, pPTDACur->ptda_handle, VMFM_NOLDR )));
#else
            VMFreeMem( laddr, pPTDACur->ptda_handle, VMFM_NOLDR );
#endif

        }
        else
        {

// Taken out for 2.1 GA ship per Glenn. Being hit intermittently with some
// apps. No time to debug, but must not panic as the debug kernel does get
// shipped. Paul.
#ifdef LDRSTRICTxxxx
            ldrAssert( !(VMFreeMem( laddr, pPTDACur->ptda_handle, NA )));
#else
            VMFreeMem( laddr, pPTDACur->ptda_handle, NA );
#endif

        }
    }

    /*
     * Free the LDT callgates referenced by this module.
     */
    ldrEachObjEntry( 0, pmte, ldrFreeLDTGate, 0 );

    ldrDoGCollect = 1;

    return( NO_ERROR );
}




/***LP  ldrFreeMTE - Free module table entry.
 *
 *      Remove the module table entry from the linked list of entries
 *      and free its memory.
 *
 *      ENTRY   pmte                    - pointer to mte to free
 *
 *      EXIT    NONE
 *
 */

void ldrFreeMTE( register ldrmte_t *pmte )
{
    register ulong_t    i;
    register ldrsmte_t *psmte;         // pointer to swappable mte
    ULONG               laddr;
    uchar_t            *pbcache;       // Pointer to cache
    VMKHIB              khib;
    VMOB               *pob_packseg;
    VMHOB               hdl_packseg = HOBNULL;

#ifdef PMDEBUG
    DebugUnLinkSymMTE( pmte, pmte->mte_handle );
#endif

    if (pmte->mte_flags1 & FSHMOD)
        return;

    psmte = pmte->mte_swapmte;

    // close the file if it is not already closed
    if ( SFN_NULL != (short)pmte->mte_sfn )
    {
        ldrClose( pmte->mte_sfn );
        pmte->mte_sfn = SFN_NULL;
    }

#if RAS
    /* Clean up the Dynamic Trace Object, if any. */
    if (fDYNActive)
        DYNDetach (pmte->mte_handle);
#endif

    // free callgates
#ifdef LDRSTRICT
    ldrAssert( !( ldrEachObjEntry( 0, pmte, ldrFreeCallGate, 0 ) ) );
#else
    ldrEachObjEntry( 0, pmte, ldrFreeCallGate, 0 );
#endif

    // Free ID Cache and address space for all objects

    if (ldrIsNE(pmte))
    {
        for ( i = 0; i < psmte->smte_objcnt; i++ )
        {
            register ldrste_t *pste;

            pste = &((ldrste_t *)psmte->smte_objtab)[i];

            /*
             * If the segment is packed setup the hdl_packseg field.
             * This is freed as one object later.
             */
            if (pste->ste_flags & STE_PACKED)
            {

#ifdef LDRSTRICT
                if(    (hdl_packseg != HOBNULL)
                    && (hdl_packseg != pste->ste_seghdl) )
                    panic("ldrFreeMTE: Packed seg handles");
#endif
                hdl_packseg = pste->ste_seghdl;

            }

            // If fixup data has been saved for this segment, free
            // the heap object used to store them.
            if (pste->ste_fixups)
            {
                VMFreeKHB( VM_HKH_PUB_SWAPRW, (char *)pste->ste_fixups );
            }

            // If Device Driver free GDT selector segments from resident
            // heap.
            if (    pmte->mte_flags1 & DEVDRVMOD
                 && pste->ste_flags & STE_GDTSEG
                 && pste->ste_selector != 0 )
            {
                SELFreeKHB((VMHKH) ((pste->ste_flags & STE_DATA) ?
                           ((i < 2) ? VM_HKRH_PUBRW : VM_HKSH_PUBRW) :
                           ((i < 2) ? VM_HKRH_PUBRO : VM_HKSH_PUBRO)),
                           ((i < 2) ? NA : SEL_FREE_KHB_OVERRIDE),
                           pste->ste_selector);
            }

            // If a FSD, then free kernel heap object or GDT selector
            // as appropriate (FSDs are allocated from the kernel heap,
            // the mini-FSD is not).
            else if (     (pmte->mte_flags1 & FSDMOD)
                       && pste->ste_selector != 0 )
            {
                laddr = SELVirtToLin( 0, pste->ste_selector );

                if ( VMGetInfoKHB( (char *)laddr, SSToDS(&khib) ) == NO_ERROR )
                {
                    SELFreeKHB( khib.khib_hkh, NA, pste->ste_selector);
                }
                else
                {
                    VMFreeMem( laddr, HOBNULL, VMFM_GC );
                }
            }

            else
            {
                // If a instance segment free ID cache
                if (     (pste->ste_seghdl != HOBNULL)
                      && (pste->ste_selector & SEL_LDTBIT)
                      && ((pste->ste_flags & STE_SHARED) == 0)  )
                {
                    // If cached instance data object
                    ldrAssert( pste->ste_offset != 0 );
                    if ( VMHandleVerify( pste->ste_seghdl,
                                         1,
                                         SSToDS(&pbcache) ) == NO_ERROR )
                    {
                        // Free the pseudohandle
                        VMFreePseudoHandle( pste->ste_seghdl );

                        // Free small caches from heap
                        VMFreeKHB( VM_HKH_PUB_SWAPRW, pbcache );
                    }
                    else    // else cache has own object
                    {
                        if ( VMGetLaddr( pste->ste_seghdl,
                                         SSToDS(&laddr) ) )
                        {

#ifdef LDRSTRICT
                            panic("LDRFreeMTE : VMGetLaddr");
#endif

                        }
                        else
                        {
                            VMFreeMem( laddr, HOBNULL, 0 );
                        }
                    }
                }

                // If a library module free reserved address space
                if (     (pmte->mte_flags1 & LIBRARYMOD)
                      && (pste->ste_selector != 0)  )
                {
                    VMFreeMem( SelToLaTiled(pste->ste_selector),
                               HOBNULL,
                              VMFM_NOLDR | VMFM_GC );
                }
            }
        }
    }  // end of 16-bit module

    else  // 32-bit module
    {
        for ( i = 0; i < psmte->smte_objcnt; i++ )
        {
            register ldrote_t *pote;

            pote = &((ldrote_t *)psmte->smte_objtab)[i];

            // Handle VDDMOD objects first.
            if (    (pmte->mte_flags1 & VDDMOD)
                 && (pote->ote_flags & OBJALLOC)  )
            {
                VDMMDeassignAddress( (char *)pote->ote_vddaddr,
                                     pote->ote_flags );
            }

            else
            {
                // check for instance objects and free
                if (     (pote->ote_handle != HOBNULL)
                     && !(pote->ote_flags & OBJSHARED)
                     && !(pote->ote_flags & OBJEXEC)
                     &&  (pote->ote_flags & OBJWRITE)   )
                {
                    if ( !( VMGetLaddr( pote->ote_handle, SSToDS(&laddr) ) ))
                        VMFreeMem( laddr, HOBNULL, 0 );
                }

                // free non-instance objects that are shared
                if (    (pmte->mte_flags1 & LIBRARYMOD)
                     && (pote->ote_flags & OBJALLOC) )
                {
                    VMFreeMem( pote->ote_base, HOBNULL, VMFM_NOLDR | VMFM_GC );
                    pote->ote_flags &= ~OBJALLOC;
                }
            }

        }
    }  // end of 32-bit module

    /*
     * If a packed segment object exists and is valid free it.
     */
    if(hdl_packseg != HOBNULL)
    {
       if(    (VMHandleVerify( hdl_packseg,
                               NULL,
                               SSToDS(&pob_packseg) ) == NO_ERROR )
            && (pob_packseg->ob_hobmte == pmte->mte_handle) )
       {
            VMGetLaddr( hdl_packseg, SSToDS(&laddr) );
#ifdef LDRSTRICT
            ldrAssert( !(VMFreeMem( laddr, (TKContext())->ptda_handle, 0 ) ));
#else
            VMFreeMem( laddr, (TKContext())->ptda_handle, 0 );
#endif
       }
    }

    // free the MTE's PseudoHandle
    VMFreePseudoHandle( pmte->mte_handle );

    if (pmte->mte_swapmte->smte_pfilecache)
    {
#ifdef LDRSTRICT
        ldrAssert( !(VMFreeMem( pmte->mte_swapmte->smte_pfilecache, 0, 0 )));
#else
        VMFreeMem( pmte->mte_swapmte->smte_pfilecache, 0, 0 );
#endif
    }

    // If freeing MTE for module that is in ldrSysTbl, we must
    // update the entries in ldrSysTbl

    //Skip for-loop if MTESYSTEMDLL bit is not set
    if (pmte->mte_flags2 & MTESYSTEMDLL)
    {
        for ( i = 0; i < NUMSYSTBLENT; i++)
        {
             if (ldrSysTbl[i].pMTE == pmte )
             {
                 ldrSysTbl[i].pMTE = NULL;

             }
        }
    }

    // free the swappable memory for the MTE
    VMFreeKHB( VM_HKH_PUB_SWAPRW, pmte->mte_swapmte );

    // free the resident memory for the MTE
    VMFreeKHB( VM_HKH_PUB_RESRW, pmte );

    return;
}


#pragma code_seg()
#pragma code_seg("SWAP32_13","CODE")


/***LP  ldrWasLoadModuled - See if module is runtime attached to the process
 *
 *      ldrWasLoadModuled determines whether the module is runtime-attached
 *      to the process. An error will be returned if the module was only
 *      STATICLY attached.
 *
 *      If pptda == NULL, the kernel demand load data list will be
 *      examined.
 *
 *      ENTRY   hmte                    - handle to mte to look for
 *              pptda                   - pointer to PTDA to search
 *              pcref                   - pointer to place to return ref count
 *
 *      EXIT    ULONG
 *              NO_ERROR
 *                      pptda has a runtime attachment to hmte,
 *                      or hmte references DOSCALLS
 *
 *              ERROR_INVALID_HANDLE
 *                      pptda has no runtime attachment to hmte
 *
 */

ULONG ldrWasLoadModuled( VMHOB     hmte,
                         ptda_t   *pptda,
                         ulong_t  *pcref )
{
    register ldrdld_t *pldrdld;

    if (hmte == DosModHdl)
        hmte = hMteDoscall1;

    if (pptda)
        pldrdld = pptda->ptda_pldrdldHead;
    else
        pldrdld = pldrdldHeadKernel;

    // Find the ldrdld record
    while (pldrdld)
    {
        if (pldrdld->ldrdld_hmte == hmte)
        {
            // Module already being freed
            if (pldrdld->ldrdld_cRef == 0)
                break;

            // Return count if caller wants it
            if (pcref)
                *pcref = pldrdld->ldrdld_cRef;

            return(NO_ERROR);
        }
        pldrdld = pldrdld->ldrdld_pldrdldNext;
    }

    return( ERROR_INVALID_HANDLE );
}

#pragma code_seg()
#pragma code_seg("SWAP32_27", "CODE")

/***LP  ldrDecDLDRecord - Destroy one runtime attachment to a module
 *
 *      ldrDecDLDRecord decrements the count of runtime links to a
 *      module from a process.  If it goes to zero, the ldrdld_t
 *      record is to be freed.
 *
 *      If the record does not exist, 0 will be returned.
 *
 *      The number of remaining attachments (after the detach
 *      transaction) are returned to the caller.
 *
 *      If pptda == NULL, the kernel demand load data list will be
 *      traversed.
 *
 *      ENTRY   hmte                    - handle to mte to detach
 *              pptda                   - pointer to PTDA to detach from
 *              funlink                 - unlink if initial ref count <= 1
 *
 *      EXIT    ULONG
 *              Returns the number of remaining runtime attachments
 *              to the module from the PTDA.
 *
 */

ULONG ldrDecDLDRecord( VMHOB   hmte,
                       PPTDA   pptda,
                       int     funlink )
{
    register ldrdld_t  *pldrdldCur;
    register ldrdld_t **ppldrdld;

    if (pptda)
        ppldrdld = &pptda->ptda_pldrdldHead;
    else
        ppldrdld = &pldrdldHeadKernel;

    // Find the ldrdld record from pptda and hmte
    pldrdldCur = *ppldrdld;
    while (    pldrdldCur != NULL
            && pldrdldCur->ldrdld_hmte != hmte )
    {
        ppldrdld = &pldrdldCur->ldrdld_pldrdldNext;
        pldrdldCur = *ppldrdld;
    }

    if (pldrdldCur == NULL)
        return(0);           // note - this is not a return code

    ldrAssert( pldrdldCur->ldrdld_cRef != 0 || funlink );

    // Decrement the reference count, and free the list
    // element if the reference count is zero or goes to zero
    // and the unlink flag is set.
    if ( pldrdldCur->ldrdld_cRef-- <= 1 && funlink )
    {
        *ppldrdld = pldrdldCur->ldrdld_pldrdldNext;
        VMFreeKHB( VM_HKH_PUB_RESRW, pldrdldCur );
        return(NO_ERROR);
    }

    return( (ULONG)pldrdldCur->ldrdld_cRef );
}



#pragma code_seg()
#pragma code_seg("LDRSWAP_TEXT", "CODE")

/***LP  ldrFindDetach - Find all modules to detach as if an mte is detached.
 *
 *      Locate the set of module which are to be detached if hmte
 *      was to become detached from the process.  Note that the
 *      module has not yet been actually detached from the process.
 *
 *      If the module is still attached to the process, and if the
 *      reference count to that module from this process is MORE THAN 1,
 *      ldrFindDetach will mark that module as still USED.
 *
 *      ENTRY   hmte        - handle to mte to detach
 *              pptda       - pointer to PTDA to detach
 *              flExitList  - TRUE if called from exit list with dummy ptda
 *
 *      EXIT    NO_ERROR
 *
 */

ULONG ldrFindDetach( VMHOB   hmte,
                     PPTDA   pptda,
                     ULONG   flExitList )
{
    register ldrdld_t *pldrdld;
    ldrmte_t          *pmte;
    VMHOB              SavHMTE;

#ifdef LDRSTRICT
    // do not go into the code below if we have a bad mte handle
    if ( !(pmte = ldrValidateMteHandle(hmte)) )
        return( ERROR_INVALID_HANDLE );
#else
    pmte = ldrpMTEFromHandle(hmte);
#endif

    // Initialise GLOBAL counters - used by LDRDLLExitList and ldrFreeModule
    cDLLTerm = ldrcDLLDetach = 0;

    if (DDInitTime)
    {
        SavHMTE = pptda->ptda_module;
        pptda->ptda_module = 0;
    }

    // Mark all modules used by the parent module, and the child
    // module, but do NOT mark hmte unless it is referenced more
    // than once from this process.
    ldrMarkInGraph( pptda->ptda_module );

    pldrdld = pptda->ptda_pldrdldHead;
    while (pldrdld)
    {
        if (pldrdld->ldrdld_hmte != hmte)
        {
            ldrMarkInGraph( pldrdld->ldrdld_hmte );
        }
        else
        {
            if ( pldrdld->ldrdld_cRef > 1 )
                ldrMarkInGraph( pldrdld->ldrdld_hmte );
        }

        pldrdld = pldrdld->ldrdld_pldrdldNext;
    }

    if ( pptda->pPTDAExecChild )
    {
        ldrMarkInGraph( pptda->pPTDAExecChild->ptda_module );
    }

    /*
     * If the parent module is HOBNULL we have been called from
     * ldrDLLExitList. Traverse the graph, finding all USED modules,
     * marking them with the USED bit set upon exit, done so DLL
     * termination is called for all attached DLL's; otherwise,
     * Traverse the graph, finding all USED modules, marking them
     * with both USED and INGRAPH bits set upon exit.
     */
    if (pPTDACur->ptda_module == HOBNULL)
    {
       // ldrFindGraph(FALSE);
       if (flExitList)
           ldrFindGraph(FALSE);
       else
           ldrFindGraph(TRUE);
    }
    else
    {
       ldrFindGraph(TRUE);
    }

    /* Traverse the graph, finding all modules which are used by hmte,
     * but which are NOT also used by some other active module in the
     * process.  These are marked USED, but not INGRAPH.
     */

    ldrMarkInGraph(hmte);
    ldrFindGraph(FALSE);

    if (DDInitTime)
    {
        pptda->ptda_module = SavHMTE;
    }

    return( NO_ERROR );
}


#pragma code_seg()
#pragma code_seg("SWAP32_5","CODE")


/***LP  ldrMarkTaskInGraph - Mark a task's in-use modules as roots.
 *
 *      ldrMarkTaskInGraph marks any modules in use by the requested
 *      task as INGRAPH, and NOT USED.  This is used as a prelude to
 *      calling ldrFindGraph to find the graph of modules used by
 *      this process.   When called once for each process in the
 *      system, followed by a single call to ldrFindGraph, all
 *      referenced modules in the system will be marked.
 *
 *      If pptda == NULL, the kernel demand load data list will be
 *      traversed.
 *
 *      This is sometimes used as a TKScanTasks worker, hence pdummy.
 *
 *      ENTRY   pptda                   - pointer to PTDA to mark
 *              pdummy                  - unused pointer (TKScanTasks)
 *
 *      EXIT    NO_ERROR
 *
 */

ULONG ldrMarkTaskInGraph( PPTDA pptda,
                          void *pdummy )
{
    register ldrdld_t *pldrdld;

    /* Mark all modules used by the parent module, it's
     * runtime attachments, and the child module.
     */

    if (pptda)
    {
        ldrMarkInGraph( pptda->ptda_module );
        if ( pptda->pPTDAExecChild )
            ldrMarkInGraph( pptda->pPTDAExecChild->ptda_module );
        pldrdld = pptda->ptda_pldrdldHead;
    }
    else
    {
        pldrdld = pldrdldHeadKernel;
    }

    while ( pldrdld )
    {
        ldrMarkInGraph( pldrdld->ldrdld_hmte );
        pldrdld = pldrdld->ldrdld_pldrdldNext;
    }
    return (NO_ERROR);

    pdummy;
}




/***LP  ldrMarkInGraph - Mark a module as INGRAPH, if not already USED
 *
 *      ldrMarkInGraph sets the INGRAPH bit for a module, if it
 *      is not already marked as USED.  Note that if a module
 *      is marked as USED, INGRAPH bit can never be modified.
 *      This is because INGRAPH is used to distinguish between
 *      discard-candidate modules (USED, but not INGRAPH), and
 *      regular USED modules (USED and INGRAPH).
 *
 *      ldrMarkInGraph does nothing if hmte == HOBNULL, because it
 *      is often called with null handles.
 *
 *      ENTRY   hmte                    - handle to MTE to mark
 *
 *      EXIT    NONE
 *
 */

void ldrMarkInGraph( VMHOB hmte )
{
    if (hmte)    // if called with null module handle - just return
    {
        register ldrmte_t *pmte;

        pmte = ldrpMTEFromHandle( hmte );

        if ( !(pmte->mte_flags1 & USED) )
            pmte->mte_flags1 |= INGRAPH;
    }
}




/***LP  ldrFindGraph - Scan module graph updating INGRAPH and USED bits.
 *
 *      ldrFindGraph sets the INGRAPH and USED bits to locate Free,
 *      Referenced, and to-be-detached modules.
 *
 *      The bits mean the following :
 *
 *      INGRAPH USED
 *      0       0       Normal Clear state.  Module not referenced.
 *      1       0       Traversal Completion Required.  (Intermediate state).
 *      0       1       Module is about to be Detached.
 *      1       1       Module is Referenced.
 *
 *      When the loader semaphore is clear, both INGRAPH and USED
 *      must be clear for all modules in the system.
 *
 *      To find a set of referenced modules in the graph, do the following:
 *
 *      For all root modules of interest:
 *              ldrMarkInGraph (hmte)
 *
 *      ldrFindGraph (TRUE)
 *
 *      ldrFindGraph will traverse the graph, and set both the USED
 *      and INGRAPH bits for the marked root modules, and all modules
 *      to which those modules have references.
 *
 *      ldrFindGraph will not continue the traversal downward if the
 *      USED bit is already set.
 *
 *      To find a second disjoint set of modules (ones which are
 *      referenced by one set of modules, but not also referenced
 *      by a another set of modules) do the following :
 *
 *      Use ldrFindGraph (TRUE) as above to mark the first set of
 *      modules (the excluding set).  Then, call ldrMarkInGraph
 *      again (as above) for all root modules of the new disjoint
 *      set (usually, just the one module to be detached).  Then,
 *      call ldrFindGraph (FALSE).  This second call will mark a
 *      new set of modules with the USED bit set, but with the
 *      INGRAPH bit clear.  This set of modules is the set to be detached.
 *
 *      When called to clear the INGRAPH bit, ldrFindGraph will count
 *      the number of modules it finds that require DLL termination.
 *      This count is stored in the global variable cDLLTerm.  It will
 *      also count the total number of DLLs being detached from the
 *      process.  It stores this value in ldrcDLLDetach.
 *
 *      ENTRY   fAttached
 *                      TRUE  : Set INGRAPH for USED modules
 *                      FALSE : Clear INGRAPH for USED modules
 *
 *      EXIT    NONE
 *
 *      Pseudocode :
 *
 *      repeat
 *          DoAgain = FALSE
 *          for (all MTEs in the system)
 *              if (INGRAPH, but NOT USED)
 *                  set USED bit
 *                  if (not fAttached)
 *                      clear INGRAPH bit
 *                      increment ldrcDLLDetach
 *                      if (DLL requests termination notification)
 *                          increment cDLLTerm
 *                  for each referenced module
 *                      if the (USED and INGRAPH bits are both clear)
 *                          Set the InGraph bit
 *                          Set the 'DoAgain' flag to TRUE
 *      } while (DoAgain)
 *
 */

void ldrFindGraph( ULONG fAttached )
{
    ULONG              fDoAgain;
    register ULONG     i;
    register ldrmte_t *pmte;
    register ldrmte_t *pmteDown;

    do
    {
        fDoAgain = FALSE;
        for ( pmte = mte_h; pmte != NULL; pmte = pmte->mte_link )
        {
            if ( (pmte->mte_flags1 & (USED|INGRAPH)) == INGRAPH )
            {
                pmte->mte_flags1 |= USED;

                if (!fAttached)
                {
                    pmte-> mte_flags1 &= ~INGRAPH;

                    // Count modules being detached
                    ldrcDLLDetach++;

                    // Count modules with DLL termination
                    if (pmte->mte_flags1 & MTEDLLTERM)
                        cDLLTerm++;
                }

                for ( i=0; i < pmte->mte_impmodcnt; i++ )
                {
                    pmteDown = (ldrmte_t *) ( *(
                       ( (ulong_t *) ( ((char *)pmte) + sizeof(ldrmte_t) ) )
                                         + i ) );

                    /*
                     * If the current module will remain in the MTE
                     * graph, then the mte_modptrs table may not
                     * contain a null pointer.  However, if the
                     * current module is a detach candidate, then
                     * it is possible to have a NULL pointer in
                     * the mte_modptrs table, due to a possible
                     * incomplete load.
                     *
                     * Assert this, and avoid all null pointers.
                     */

                    ldrAssert(    !fAttached
                               || (pmteDown != NULL)
                               || (pmte->mte_flags1 & LDRINVALID)  );

                    if (    (pmteDown != NULL)
                         && ( (pmteDown->mte_flags1 & (USED|INGRAPH) ) == 0 ) )
                    {
                        fDoAgain = TRUE;
                        pmteDown->mte_flags1 |= INGRAPH;
                    }
                }
            }
        }

    } while (fDoAgain);

}


#pragma code_seg()
#pragma code_seg("LDRSWAP_TEXT", "CODE")


/***LP  ldrRecordDLLTerm - ldrRunGraph subroutine to record DLL terminations
 *
 *      This function increments cDLLTerm each time it is called
 *      with a pointer to an MTE of a module requiring DLL termination
 *      notification.  It also records the handle of the module in
 *      the buffer to which pDLLTerm points.  NOTE: the zeroth entry
 *      of dllt_ahmod is used for the handle of the MTE for which
 *      DosFreeModule was called.
 *
 *      ldrRecordDLLTerm (pmte)
 *
 *      ENTRY   pmte            - MTE pointer
 *      RETURN  ulong_t         - NO_ERROR (always successful)
 *
 *      CALLS   NONE
 *
 *      WARNING NONE
 *
 *      EFFECTS Increments cDLLTerm; puts handle in pDLLTerm->dllt_ahmod.
 */

ulong_t ldrRecordDLLTerm( ldrmte_t *pmte )
{
    if (pmte->mte_flags1 & MTEDLLTERM)
    {
        // Must have buffer and valid count
        ldrAssert(    pDLLTerm != NULL
                   && cDLLTerm < (ulong_t)pDLLTerm->dllt_chmod  );

        // Record the handle
        pDLLTerm->dllt_ahmod[++cDLLTerm] = pmte->mte_handle;
    }
    return(NO_ERROR);
}





/***LP  ldrEnumDLLTerm - enumerate DLLs requiring termination notification
 *
 *      This function enumerates the DLLs requiring instance termination
 *      notification.  If there are none, it returns ERROR_NO_DLL_TERM.
 *      If there are some, it allocates a buffer in which to record
 *      their handles and returns a pointer to the buffer.  It assumes
 *      that the count of such DLLs has already been established by a
 *      call to ldrFindDetach.
 *
 *      ldrEnumDLLTerm (ppdt)
 *
 *      ENTRY   ppdt            - place to return buffer pointer
 *      RETURN  int             - error code (NO_ERROR if successful)
 *
 *      CALLS   ldrRunGraph, ldrRecordDLLTerm, VMAllocKHB
 *
 *      WARNING Expects cDLLTerm to be set appropriately upon entry.
 *
 *      EFFECTS Allocates heap record; uses cDLLTerm and pDLLTerm.
 */

int ldrEnumDLLTerm( dllterm_t **ppdt )
{
    int rc;

    // return if no work to do
    if (!cDLLTerm)
        return(ERROR_NO_DLL_TERM);

    // allocate buffer
    if ( rc = VMAllocKHB( VM_HKH_PUB_SWAPRW,
                          sizeof(dllterm_t) + cDLLTerm*sizeof(VMHOB),
                          (VMHOB) LDRNONRESOWNER,
                          NA,
                          ppdt ) )
        return(rc);

    pDLLTerm = *ppdt;

    // Save count of handles
    pDLLTerm->dllt_chmod = (ushort_t)cDLLTerm;

    // Reset count for ldrRecordDLLTerm
    cDLLTerm = 0;

    // Record MTE handles
    ldrRunGraph( FALSE, USED, ldrRecordDLLTerm );

    // Assert that the counts match when done
    ldrAssert( cDLLTerm == (ulong_t)pDLLTerm->dllt_chmod );

    return(NO_ERROR);
}



/***LP  ldrRunGraph - Apply a function to selected modules of the MTE list.
 *
 *      ldrRunGraph calls a worker function for all modules in the
 *      MTE list which have a selected setting of the USED and INGRAPH
 *      bits.
 *
 *      If fClear is true, ldrRunGraph will clear the USED and INGRAPH
 *      bits just after calling the function.
 *
 *      If the worker function returns an error, the scan will be
 *      terminated, and the error code will be propogated to the caller.
 *
 *      ENTRY   fClear
 *                      TRUE  : Leave with USED and INGRAPH bits clear
 *                      FALSE : Do not modify USED and INGRAPH bits
 *              fInGraphUsed
 *                      Combination of USED and INGRAPH bits to match on :
 *
 *                      USED    INGRAPH   Meaning
 *                      Clear   Clear   : Module not referenced
 *                      Clear   Set     : Invalid state at this point
 *                      Set     Clear   : Module is a detach candidate
 *                      Set     Set     : Module is referenced
 *              pwork
 *                      Pointer to worker routine to call.
 *                      The worker is passed an mte pointer.
 *
 *      EXIT    ULONG
 *              NO_ERROR
 *                      Worker called for each matching module
 *              ERROR
 *                      Worker returned error code; scan aborted.
 *
 */

ULONG ldrRunGraph( ULONG fClear,
                   ULONG fInGraphUsed,
                   ULONG (*pwork)(ldrmte_t *pmte) )
{
    register ldrmte_t *pmte;
    ULONG              rc = NO_ERROR;

    for ( pmte = mte_h; pmte != NULL; pmte = pmte->mte_link )
    {
        if ( (pmte->mte_flags1 & (USED|INGRAPH)) == fInGraphUsed )
        {
            if ( rc = (*pwork)(pmte) )
            {
                return(rc);
            }
        }
        if (fClear)
            pmte->mte_flags1 &= ~(USED|INGRAPH);
    }
    return (NO_ERROR);
}


// end of swappable code segment
#pragma code_seg()
