root/usr/src/cmd/sgs/rtld/common/remove.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Remove objects.  Objects need removal from a process as part of:
 *
 *  -   a dlclose() request
 *
 *  -   tearing down a dlopen(), lazy-load, or filter hierarchy that failed to
 *      completely load
 *
 * Any other failure condition will result in process exit (in which case all
 * we have to do is execute the fini's - tear down is unnecessary).
 *
 * Any removal of objects is therefore associated with a dlopen() handle.  There
 * is a small window between creation of the first dlopen() object and creating
 * its handle (in which case remove_so() can get rid of the new link-map if
 * necessary), but other than this all object removal is driven by inspecting
 * the components of a handle.
 *
 * Things to note.  The creation of a link-map, and its addition to the
 * link-map list occurs in elf_new_lm(), if this returns success the link-map
 * is valid and added, otherwise any steps (allocations) in the process of
 * creating the link-map would have been undone.  If a failure occurs between
 * creating the link-map and adding it to a handle, remove_so() is called to
 * remove the link-map.  If a failures occurs after a handle have been
 * created, remove_hdl() is called to remove the handle and the link-map.
 */

#include        <string.h>
#include        <stdio.h>
#include        <unistd.h>
#include        <dlfcn.h>
#include        <sys/debug.h>
#include        <sys/avl.h>
#include        <libc_int.h>
#include        <debug.h>
#include        "_rtld.h"
#include        "_audit.h"
#include        "_elf.h"
#include        "msg.h"

/*
 * Atexit callback provided by libc.  As part of dlclose() determine the address
 * ranges of all objects that are to be deleted.  Pass this information to
 * libc's pre-atexit routine.  Libc will purge any registered atexit() calls
 * related to those objects about to be deleted.
 */
static int
purge_exit_handlers(Lm_list *lml, Rt_map **tobj)
{
        uint_t                  num;
        Rt_map                  **_tobj;
        Lc_addr_range_t         *addr, *_addr;
        int                     error;
        int                     (*fptr)(Lc_addr_range_t *, uint_t);

        /*
         * Has a callback been established?
         */
        if ((fptr = lml->lm_lcs[CI_ATEXIT].lc_un.lc_func) == NULL)
                return (0);

        /*
         * Determine the total number of mapped segments that will be unloaded.
         */
        for (num = 0, _tobj = tobj; *_tobj != NULL; _tobj++) {
                Rt_map  *lmp = *_tobj;

                num += MMAPCNT(lmp);
        }

        /*
         * Account for a null entry at the end of the address range array.
         */
        if (num++ == 0)
                return (0);

        /*
         * Allocate an array for the address range.
         */
        if ((addr = malloc(num * sizeof (Lc_addr_range_t))) == NULL)
                return (1);

        /*
         * Fill the address range with each loadable segments size and address.
         */
        for (_tobj = tobj, _addr = addr; *_tobj != NULL; _tobj++) {
                Rt_map                  *lmp = *_tobj;
                mmapobj_result_t        *mpp = MMAPS(lmp);
                uint_t                  ndx;

                for (ndx = 0; ndx < MMAPCNT(lmp); ndx++, mpp++) {
                        _addr->lb = (void *)(uintptr_t)(mpp->mr_addr +
                            mpp->mr_offset);
                        _addr->ub = (void *)(uintptr_t)(mpp->mr_addr +
                            mpp->mr_msize);
                        _addr++;
                }
        }
        _addr->lb = _addr->ub = 0;

        leave(LIST(*tobj), 0);
        error = (*fptr)(addr, (num - 1));
        (void) enter(0);

        /*
         * If we fail to converse with libc, generate an error message to
         * satisfy any dlerror() usage.
         */
        if (error)
                eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ATEXIT), error);

        free(addr);
        return (error);
}

/*
 * Break down an Alist containing pathname descriptors.  In most instances, the
 * Alist is removed completely.  However, in some instances the alist is cleaned
 * of all entries, but retained for later use.
 */
void
remove_alist(Alist **alpp, int complete)
{
        Alist   *alp = *alpp;

        if (alp) {
                if (complete) {
                        free((void *)alp);
                        *alpp = NULL;
                } else {
                        alist_reset(alp);
                }
        }
}

/*
 * Remove a link-map list descriptor.  This is called to finalize the removal
 * of an entire link-map list, after all link-maps have been removed, or none
 * got added.  As load_one() can process a list of potential candidate objects,
 * the link-map descriptor must be maintained as each object is processed.  Only
 * after all objects have been processed can a failure condition finally tear
 * down the link-map list descriptor.
 */
void
remove_lml(Lm_list *lml)
{
        if (lml && (lml->lm_head == NULL)) {
                if (lml->lm_lmidstr)
                        free(lml->lm_lmidstr);
                if (lml->lm_alp)
                        free(lml->lm_alp);
                if (lml->lm_lists)
                        free(lml->lm_lists);
                if (lml->lm_aud_cookies)
                        free(lml->lm_aud_cookies);

                /*
                 * Cleanup any pending RTLDINFO in the case where it was
                 * allocated but not called (see _relocate_lmc()).
                 */
                if (lml->lm_rti)
                        free(lml->lm_rti);
                if (lml->lm_fpavl) {
                        /*
                         * As we are freeing the link-map list, all nodes must
                         * have previously been removed.
                         */
                        ASSERT(avl_numnodes(lml->lm_fpavl) == 0);
                        free(lml->lm_fpavl);
                }
                (void) aplist_delete_value(dynlm_list, lml);
                free(lml);
        }
}

/*
 * Remove a link-map.  This removes a link-map from its associated list and
 * free's up the link-map itself.  Note, all components that are freed are local
 * to the link-map, no inter-link-map lists are operated on as these are all
 * broken down by dlclose() while all objects are still mapped.
 *
 * This routine is called from dlclose() to zap individual link-maps after their
 * interdependencies (DEPENDS(), CALLER(), handles, etc.) have been removed.
 * This routine is also called from the bowels of load_one() in the case of a
 * link-map creation failure.
 */
void
remove_so(Lm_list *lml, Rt_map *lmp, Rt_map *clmp)
{
        Dyninfo *dip;

        if (lmp == NULL)
                return;

        /*
         * Unlink the link map from the link-map list.
         */
        if (lml && lmp)
                lm_delete(lml, lmp, clmp);

        /*
         * If this object contributed any local external vectors for the current
         * link-map list, remove the vectors.  If this object contributed any
         * global external vectors we should find some new candidates, or leave
         * this object lying around.
         */
        if (lml) {
                int     tag;

                for (tag = 0; tag < CI_MAX; tag++) {
                        if (lml->lm_lcs[tag].lc_lmp == lmp) {
                                lml->lm_lcs[tag].lc_lmp = NULL;
                                lml->lm_lcs[tag].lc_un.lc_val = 0;
                        }
                        if (glcs[tag].lc_lmp == lmp) {
                                ASSERT(glcs[tag].lc_lmp != NULL);
                                glcs[tag].lc_lmp = NULL;
                                glcs[tag].lc_un.lc_val = 0;
                        }
                }
        }

        DBG_CALL(Dbg_file_delete(lmp));

        /*
         * If this object is an auditor, determine whether any link-map lists
         * are maintaining cookies to represent this auditor.  These cookies
         * are established for local auditing preinit and activity events.
         */
        if (FLAGS(lmp) & FLG_RT_AUDIT) {
                Lm_list *nlml;
                Aliste  idx1;

                for (APLIST_TRAVERSE(dynlm_list, idx1, nlml)) {
                        Rt_map          *hlmp = nlml->lm_head;
                        Audit_client    *acp;
                        Aliste          idx2;

                        if ((hlmp == NULL) || (FLAGS(hlmp) & FLG_RT_AUDIT))
                                continue;

                        for (ALIST_TRAVERSE(nlml->lm_aud_cookies, idx2, acp)) {
                                if (acp->ac_lmp != lmp) {
                                        alist_delete(nlml->lm_aud_cookies,
                                            &idx2);
                                        break;
                                }
                        }
                }
        }

        /*
         * If this is a temporary link-map, put in place to facilitate the
         * link-edit or a relocatable object, then the link-map contains no
         * information that needs to be cleaned up.
         */
        if (FLAGS(lmp) & FLG_RT_OBJECT)
                return;

        /*
         * Remove any FullpathNode AVL names if they still exist.
         */
        if (FPNODE(lmp))
                fpavl_remove(lmp);

        /*
         * Remove any alias names.
         */
        if (ALIAS(lmp))
                free(ALIAS(lmp));

        /*
         * Remove any of this objects filtee infrastructure.  The filtees them-
         * selves have already been removed.
         */
        if (((dip = DYNINFO(lmp)) != NULL) && (FLAGS1(lmp) & MSK_RT_FILTER)) {
                uint_t  cnt, max = DYNINFOCNT(lmp);

                for (cnt = 0; cnt < max; cnt++, dip++) {
                        if ((dip->di_info == NULL) ||
                            ((dip->di_flags & MSK_DI_FILTER) == 0))
                                continue;

                        remove_alist((Alist **)&(dip->di_info), 1);
                }
        }

        /*
         * Deallocate any remaining cruft and free the link-map.
         */
        if (RLIST(lmp))
                remove_alist(&RLIST(lmp), 1);

        if (AUDITORS(lmp))
                audit_desc_cleanup(lmp);
        if (AUDINFO(lmp))
                audit_info_cleanup(lmp);

        /*
         * Note that COPY_R() and COPY_S() reference the same memory
         * location, and that we want to release the memory referenced
         * without regard to which list it logically belongs to. We can
         * use either pointer to do this.
         */
        if (COPY_R(lmp))
                free(COPY_R(lmp));

        /*
         * During a dlclose() any groups this object was a part of will have
         * been torn down.  However, we can get here to remove an object that
         * has failed to load, perhaps because its addition to a handle failed.
         * Therefore if this object indicates that its part of a group tear
         * these associations down.
         */
        if (GROUPS(lmp) != NULL) {
                Aliste  idx1;
                Grp_hdl *ghp;

                for (APLIST_TRAVERSE(GROUPS(lmp), idx1, ghp)) {
                        Grp_desc        *gdp;
                        Aliste          idx2;

                        for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                                if (gdp->gd_depend != lmp)
                                        continue;

                                alist_delete(ghp->gh_depends, &idx2);
                                break;
                        }
                }
                free(GROUPS(lmp));
        }
        if (HANDLES(lmp))
                free(HANDLES(lmp));

        /*
         * Clean up reglist if needed
         */
        if (reglist) {
                Reglist *cur, *prv, *del;

                cur = prv = reglist;
                while (cur) {
                        if (cur->rl_lmp == lmp) {
                                del = cur;
                                if (cur == reglist) {
                                        reglist = cur->rl_next;
                                        cur = prv = reglist;
                                } else {
                                        prv->rl_next = cur->rl_next;
                                        cur = cur->rl_next;
                                }
                                free(del);
                        } else {
                                prv = cur;
                                cur = cur->rl_next;
                        }
                }
        }

        /*
         * If this link map represents a relocatable object concatenation, then
         * the image was simply generated in allocated memory.  Free the memory.
         * Note: memory maps were fabricated for the relocatable object, and
         * the mapping infrastructure must be free'd, but there are no address
         * mappings that must be unmapped.
         *
         * Otherwise, unmap the object.
         */
        if (FLAGS(lmp) & FLG_RT_IMGALLOC)
                free((void *)ADDR(lmp));

        if (CAPCHAIN(lmp))
                free((void *)CAPCHAIN(lmp));

        if (MMAPS(lmp)) {
                if ((FLAGS(lmp) & FLG_RT_IMGALLOC) == 0)
                        unmap_obj(MMAPS(lmp), MMAPCNT(lmp));
                free(MMAPS(lmp));
        }

        free(lmp);
}

/*
 * Traverse an objects dependency list removing callers and dependencies.
 * There's a chicken and egg problem with tearing down link-maps.  Any
 * relationship between link-maps is maintained on a DEPENDS list, and an
 * associated CALLERS list.  These lists can't be broken down at the time a
 * single link-map is removed, as any related link-map may have already been
 * removed.  Thus, lists between link-maps must be broken down before the
 * individual link-maps themselves.
 */
static void
remove_lists(Rt_map *lmp, int lazy)
{
        Aliste          idx1;
        Bnd_desc        *bdp;

        /*
         * First, traverse this objects dependencies.
         */
        for (APLIST_TRAVERSE(DEPENDS(lmp), idx1, bdp)) {
                Rt_map          *dlmp = bdp->b_depend;

                /*
                 * Remove this object from the dependencies callers.
                 */
                (void) aplist_delete_value(CALLERS(dlmp), bdp);
                free(bdp);
        }
        if (DEPENDS(lmp)) {
                free(DEPENDS(lmp));
                DEPENDS(lmp) = NULL;
        }

        /*
         * Second, traverse this objects callers.
         */
        for (APLIST_TRAVERSE(CALLERS(lmp), idx1,  bdp)) {
                Rt_map          *clmp = bdp->b_caller;
                Dyninfo         *dip;

                /*
                 * If we're removing an object that was triggered by a lazyload,
                 * remove the callers DYNINFO() entry and bump the lazy counts.
                 * This reinitialization of the lazy information allows a lazy
                 * object to be reloaded again later.  Although we may be
                 * breaking down a group of lazyloaded objects because one has
                 * failed to relocate, it's possible that one or more of the
                 * individual objects can be reloaded without a problem.
                 */
                if (lazy && ((dip = DYNINFO(clmp)) != NULL)) {
                        uint_t  cnt, max = DYNINFOCNT(clmp);

                        for (cnt = 0; cnt < max; cnt++, dip++) {
                                if ((dip->di_flags & FLG_DI_LAZY) == 0)
                                        continue;

                                if (dip->di_info == (void *)lmp) {
                                        dip->di_info = NULL;

                                        if (LAZY(clmp)++ == 0)
                                                LIST(clmp)->lm_lazy++;
                                }
                        }
                }

                (void) aplist_delete_value(DEPENDS(clmp), bdp);
                free(bdp);
        }
        if (CALLERS(lmp)) {
                free(CALLERS(lmp));
                CALLERS(lmp) = NULL;
        }
}

/*
 * Delete any temporary link-map control list.
 */
void
remove_cntl(Lm_list *lml, Aliste lmco)
{
        Aliste  _lmco = lmco;
#if     DEBUG
        Lm_cntl *lmc;

        lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco);

        /*
         * This element should be empty.
         */
        ASSERT(lmc->lc_head == NULL);
#endif
        alist_delete_by_offset(lml->lm_lists, &_lmco);
}

/*
 * If a lazy loaded object, or filtee fails to load, possibly because it, or
 * one of its dependencies can't be relocated, then tear down any objects
 * that are apart of this link-map control list.
 */
static void
remove_incomplete(Lm_list *lml, Aliste lmco, Rt_map *clmp)
{
        Rt_map  *lmp;
        Lm_cntl *lmc;

        /* LINTED */
        lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco);

        /*
         * If auditing is in effect, the loading of these objects might have
         * resulted in la_objopen() events being posted.  Normally, an
         * la_objclose() event is posted after an object's .fini is executed,
         * just before the objects are unloaded.  These failed objects do not
         * have their .fini's executed, but an la_objclose() event should still
         * be posted to any auditors.
         */
        if ((lml->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_OBJCLOSE) {
                for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp))
                        audit_objclose(lmp, clmp);
        }

        /*
         * Remove any lists that may point between objects.
         */
        for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp))
                remove_lists(lmp, 1);

        /*
         * Finally, remove each object.  remove_so() calls lm_delete(), thus
         * effectively the link-map control head gets updated to point to the
         * next link-map.
         */
        while ((lmp = lmc->lc_head) != NULL)
                remove_so(lml, lmp, clmp);

        lmc->lc_head = lmc->lc_tail = NULL;
}

/*
 * Determine whether an object is deletable.
 */
static int
is_deletable(APlist **lmalp, APlist **ghalp, Rt_map *lmp)
{
        Aliste          idx;
        Bnd_desc        *bdp;
        Grp_hdl         *ghp;

        /*
         * If the object hasn't yet been relocated take this as a sign that
         * it's loading failed, thus we're here to cleanup.  If the object is
         * relocated it will only be retained if it was marked non-deletable,
         * and exists on the main link-map control list.
         */
        if ((FLAGS(lmp) & FLG_RT_RELOCED) &&
            (MODE(lmp) & RTLD_NODELETE) && (CNTL(lmp) == ALIST_OFF_DATA))
                return (0);

        /*
         * If this object is the head of a handle that has not been captured as
         * a candidate for deletion, then this object is in use from a dlopen()
         * outside of the scope of this dlclose() family.  Dlopen'ed objects,
         * and filtees, have group descriptors for their callers.  Typically
         * this parent will have callers that are not apart of this dlclose()
         * family, and thus would be caught by the CALLERS test below.  However,
         * if the caller had itself been dlopen'ed, it may not have any explicit
         * callers registered for itself.  Thus, by looking for objects with
         * handles we can ferret out these outsiders.
         */
        for (APLIST_TRAVERSE(HANDLES(lmp), idx, ghp)) {
                /*
                 * If this is a private handle, then the handle isn't referenced
                 * from outside of the group of objects being deleted, and can
                 * be ignored when evaluating objects for deletion.
                 */
                if (ghp->gh_flags & GPH_PRIVATE)
                        continue;
                if (aplist_test(ghalp, ghp, 0) != ALE_EXISTS)
                        return (0);
        }

        /*
         * If this object is called by any object outside of the family of
         * objects selected for deletion, it can't be deleted.
         */
        for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) {
                if (aplist_test(lmalp, bdp->b_caller, 0) != ALE_EXISTS)
                        return (0);
        }

        /*
         * This object is a candidate for deletion.
         */
        return (1);
}

/*
 * Collect the groups (handles) and associated objects that are candidates for
 * deletion.  The criteria for deleting an object is whether it is only refer-
 * enced from the objects within the groups that are candidates for deletion.
 */
static int
gdp_collect(APlist **ghalpp, APlist **lmalpp, Grp_hdl *ghp1)
{
        Aliste          idx1;
        Grp_desc        *gdp;
        int             action;

        /*
         * Add this group to our group collection.  If it isn't added either an
         * allocation has failed, or it already exists.
         */
        if ((action = aplist_test(ghalpp, ghp1, AL_CNT_GRPCLCT)) !=
            ALE_CREATE)
                return (action);

        /*
         * Traverse the dependencies of the group and collect the associated
         * objects.
         */
        for (ALIST_TRAVERSE(ghp1->gh_depends, idx1, gdp)) {
                Rt_map  *lmp = gdp->gd_depend;

                /*
                 * We only want to process dependencies for deletion.  Although
                 * we want to purge group descriptors for parents, we don't want
                 * to analyze the parent itself for additional filters or
                 * deletion.
                 */
                if ((gdp->gd_flags & GPD_PARENT) ||
                    ((gdp->gd_flags & GPD_ADDEPS) == 0))
                        continue;

                if ((action = aplist_test(lmalpp, lmp, AL_CNT_GRPCLCT)) ==
                    ALE_ALLOCFAIL)
                        return (0);
                if (action == ALE_EXISTS)
                        continue;

                /*
                 * If this object is a candidate for deletion, determine if the
                 * object provides any filtees.  If so, the filter groups are
                 * added to the group collection.
                 *
                 * An object is a candidate for deletion if:
                 *
                 *  -   the object hasn't yet been relocated, in which case
                 *      we're here to clean up a failed load, or
                 *  -   the object doesn't reside on the base link-map control
                 *      list, in which case a group of objects, typically
                 *      lazily loaded, or filtees, need cleaning up, or
                 *  -   the object isn't tagged as non-deletable.
                 */
                if ((((FLAGS(lmp) & FLG_RT_RELOCED) == 0) ||
                    (CNTL(lmp) != ALIST_OFF_DATA) ||
                    ((MODE(lmp) & RTLD_NODELETE) == 0)) &&
                    (FLAGS1(lmp) & MSK_RT_FILTER)) {
                        Dyninfo *dip = DYNINFO(lmp);
                        uint_t  cnt, max = DYNINFOCNT(lmp);

                        for (cnt = 0; cnt < max; cnt++, dip++) {
                                Alist   *falp;
                                Aliste  idx2;
                                Pdesc   *pdp;

                                if (((falp = (Alist *)dip->di_info) == NULL) ||
                                    ((dip->di_flags & MSK_DI_FILTER) == 0))
                                        continue;

                                for (ALIST_TRAVERSE(falp, idx2, pdp)) {
                                        Grp_hdl *ghp2;

                                        if ((pdp->pd_plen == 0) || ((ghp2 =
                                            (Grp_hdl *)pdp->pd_info) == NULL))
                                                continue;

                                        if (gdp_collect(ghalpp, lmalpp,
                                            ghp2) == 0)
                                                return (0);
                                }
                        }
                }
        }
        return (1);
}

/*
 * Traverse the list of deletable candidates.  If an object can't be deleted
 * then neither can its dependencies or filtees.  Any object that is cleared
 * from being deleted drops the deletion count, plus, if there are no longer
 * any deletions pending we can discontinue any further processing.
 */
static int
remove_rescan(APlist *lmalp, APlist *ghalp, int *delcnt)
{
        Aliste          idx1;
        Rt_map          *lmp;
        int             rescan = 0;

        for (APLIST_TRAVERSE(lmalp, idx1, lmp)) {
                Aliste          idx2;
                Bnd_desc        *bdp;
                Dyninfo         *dip;
                uint_t          cnt, max;

                if (FLAGS(lmp) & FLG_RT_DELETE)
                        continue;

                /*
                 * As this object can't be deleted, make sure its dependencies
                 * aren't deleted either.
                 */
                for (APLIST_TRAVERSE(DEPENDS(lmp), idx2, bdp)) {
                        Rt_map  *dlmp = bdp->b_depend;

                        if (FLAGS(dlmp) & FLG_RT_DELETE) {
                                FLAGS(dlmp) &= ~FLG_RT_DELETE;
                                if (--(*delcnt) == 0)
                                        return (0);
                                rescan = 1;
                        }
                }

                /*
                 * If this object is a filtee and one of its filters is outside
                 * of this dlclose family, then it can't be deleted either.
                 */
                if ((FLAGS1(lmp) & MSK_RT_FILTER) == 0)
                        continue;

                dip = DYNINFO(lmp);
                max = DYNINFOCNT(lmp);

                for (cnt = 0; cnt < max; cnt++, dip++) {
                        Alist   *falp;
                        Pdesc   *pdp;

                        if (((falp = (Alist *)dip->di_info) == NULL) ||
                            ((dip->di_flags & MSK_DI_FILTER) == 0))
                                continue;

                        for (ALIST_TRAVERSE(falp, idx2, pdp)) {
                                Aliste          idx3;
                                Grp_hdl         *ghp;
                                Grp_desc        *gdp;

                                if ((pdp->pd_plen == 0) ||
                                    ((ghp = (Grp_hdl *)pdp->pd_info) == NULL))
                                        continue;

                                if (aplist_test(&ghalp, ghp, 0) ==
                                    ALE_EXISTS)
                                        continue;

                                for (ALIST_TRAVERSE(ghp->gh_depends, idx3,
                                    gdp)) {
                                        Rt_map  *dlmp = gdp->gd_depend;

                                        if (FLAGS(dlmp) & FLG_RT_DELETE) {
                                                FLAGS(dlmp) &= ~FLG_RT_DELETE;
                                                if (--(*delcnt) == 0)
                                                        return (0);
                                                rescan = 1;
                                        }
                                }

                                /*
                                 * Remove this group handle from our dynamic
                                 * deletion list.
                                 */
                                (void) aplist_delete_value(ghalp, ghp);
                        }
                }
        }
        return (rescan);
}

/*
 * Cleanup any collection alists we've created.
 */
static void
remove_collect(APlist *ghalp, APlist *lmalp)
{
        if (ghalp)
                free(ghalp);
        if (lmalp)
                free(lmalp);
}

/*
 * Remove a handle, leaving the associated objects intact.
 */
void
free_hdl(Grp_hdl *ghp)
{
        if (--(ghp->gh_refcnt) == 0) {
                Grp_desc        *gdp;
                Aliste          idx;
                uintptr_t       ndx;

                for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) {
                        Rt_map  *lmp = gdp->gd_depend;

                        if (ghp->gh_ownlmp == lmp)
                                (void) aplist_delete_value(HANDLES(lmp), ghp);
                        (void) aplist_delete_value(GROUPS(lmp), ghp);
                }
                (void) free(ghp->gh_depends);

                /* LINTED */
                ndx = (uintptr_t)ghp % HDLIST_SZ;
                (void) aplist_delete_value(hdl_alp[ndx], ghp);

                (void) free(ghp);
        }
}

/*
 * If a load operation, using a new link-map control list, has failed, then
 * forcibly remove the failed objects.  This failure can occur as a result
 * of a lazy load, a dlopen(), or a filtee load, once the application is
 * running.  If the link-map control list has not yet started relocation, then
 * cleanup is simply a process of removing all the objects from the control
 * list.  If relocation has begun, then other loads may have been triggered to
 * satisfy the relocations, and thus we need to break down the control list
 * using handles.
 *
 * The objects associated with this load must be part of a unique handle.  In
 * the case of a dlopen() or filtee request, a handle will have been created.
 * For a lazyload request, a handle must be generated so that the remove
 * process can use the handle.
 *
 * During the course of processing these objects, other objects (handles) may
 * have been loaded to satisfy relocation requirements.  After these families
 * have successfully loaded, they will have been propagated to the same link-map
 * control list.  The failed objects need to be removed from this list, while
 * any successfully loaded families can be left alone, and propagated to the
 * previous link-map control list.  By associating each load request with a
 * handle, we can isolate the failed objects while not interfering with any
 * successfully loaded families.
 */
void
remove_lmc(Lm_list *lml, Rt_map *clmp, Aliste lmco, const char *name)
{
        Grp_hdl         *ghp;
        Grp_desc        *gdp;
        Aliste          idx;
        Lm_cntl         *lmc;
        Rt_map          *lmp;

        /*
         * Determine the link-map control list, and whether any object has been
         * added to this list.
         */
        /* LINTED */
        lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco);
        if (lmc->lc_head == NULL)
                return;

        DBG_CALL(Dbg_file_cleanup(lml, name, lmco));

        /*
         * Obtain a handle for the first object on the link-map control list.
         * If none exists (which would occur from a lazy load request), and
         * the link-map control list is being relocated, create a handle.
         */
        lmp = lmc->lc_head;
        if (HANDLES(lmp)) {
                ghp = (Grp_hdl *)HANDLES(lmp)->apl_data[0];

                /*
                 * If this is a private handle, remove this state, so as to
                 * prevent any attempt to remove the handle more than once.
                 */
                ghp->gh_flags &= ~GPH_PRIVATE;

        } else if (lmc->lc_flags & LMC_FLG_RELOCATING) {
                /*
                 * Establish a handle, and should anything fail, fall through
                 * to remove the link-map control list.
                 */
                if (((ghp = hdl_create(lml, lmc->lc_head, NULL, GPH_PUBLIC,
                    GPD_ADDEPS, 0)) == NULL) ||
                    (hdl_initialize(ghp, lmc->lc_head, 0, 0) == 0))
                        lmc->lc_flags &= ~LMC_FLG_RELOCATING;
        } else {
                ghp = NULL;
        }

        /*
         * If relocation hasn't begun, simply remove all the objects from this
         * list, and any handle that may have been created.
         */
        if ((lmc->lc_flags & LMC_FLG_RELOCATING) == 0) {
                remove_incomplete(lml, lmco, clmp);

                if (ghp) {
                        ghp->gh_refcnt = 1;
                        free_hdl(ghp);
                }
                return;
        }

        ASSERT(ghp != NULL);

        /*
         * As the objects of this handle are being forcibly removed, first
         * remove any associations to objects on parent link-map control
         * lists.  This breaks the bond between a caller and a hierarchy of
         * dependencies represented by the handle, thus the caller doesn't lock
         * the hierarchy and prevent their deletion from the generic handle
         * processing or remove_hdl().
         *
         * This scenario can be produced when the relocation of a object
         * results in vectoring through a filter that is already loaded.  The
         * filtee may be on the link-map list that is presently being processed,
         * however an association between the filter and filtee would have been
         * established during filtee processing.  It is this association that
         * must be broken to allow the objects on this link-map list to be
         * removed.
         */
        for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) {
                Rt_map  *lmp = gdp->gd_depend;

                /*
                 * If this object has not been relocated, break down any
                 * dependency relationships the object might have established.
                 */
                if ((FLAGS(lmp) & FLG_RT_RELOCED) == 0)
                        remove_lists(lmp, 1);

                if (CNTL(lmp) == lmco)
                        continue;

                if (gdp->gd_flags & GPD_FILTER) {
                        Dyninfo *dip = DYNINFO(lmp);
                        uint_t  cnt, max = DYNINFOCNT(lmp);

                        for (cnt = 0; cnt < max; cnt++, dip++) {
                                Alist   *falp;
                                Aliste  idx2;
                                Pdesc   *pdp;

                                if (((falp = (Alist *)dip->di_info) == NULL) ||
                                    ((dip->di_flags & MSK_DI_FILTER) == 0))
                                        continue;

                                for (ALIST_TRAVERSE(falp, idx2, pdp)) {
                                        if ((Grp_hdl *)pdp->pd_info == ghp) {
                                                pdp->pd_info = NULL;
                                                break;
                                        }
                                }
                        }
                }
                (void) aplist_delete_value(GROUPS(lmp), ghp);
                alist_delete(ghp->gh_depends, &idx);
        }

        /*
         * Having removed any callers, set the group handle reference count to
         * one, and let the generic handle remover delete the associated
         * objects.
         */
        ghp->gh_refcnt = 1;
        (void) remove_hdl(ghp, clmp, NULL);

        /*
         * If this link-map control list still contains objects, determine the
         * previous control list and move the objects.
         */
        if (lmc->lc_head) {
                Lm_cntl *plmc;
                Aliste  plmco;

                plmco = lmco - lml->lm_lists->al_size;
                /* LINTED */
                plmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, plmco);

                lm_move(lml, lmco, plmco, lmc, plmc);
        }
}

/*
 * Remove the objects associated with a handle.  There are two goals here, to
 * delete the objects associated with the handle, and to remove the handle
 * itself.  Things get a little more complex if the objects selected for
 * deletion are filters, in this case we also need to collect their filtees,
 * and process the combined groups as a whole.  But, care still must be exer-
 * cised to make sure any filtees found aren't being used by filters outside of
 * the groups we've collect.  The series of events is basically:
 *
 *  -   Determine the groups (handles) that might be deletable.
 *
 *  -   Determine the objects of these handles that can be deleted.
 *
 *  -   Fire the fini's of those objects selected for deletion.
 *
 *  -   Remove all inter-dependency linked lists while the objects link-maps
 *      are still available.
 *
 *  -   Remove all deletable objects link-maps and unmap the objects themselves.
 *
 *  -   Remove the handle descriptors for each deleted object, and hopefully
 *      the whole handle.
 *
 * A handle that can't be deleted is added to an orphans list.  This list is
 * revisited any time another dlclose() request results in handle descriptors
 * being deleted.  These deleted descriptors can be sufficient to allow the
 * final deletion of the orphaned handles.
 */
int
remove_hdl(Grp_hdl *ghp, Rt_map *clmp, int *removed)
{
        Rt_map          *lmp;
        int             rescan = 0;
        int             delcnt = 0, rmcnt = 0, error = 0, orphans;
        APlist          *lmalp = NULL, *ghalp = NULL;
        Aliste          idx1, idx2;
        Grp_hdl         *ghp2;
        Grp_desc        *gdp;
        Lm_list         *lml = NULL;

        /*
         * Generate the family of groups and objects that are candidates for
         * deletion.  This consists of the objects that are explicitly defined
         * as dependencies of this handle, plus any filtee handles and their
         * associated objects.
         */
        if (gdp_collect(&ghalp, &lmalp, ghp) == 0) {
                remove_collect(ghalp, lmalp);
                return (0);
        }

        DBG_CALL(Dbg_file_hdl_title(DBG_HDL_DELETE));

        /*
         * Traverse the groups we've collected to determine if any filtees are
         * included.  If so, and the filtee handle is in use by a filter outside
         * of the family of objects collected for this deletion, it can not be
         * removed.
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                Grp_hdl *ghp = ghp2;

                DBG_CALL(Dbg_file_hdl_collect(ghp, 0));

                if ((ghp->gh_flags & GPH_FILTEE) == 0)
                        continue;

                /*
                 * Special case for ld.so.1.  There can be multiple instances of
                 * libdl.so.1 using this handle, so although we want the handles
                 * reference count to be decremented, we don't want the handle
                 * removed.
                 */
                if (ghp->gh_flags & GPH_LDSO) {
                        DBG_CALL(Dbg_file_hdl_collect(ghp,
                            NAME(lml_rtld.lm_head)));
                        aplist_delete(ghalp, &idx1);
                        continue;
                }

                for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                        Grp_hdl *ghp3;
                        Aliste  idx3;

                        /*
                         * Determine whether this dependency is the filtee's
                         * parent filter, and that it isn't also an explicit
                         * dependency (in which case it would have added its own
                         * dependencies to the handle).
                         */
                        if ((gdp->gd_flags &
                            (GPD_FILTER | GPD_ADDEPS)) != GPD_FILTER)
                                continue;

                        lmp = gdp->gd_depend;

                        if (FLAGS(lmp) & FLG_RT_DELETE)
                                continue;

                        if (aplist_test(&lmalp, lmp, 0) == ALE_EXISTS)
                                continue;

                        /*
                         * Remove this group handle from our dynamic deletion
                         * list.  In addition, recompute the list of objects
                         * that are candidates for deletion to continue this
                         * group verification.
                         */
                        DBG_CALL(Dbg_file_hdl_collect(ghp, NAME(lmp)));
                        aplist_delete(ghalp, &idx1);

                        free(lmalp);
                        lmalp = NULL;
                        for (APLIST_TRAVERSE(ghalp, idx3, ghp3)) {
                                Aliste          idx4;
                                Grp_desc        *gdp4;

                                for (ALIST_TRAVERSE(ghp3->gh_depends,
                                    idx4, gdp4))  {
                                        if ((gdp4->gd_flags & GPD_ADDEPS) == 0)
                                                continue;
                                        if (aplist_test(&lmalp, gdp4->gd_depend,
                                            AL_CNT_GRPCLCT) == ALE_ALLOCFAIL) {
                                                remove_collect(ghalp, lmalp);
                                                return (0);
                                        }
                                }
                        }
                        break;
                }
        }

        /*
         * Now that we've collected all the handles dependencies, traverse the
         * collection determining whether they are a candidate for deletion.
         */
        for (APLIST_TRAVERSE(lmalp, idx1, lmp)) {
                /*
                 * Establish which link-map list we're dealing with for later
                 * .fini processing.
                 */
                if (lml == NULL)
                        lml = LIST(lmp);

                /*
                 * If an object isn't a candidate for deletion we'll have to
                 * rescan the handle insuring that this objects dependencies
                 * aren't deleted either.
                 */
                if (is_deletable(&lmalp, &ghalp, lmp)) {
                        FLAGS(lmp) |= FLG_RT_DELETE;
                        delcnt++;
                } else
                        rescan = 1;
        }

        /*
         * Rescan the handle if any objects where found non-deletable.
         */
        while (rescan)
                rescan = remove_rescan(lmalp, ghalp, &delcnt);

        /*
         * Now that we have determined the number of groups that are candidates
         * for removal, mark each group descriptor as a candidate for removal
         * from the group.
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                for (ALIST_TRAVERSE(ghp2->gh_depends, idx2, gdp))
                        gdp->gd_flags |= GPD_REMOVE;
        }

        /*
         * Now that we know which objects on this handle can't be deleted
         * determine whether they still need to remain identified as belonging
         * to this group to be able to continue binding to one another.
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                Grp_hdl *ghp = ghp2;

                for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                        Aliste          idx3;
                        Bnd_desc        *bdp;

                        lmp = gdp->gd_depend;

                        if (FLAGS(lmp) & FLG_RT_DELETE)
                                continue;

                        for (APLIST_TRAVERSE(DEPENDS(lmp), idx3, bdp)) {
                                Aliste          idx4;
                                Grp_desc        *gdp4;
                                Rt_map          *dlmp = bdp->b_depend;

                                /*
                                 * If this dependency (dlmp) can be referenced
                                 * by the caller (clmp) without being part of
                                 * this group (ghp) then belonging to this group
                                 * is no longer necessary.  This can occur when
                                 * objects are part of multiple handles, or if a
                                 * previously deleted handle was moved to the
                                 * orphan list and has been reopened.  Note,
                                 * first make sure the caller can reference the
                                 * dependency with this group, if it can't we
                                 * must be bound to a filtee, so there's no need
                                 * to remain a part of this group either.
                                 */
                                if ((callable(lmp, dlmp, 0, 0) == 0) ||
                                    callable(lmp, dlmp, ghp, 0))
                                        continue;

                                if (gdp->gd_flags & GPD_REMOVE)
                                        gdp->gd_flags &= ~GPD_REMOVE;

                                for (ALIST_TRAVERSE(ghp->gh_depends,
                                    idx4, gdp4)) {
                                        if (gdp4->gd_depend != dlmp)
                                                continue;

                                        if (gdp4->gd_flags & GPD_REMOVE)
                                                gdp4->gd_flags &= ~GPD_REMOVE;
                                }
                        }
                }
        }

        /*
         * If the owner of a handle can't be deleted and it's handle descriptor
         * must remain also, don't delete the handle at all.  Leave it for
         * possible later use.  Although it's left intact, it will still be
         * moved to the orphans list, as we might be able to revisit it on later
         * dlclose() operations and finally remove the underlying objects.  Note
         * that the handle still remains attached to the owner via the HANDLES
         * list, so that it can be re-associated to the owner if a dlopen()
         * of this object reoccurs.
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                Grp_hdl *ghp = ghp2;

                /*
                 * If this handle is already an orphan, or if it's owner is
                 * deletable there's no need to inspect its dependencies.
                 */
                if ((ghp->gh_ownlmp == NULL) ||
                    (FLAGS(ghp->gh_ownlmp) & FLG_RT_DELETE))
                        continue;

                /*
                 * Make sure all handle dependencies aren't removed or the
                 * dependencies themselves aren't deleted.
                 */
                for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                        lmp = gdp->gd_depend;

                        /*
                         * The first dependency of a non-orphaned handle is the
                         * owner.  If the handle descriptor for this isn't
                         * required there's no need to look at any other of the
                         * handles dependencies.
                         */
                        if ((lmp == ghp->gh_ownlmp) &&
                            (gdp->gd_flags & GPD_REMOVE))
                                break;

                        if (gdp->gd_flags & GPD_REMOVE)
                                gdp->gd_flags &= ~GPD_REMOVE;
                        if (FLAGS(lmp) & FLG_RT_DELETE) {
                                FLAGS(lmp) &= ~FLG_RT_DELETE;
                                delcnt--;
                        }
                }
        }

        /*
         * Final scan of objects to see if any objects are to to be deleted.
         * Also - display diagnostic information on what operations are to be
         * performed on the collected handles before firing .fini's (which
         * produces additional diagnostics).
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                Grp_hdl *ghp = ghp2;

                DBG_CALL(Dbg_file_hdl_title(DBG_HDL_DELETE));

                for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                        Grp_hdl *ghp3;
                        Aliste  idx3;
                        int     flag;

                        lmp = gdp->gd_depend;

                        /*
                         * Note, we must never delete a parent.  The parent
                         * may already be tagged for deletion from a previous
                         * dlclose(). That dlclose has triggered this dlclose(),
                         * but the parents deletion is the responsibility of the
                         * previous dlclose(), not this one.
                         */
                        if ((FLAGS(lmp) & FLG_RT_DELETE) &&
                            ((gdp->gd_flags & GPD_PARENT) == 0)) {
                                flag = DBG_DEP_DELETE;

                                /*
                                 * Remove any pathnames from the FullpathNode
                                 * AVL tree.  As we're about to fire .fini's,
                                 * it's possible this object will be required
                                 * again, in which case we want to make sure a
                                 * new version of the object gets loaded.
                                 */
                                if (FPNODE(lmp))
                                        fpavl_remove(lmp);
                        } else if (gdp->gd_flags & GPD_REMOVE)
                                flag = DBG_DEP_REMOVE;
                        else
                                flag = DBG_DEP_REMAIN;

                        DBG_CALL(Dbg_file_hdl_action(ghp, lmp, flag, 0));

                        /*
                         * If this object contains any private handles, remove
                         * them now.
                         */
                        for (APLIST_TRAVERSE(HANDLES(lmp), idx3, ghp3)) {
                                if (ghp3->gh_flags & GPH_PRIVATE)
                                        free_hdl(ghp3);
                        }
                }
        }

        /*
         * If there are objects to be deleted process their .fini's.
         */
        if (delcnt) {
                Rt_map  **tobj;

                /*
                 * Sort and fire all fini's of the objects selected for
                 * deletion.  Note that we have to start our search from the
                 * link-map head - there's no telling whether this object has
                 * dependencies on objects that were loaded before it and which
                 * can now be deleted.  If the tsort() fails because of an
                 * allocation error then that might just be a symptom of why
                 * we're here in the first place - forgo the fini's but
                 * continue to try cleaning up.
                 */
                lml->lm_flags |= LML_FLG_OBJDELETED;

                if (((tobj = tsort(lml->lm_head, delcnt,
                    (RT_SORT_DELETE | RT_SORT_FWD))) != NULL) &&
                    (tobj != (Rt_map **)S_ERROR)) {
                        error = purge_exit_handlers(lml, tobj);
                        call_fini(lml, tobj, clmp);
                }
        }

        /*
         * Now that .fini processing (which may have involved new bindings)
         * is complete, remove all inter-dependency lists from those objects
         * selected for deletion.
         */
        for (APLIST_TRAVERSE(lmalp, idx1, lmp)) {
                Dyninfo *dip;
                uint_t  cnt, max;

                if (FLAGS(lmp) & FLG_RT_DELETE)
                        remove_lists(lmp, 0);

                /*
                 * Determine whether we're dealing with a filter, and if so
                 * process any inter-dependencies with its filtee's.
                 */
                if ((FLAGS1(lmp) & MSK_RT_FILTER) == 0)
                        continue;

                dip = DYNINFO(lmp);
                max = DYNINFOCNT(lmp);

                for (cnt = 0; cnt < max; cnt++, dip++) {
                        Alist   *falp;
                        Aliste  idx2;
                        Pdesc   *pdp;

                        if (((falp = (Alist *)dip->di_info) == NULL) ||
                            ((dip->di_flags & MSK_DI_FILTER) == 0))
                                continue;

                        for (ALIST_TRAVERSE(falp, idx2, pdp)) {
                                Grp_hdl *ghp;

                                if ((pdp->pd_plen == 0) ||
                                    ((ghp = (Grp_hdl *)pdp->pd_info) == NULL))
                                        continue;

                                /*
                                 * Determine whether this filtee's handle is a
                                 * part of the list of handles being deleted.
                                 */
                                if (aplist_test(&ghalp, ghp, 0) == ALE_EXISTS) {
                                        /*
                                         * If this handle exists on the deletion
                                         * list, then it has been removed.  If
                                         * this filter isn't going to be
                                         * deleted, sever its reference to the
                                         * handle.
                                         */
                                        pdp->pd_info = NULL;
                                } else {
                                        /*
                                         * If this handle isn't on the deletion
                                         * list, then it must still exist.  If
                                         * this filter is being deleted, make
                                         * sure the filtees reference count
                                         * gets decremented.
                                         */
                                        if (FLAGS(lmp) & FLG_RT_DELETE) {
                                                (void) dlclose_core(ghp,
                                                    lmp, lml);
                                        }
                                }
                        }
                }
        }

        /*
         * If called from dlclose(), determine if there are already handles on
         * the orphans list that we can reinvestigate.
         */
        if ((removed == 0) && aplist_nitems(hdl_alp[HDLIST_ORP]))
                orphans = 1;
        else
                orphans = 0;

        /*
         * Finally remove any handle infrastructure and remove any objects
         * marked for deletion.
         */
        for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) {
                Grp_hdl *ghp = ghp2;

                /*
                 * If we're not dealing with orphaned handles remove this handle
                 * from its present handle list.
                 */
                if (removed == 0) {
                        uintptr_t ndx;

                        /* LINTED */
                        ndx = (uintptr_t)ghp % HDLIST_SZ;
                        (void) aplist_delete_value(hdl_alp[ndx], ghp);
                }

                /*
                 * Traverse each handle dependency.  Retain the dependencies
                 * flags to insure we don't delete any parents (the flags
                 * information is deleted as part of the alist removal that
                 * occurs before we inspect the object for deletion).
                 */
                for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) {
                        uint_t  flags = gdp->gd_flags;

                        if ((flags & GPD_REMOVE) == 0)
                                continue;

                        lmp = gdp->gd_depend;
                        rmcnt++;

                        /*
                         * If this object is the owner of the handle break that
                         * association in case the handle is retained.
                         */
                        if (ghp->gh_ownlmp == lmp) {
                                (void) aplist_delete_value(HANDLES(lmp), ghp);
                                ghp->gh_ownlmp = NULL;
                        }

                        (void) aplist_delete_value(GROUPS(lmp), ghp);
                        alist_delete(ghp->gh_depends, &idx2);

                        /*
                         * Complete the link-map deletion if appropriate.
                         */
                        if ((FLAGS(lmp) & FLG_RT_DELETE) &&
                            ((flags & GPD_PARENT) == 0)) {
                                tls_modaddrem(lmp, TM_FLG_MODREM);
                                remove_so(LIST(lmp), lmp, clmp);
                        }
                }

                /*
                 * If we've deleted all the dependencies of the handle, finalize
                 * the cleanup by removing the handle itself.
                 *
                 * Otherwise we're left with a handle containing one or more
                 * objects that can not be deleted (they're in use by other
                 * handles, non-deletable, etc.), but require to remain a part
                 * of this group to allow them to continue binding to one
                 * another.
                 *
                 * If the handles reference count is zero, or represents a
                 * link-map list (dlopen(0)), then move that handle to the
                 * orphans list.  Should another dlclose() operation occur that
                 * results in the removal of handle descriptors, these orphan
                 * handles are re-examined to determine if their deletion can
                 * be completed.
                 */
                if (ghp->gh_depends->al_nitems == 0) {
                        free(ghp->gh_depends);
                        free(ghp);

                } else if ((ghp->gh_refcnt == 0) &&
                    ((ghp->gh_flags & GPH_ZERO) == 0)) {
                        /*
                         * Move this handle to the orphans list.
                         */
                        (void) aplist_append(&hdl_alp[HDLIST_ORP], ghp,
                            AL_CNT_HANDLES);

                        if (DBG_ENABLED) {
                                DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ORPHAN));
                                for (ALIST_TRAVERSE(ghp->gh_depends, idx1, gdp))
                                        DBG_CALL(Dbg_file_hdl_action(ghp,
                                            gdp->gd_depend, DBG_DEP_ORPHAN, 0));
                        }
                }
        }

        /*
         * If no handle descriptors got removed there's no point in looking for
         * orphans to process.
         */
        if (rmcnt == 0)
                orphans = 0;

        /*
         * Cleanup any alists we've created.
         */
        remove_collect(ghalp, lmalp);

        /*
         * If orphan processing isn't required we're done.  If our processing
         * originated from investigating orphans, return the number of handle
         * descriptors removed as an indication whether orphan processing
         * should continue.
         */
        if (orphans == 0) {
                if (removed)
                        *removed = rmcnt;
                return (error);
        }

        /*
         * Traverse the orphans list as many times as necessary until no
         * handle removals occur.
         */
        do {
                APlist          *alp;
                Aliste          idx;
                Grp_hdl         *ghp, *oghp = NULL;
                int             title = 0;

                /*
                 * Effectively clean the HDLIST_ORP list.  Any object that can't
                 * be removed will be re-added to the list.
                 */
                alp = hdl_alp[HDLIST_ORP];
                hdl_alp[HDLIST_ORP] = NULL;

                rescan = 0;
                for (APLIST_TRAVERSE(alp, idx, ghp)) {
                        int     _error, _remove;

                        if (title++ == 0)
                                DBG_CALL(Dbg_file_del_rescan(ghp->gh_ownlml));

                        if (oghp) {
                                (void) aplist_delete_value(alp, oghp);
                                oghp = NULL;
                        }

                        if (((_error = remove_hdl(ghp, clmp, &_remove)) != 0) &&
                            (error == 0))
                                error = _error;

                        if (_remove)
                                rescan++;

                        oghp = ghp;
                }
                if (oghp) {
                        (void) aplist_delete_value(alp, oghp);
                        oghp = NULL;
                }
                if (alp)
                        free((void *)alp);

        } while (rescan && aplist_nitems(hdl_alp[HDLIST_ORP]));

        return (error);
}