/*static char *SCCSID = "@(#)recstruc.c	6.2 92/02/19";*/
/*static char *SCCSID = "@(#)recstruc.c	6.2 92/02/19";*/
/***	RECSTRUC.C - chk_gather_structure routine to recover orphaned
 *					 structures off disk
 *
 *	CHKDSK
 *	Peter A. Williams
 *	Copyright 1988 Microsoft Corporation
 *
 *	Modification history:
 *	P.A. Williams	08/01/89	Original for HPFS 1.0
 *
 */

#include "ldefs.h"
#include "ltypes.h"
#include "globals.h"
#include <pr_chk.h>

/***	chk_gather_structure - trace a block's children and collect them
 *
 *	This function is called by chk_recover_tree when it has traced
 *	a lost structure as far back through parent pointers as it
 *	can.  Gather_structure recursively chains through the block's
 *	child pointers, allocating memory for them and linking them
 *	together.  The "parent" and "children" fields of the root
 *	of the tree we're creating will be filled in.
 *
 *	The pointer passed already contains an image of the block
 *	in question.  Gather_structure fills in the child pointers,
 *	and when chaining recursively, allocates a mem_blk and
 *	fills it in for the child BEFORE recursion.
 *
 *	If the structure is totally useless (for example, if a file's
 *	only run is cross-linked to an existing, good file), this function
 *	returns a non-zero value, indicating that the caller should delete
 *	any reference to the node.
 *
 *	chk_gather_structure (lsn, mem, top, type)
 *
 *	ENTRY		lsn  - where this structure is on disk
 *			mem  - memory tree node containing this disk image
 *			top  - root of the memory tree we're working on
 *			type - what kind of block this is
 *
 *	EXIT		Returns non-zero if references should be deleted,
 *			  zero if structure is recoverable
 *			Tree built under "mem"
 *
 *	CALLS		clr_bit
 *			chk_gather_structure (recursive)
 *			chk_get_child
 *			use_sectors
 *
 *	EFFECTS		Allocates memory
 */

/*
 * The following macro equals the number of bytes used in each call to
 * chk_gather_structure, used by the stack checking routines.  If any local
 * variables or parameters are added, this macro should be updated.
 */

#define GS_STACK 44

USHORT chk_gather_structure (lsn, mem, top, type)
	ULONG lsn;
	 mem_blk *mem;
	 mem_blk *top;
	USHORT type;
{
    register USHORT i, j;
    USHORT rc;
    ULONG l;
     mem_blk *p;
    union dp dp;
 PALLEAF leaf;
    PULONG ptr;

    if (chk_enter_recursive (GS_STACK)) {
	chk_exit_recursive (GS_STACK);
	return (FALSE);
    }

#ifdef TRACE
    fprintf (stderr, "chk_gather_structure (%lx, [buffer], [top], TYPE_%s)\n",
		    lsn, chk_vstypes [type]);
    fflush (stderr);
#endif

    rc = FALSE;				/* assume a good block */
    mem->psn = lsn;			/* save disk address in mem structure */

    /*
     * Process an FNODE -- for a file or a subdirectory.
     */


    if ((type == TYPE_FNODE) || (type == TYPE_SUBDIR)) {

	/* this value indicates the number of sectors
	 * lost because ALLEAF or ALNODE records had
	 * to be deleted because of cross-links. This
	 * value is subtracted from the LOF field of
	 * subsequent records to make them correct
	 */
	chk_lof_offset = 0L;

	/*
	 * Mark the FNODE in the disk map and bit map.
	 * If chk_deleted_this_pass is true, we just check for
	 * cross-links; otherwise, we mark it as known in the
	 * disk map.
	 */

	if (chk_deleted_this_pass)
	    i = chk_sector_usage (lsn, (ULONG)SECTORS_PER_FNODE, DISK_KNOWN);
	else
	    i = use_sectors (lsn,
			     (ULONG)SECTORS_PER_FNODE,
			     (type == TYPE_SUBDIR) ? DISK_SUBDIR : DISK_FNODE);


	/*
	 *  If the FNODE is cross-linked, tell the caller
	 *  to delete the reference.
	 */
	if (i > DISK_KNOWN)
		return (TRUE);

	/*
	 * If it's a subdirectory, just gather its DIRBLK.  If we've
	 * already found that DIRBLK, indicate that we need a child
	 * and don't bother looking at it.
	 */

	leaf = mem->blk.f.fst.a.aall;

	if ((type == TYPE_SUBDIR) || (mem->blk.f.bFlag & FNF_DIR)) {

	    if (chk_sector_usage (leaf->lsnPhys,
				  (ULONG)SECTORS_PER_DIRBLK,
				  (UCHAR)(chk_deleted_this_pass ?
					DISK_KNOWN : DISK_UNKNOWN))) {

		return (!chk_lookup_child (mem, leaf->lsnPhys, DBSIGVAL));

	    } else if ((p=chk_get_child (leaf->lsnPhys,
				       mem,
				       top,
				       TYPE_DIRBLK)) != NULL) {

		if ((p->blk.d.sig != DBSIGVAL) ||
		     chk_gather_structure (leaf->lsnPhys, p, top, TYPE_DIRBLK)) {

		    /*
		     *	Remove the child from the recovery tree.  If
		     *	it not a DIRBLK, zero out it's psn to protect
		     *	it from being deallocated.  (Otherwise, it's
		     *	a bad DIRBLK and needs to be wiped out.)
		     *	Inform the caller that this FNODE is bad.
		     */
		    if ( p->blk.d.sig != DBSIGVAL ) {
			p->psn = 0;
		    }
		    chk_kill_child (p, mem, SECTORS_PER_DIRBLK);
		    rc = TRUE;
		}
	    }
	    else
		rc = TRUE;
	}

	/*
	 * We've got a file.  If the pointers point to runs, allocate them
	 * in the disk map and bitmap;	otherwise, recursively descend the
	 * allocation tree.
	 */

	else if (!(mem->blk.f.fst.alb.bFlag & ABF_NODE)) {

	    /*
	     *	The entries in the FNODE are leaves.  Make sure
	     *	that fst.alb.cUsed is reasonable.
	     */
	    if ( (mem->blk.f.fst.alb.cUsed == 0) ||
		 (mem->blk.f.fst.alb.cUsed > LEAFPERFNODE) ) {

		/*
		 *  This is either the FNODE for a zero-length file,
		 *  or it is badly corrupted.  In either case, we
		 *  do not want to try to recover it.
		 */
		rc = TRUE;

	    } else {
		for (i=0; i<mem->blk.f.fst.alb.cUsed; ) {

		    /* adjust LOF for deleted sectors  */
		    leaf->lsnLog -= chk_lof_offset;

		    if (chk_deleted_this_pass)
			j = chk_sector_usage (leaf->lsnPhys,
					      leaf->csecRun,
					      DISK_KNOWN);
		    else
			j = use_sectors (leaf->lsnPhys, leaf->csecRun, DISK_DATA);

		    if ((j > DISK_KNOWN) ||
			(leaf->lsnPhys >= number_of_sectors) ||
			(leaf->lsnPhys + leaf->csecRun > number_of_sectors)) {

			/* inc offset for deleted sectors  */
			chk_lof_offset += leaf->csecRun;

			if (i != mem->blk.f.fst.alb.cUsed-1) {

			    memmove (leaf,
				     leaf+1,
				     (mem->blk.f.fst.alb.cUsed - i - 1)
						* sizeof ( ALLEAF));

			}
			if (!(--mem->blk.f.fst.alb.cUsed)) {

			    /*	Killed the last run;  tell caller
			     *	the FNODE's bad.
			     */
			    rc = TRUE;
			}
			else {
			    mem->blk.f.fst.alb.cFree++;
			    mem->blk.f.fst.alb.oFree -= sizeof ( ALLEAF);
			}
		    }
		    else {
			i++;
			leaf++;
		    }
		} /* for */
	    } /*  if fst.alb.cUsed is reasonable  */
	}

	/*
	 * Treat the pointers as B+TREE pointers.
	 */

	else {

	    if ((mem->blk.f.fst.alb.cUsed == 0 ) ||
		(mem->blk.f.fst.alb.cUsed > NODEPERFNODE) ){

		/*
		 *  This FNODE is not consistent, so we will
		 *  not take the risk of recovering it.
		 */
		rc = TRUE;

	    } else {
		ptr = &mem->blk.f.fst.a.aall [0].lsnLog;
		for (i=0; i<mem->blk.f.fst.alb.cUsed; ) {

		    if (chk_sector_usage (*(ptr+1),
					  (ULONG)SECTORS_PER_AB,
					  (UCHAR)(chk_deleted_this_pass ?
					       DISK_KNOWN : DISK_UNKNOWN))) {

			if (chk_lookup_child (mem, *(ptr+1), ABSIGVAL)) {

			    /*	child is good--advance to next.  */
			    ptr += 2;
			    i++;
			}
			else {

			    /*
			     * The child is crosslinked--delete reference.
			     *
			     * inc offset for
			     * sectors lost.Only subtract
			     * previous LOF value if this
			     * is not the first ALNODE
			     */
			    chk_lof_offset += *ptr - (i ? *(ptr-2) : 0);

			    /* remove reference to it  */
			    if (i != mem->blk.f.fst.alb.cUsed-1) {

				memmove (ptr,
					 ptr+2,
					 (mem->blk.f.fst.alb.cUsed - i - 1)
						* sizeof (ALNODE));

			    }

			    if (!(--mem->blk.f.fst.alb.cUsed)) {

				/*  Killed last run;  tell caller
				 *  FNODE's bad.
				 */
				rc = TRUE;
			    }
			    else {

				mem->blk.f.fst.alb.cFree++;
				mem->blk.f.fst.alb.oFree -= sizeof (ALNODE);
			    }
			}
		    }
		    else if ((p=chk_get_child (*(ptr+1), mem, top, TYPE_AB))!=NULL){

			if ((p->blk.a.sig != ABSIGVAL) ||
			    chk_gather_structure (*(ptr+1), p, top, TYPE_AB)) {

			    /*
			     *	Remove the child from the recovery tree.
			     *	If it was rejected because of signature
			     *	mismatch, zero out its psn to protect it
			     *	from being deallocated.  If it's a bad
			     *	allocation sector (the other possibility)
			     *	it should be deallocated.
			     */
			    if ( p->blk.a.sig != ABSIGVAL ) {
				p->psn = 0;
			    }
			    chk_kill_child (p, mem, SECTORS_PER_AB);

			    /*
			     * inc offset for
			     * sectors lost.Only subtract
			     * previous LOF value if this
			     * is not the first ALNODE
			     */
			    chk_lof_offset += *ptr - (i ? *(ptr-2) : 0);

			    /* remove reference to it  */
			    if (i != mem->blk.f.fst.alb.cUsed-1) {

				memmove (ptr,
					 ptr+2,
					 (mem->blk.f.fst.alb.cUsed - i - 1)
						* sizeof (ALNODE));

			    }

			    if (!(--mem->blk.f.fst.alb.cUsed)) {

				/*  Killed last run;  tell caller
				 *  FNODE's bad.
				 */
				rc = TRUE;
			    }
			    else {

				mem->blk.f.fst.alb.cFree++;
				mem->blk.f.fst.alb.oFree -= sizeof (ALNODE);
			    }
			}
			else {

			    /*	Adjust LOF for lost sectors
			     *	because cross-link run was removed.
			     */
			    if (*ptr != 0xffffffff)
				*ptr -= chk_lof_offset;

			    ptr += 2;
			    i++;

			}
		    }
		    else {
			ptr += 2;
			i++;
		    }
		} /* for */
	    } /* if fst.alb.cUsed is reasonable */
	} /* if no ABF_NODE */
    } /* if TYPE_FNODE or TYPE_SUBDIR */

    /*
     * Process a directory block.
     */

    else if (type == TYPE_DIRBLK) {

	/*
	 * Mark the DIRBLK in the disk map and bitmap.
	 *  If chk_deleted_this_pass is true, we just check for
	 *  cross-links; otherwise, we want to mark it in
	 *  the disk map.
	 */

	if (chk_deleted_this_pass)
	    i = chk_sector_usage (lsn, (ULONG)SECTORS_PER_DIRBLK, DISK_KNOWN);
	else
	    i = use_sectors (lsn, (ULONG)SECTORS_PER_DIRBLK, DISK_DIRBLK);


	/*
	 *  If the sector is cross-linked, remove it.
	 */
	if (i > DISK_KNOWN)
		return (TRUE);

	/*
	 * Process all entries in this DIRBLK -- files, subdirectories, and
	 * child directory blocks.
	 */

	dp.p = DIR_START (mem->blk.d);		/* point to first record */

	while (TRUE) {				/* break out manually */

	    /*
	     * we must check that the cchThisEntry field is valid
	     * and fix it if possible because we need it to get
	     * to the next DIRENT and find the DIR_BTP if there
	     * is one.
	     */

	    if (chk_bad_DIR_ELEN (&mem->blk, *((PuDIRENT)&dp)))
		    chk_truncate_dirblk (lsn, &mem->blk, *((PuDIRENT)&dp));

	    if (dp.d->fFlags & DF_BTP) {

		/*  This entry has a down pointer;  fetch it.  */
		l = DOWN_PTR (dp);
		if (chk_sector_usage (l,
				      (ULONG)SECTORS_PER_DIRBLK,
				      (UCHAR)(chk_deleted_this_pass ?
						DISK_KNOWN : DISK_UNKNOWN))) {

		    if (!chk_lookup_child (mem, l, DBSIGVAL)) {

			/*
			 *  Did not find child in list of recovered
			 *  objects--remove the down pointer.
			 */
			dp.d->fFlags &= ~DF_BTP;
			memmove (dp.p + dp.d->cchThisEntry - sizeof (ULONG),
				 dp.p + dp.d->cchThisEntry,
				 ((PUCHAR)&(mem->blk.d) + DIRBLK_SIZE)
					- dp.p - dp.d->cchThisEntry);

			dp.d->cchThisEntry -= sizeof (ULONG);
			mem->blk.d.offulFirstFree -= sizeof (ULONG);
		    }
		}
		else if ((p=chk_get_child (l, mem, top, TYPE_DIRBLK)) != NULL) {

		    if ((p->blk.d.sig != DBSIGVAL) ||
			chk_gather_structure (l, p, top, TYPE_DIRBLK)) {

			/*
			 *  We have to remove the child from the
			 *  recovery tree.  However, if it is rejected
			 *  because of a non-matching signature, we
			 *  don't want to smear it, so we'll zero the
			 *  psn.
			 */
			if( p->blk.d.sig != DBSIGVAL ) {
			    p->psn = 0;
			}

			chk_kill_child (p, mem, SECTORS_PER_DIRBLK);

			/*
			 *  Since the downpointer is bad, we'll remove
			 *  it.  This entails wiping out the flag bit,
			 *  shifting everything after this entry up
			 *  four bytes, changing the length of this
			 *  entry and updating the offset of first free.
			 *  Since there is no guarantee that the dirblk's
			 * offulFirstFree field is correct, more the whole
			 *  remainder of the dirblk (used or not).
			 */
			dp.d->fFlags &= ~DF_BTP;
			memmove (dp.p + dp.d->cchThisEntry - sizeof (ULONG),
				 dp.p + dp.d->cchThisEntry,
				 ((PUCHAR)&(mem->blk.d) + DIRBLK_SIZE)
					- dp.p - dp.d->cchThisEntry);

			dp.d->cchThisEntry -= sizeof (ULONG);
			mem->blk.d.offulFirstFree -= sizeof (ULONG);
		    }
		}
	    } /* DF_BTP */

	    if ((dp.d->fFlags & DF_END) ||
		(dp.p - (PUCHAR)&mem->blk > (USHORT)mem->blk.d.offulFirstFree) ||
		(!dp.d->cchThisEntry)) {

		/*
		 *  This is the end entry, or we're in a corrupt 
		 *  DIRBLK, so we stop.
		 */
		    break;
	    }

	    j = (dp.d->fAttr & ATTR_DIRECTORY) ?
				    TYPE_SUBDIR : TYPE_FNODE;

	    if (!memcmp (dp.d->bName, "\001\001", 2)) {

		/*  It's the '..' entry--skip it.  */
		GETNEXT_ENTRY (dp);
		continue;
	    }

	    /*
	     * At this point we are checking the individual fields of a
	     * DIRENT.
	     */

	    if (!chk_check_name (dp.d->bName,
				 &dp.d->bCodePage,
				 dp.d->cchName))  {

		/*
		 *  The name is invalid:  remove this dirent.
		 *  chk_remove_dirent will advance the dp
		 *  ptr to the next dirent (if needed)
		 */
		chk_remove_dirent (&dp, &mem);

	    } else if (chk_sector_usage (dp.d->lsnFNode,
					 (ULONG)SECTORS_PER_FNODE,
					 (UCHAR)(chk_deleted_this_pass ?
						DISK_KNOWN:DISK_UNKNOWN)) ||

		       (dp.d->fFlags & DF_SPEC) ) {

		/*
		 *  Either the child has already been found, or
		 *  this is a cross link.  Check the list of
		 *  recovered objects.
		 */
		if (!chk_lookup_child (mem, dp.d->lsnFNode, FNSIGVAL))
		    chk_remove_dirent (&dp, &mem);
		else
		    GETNEXT_ENTRY (dp);

	    }
	    else if ((p=chk_get_child (dp.d->lsnFNode, mem, top, j)) != NULL) {

		if ((p->blk.f.sig != FNSIGVAL) ||
		    chk_gather_structure (dp.d->lsnFNode, p, top, j)) {

		    /*
		     *	The FNODE is bad; remove this dirent.
		     *	Note that chk_remove_dirent will advance
		     *	dp to the next entry.
		     *
		     *	If the child was rejected because of
		     *	a non-matching signature, we zero out
		     *	its psn so that it won't get deallocated.
		     *	(It might be a perfectly innocent block
		     *	indicted by a malicious dirent.)
		     *	If it is an FNODE, it should be deallocated,
		     *	since it's a bad FNODE.
		     */
		    if ( p->blk.f.sig != FNSIGVAL ) {
			p->psn = 0;
		    }
		    chk_kill_child (p, mem, SECTORS_PER_FNODE);
		    chk_remove_dirent (&dp, &mem);

		}
		else {
		    if (p->blk.f.fst.a.aall[0].lsnPhys &&
			!p->blk.f.fst.a.aall[0].csecRun &&
			!(dp.d->fAttr & ATTR_DIRECTORY))
			dp.d->fAttr |= ATTR_DIRECTORY;

		    GETNEXT_ENTRY (dp);
		}
	    }
	    else
		    GETNEXT_ENTRY (dp);			   /* move to next entry */


	    if ((dp.p == DIR_START (mem->blk.d)) &&
		((dp.d->fFlags & (DF_END | DF_BTP)) == DF_END))

		    /*
		     *	The only entry left in the DIRBLK is the
		     *	END entry, and it has no downpointer;
		     *	this DIRBLK is useless and should be removed.
		     *	Tell caller to punt.
		     */
		    rc = TRUE;

	} /* while DIRENTs to process */

	if ((ULONG)(dp.p + dp.d->cchThisEntry - (PCHAR)&mem->blk.d) !=
	    mem->blk.d.offulFirstFree)
	    mem->blk.d.offulFirstFree = (ULONG)(dp.p + dp.d->cchThisEntry -
						(PCHAR)&mem->blk.d);

    } /* TYPE_DIRBLK */

    /*
     * Process an indirect block.
     */

    else {

	/*
	 * Mark the allocation block in the disk map and bitmap.
	 * If chk_deleted_this_pass is true, we just check the
	 * links.
	 */

	if (chk_deleted_this_pass)
		i = chk_sector_usage (lsn, (ULONG)SECTORS_PER_AB, DISK_KNOWN);
	else
		i = use_sectors (lsn, (ULONG)SECTORS_PER_AB, DISK_AB);

	/*  If it's crosslinked, it should be removed.  */
	if (i > DISK_KNOWN)
	    return (TRUE);

	if (!(mem->blk.a.alb.bFlag & ABF_NODE)) {

	    /*
	     *	This is a leaf.  The pointers point directly
	     *	to data runs.
	     *
	     *	Set up a pointer to the first record, and then
	     *	verify and recover each of the records.
	     */
	    leaf = ( ALLEAF *)AB_START (&mem->blk);

	    if ( (mem->blk.a.alb.cUsed == 0) ||
		 (mem->blk.a.alb.cUsed > LEAFPERSEC) ) {

		/*
		 *  The count of used fields is not reasonable--
		 *  it is either zero or too large.  Inform the
		 *  caller that this ALSEC is bad.
		 */
		rc = TRUE;

	    } else {

		for (i=0; i<mem->blk.a.alb.cUsed; ) {

		    /*	Adjust running LOF offset  */
		    leaf->lsnLog -= chk_lof_offset;

		    if (chk_deleted_this_pass)
			    j = chk_sector_usage (leaf->lsnPhys,
						  leaf->csecRun,
						  DISK_KNOWN);
		    else
			    j = use_sectors (leaf->lsnPhys,
					     leaf->csecRun,
					     DISK_DATA);

		    if ((j > DISK_KNOWN) ||
			(leaf->lsnPhys >= number_of_sectors) ||
			(leaf->lsnPhys + leaf->csecRun > number_of_sectors)) {

			/*  Increment the running LOF offset  */
			chk_lof_offset += leaf->csecRun;

			if (i != mem->blk.a.alb.cUsed-1) {
			    memmove (leaf,
				     leaf+1,
				     (mem->blk.a.alb.cUsed - i - 1)
					    * sizeof (ALLEAF));
			}
			if (!(--mem->blk.a.alb.cUsed)) {

			    /*
			     *	We killed the last run;	 tell
			     *	caller the ALSEC's bad, and
			     *	stop processing this block.
			     */
				rc = TRUE;
				break;
			} else {

			    mem->blk.a.alb.cFree++;
			    mem->blk.a.alb.oFree -= sizeof (ALLEAF);
			}
		    }
		    else {
			leaf++;
			i++;
		    }
		} /* for */
	    }  /*  OCNT is reasonable  */
	} /* if not ABF_NODE */
	else {

	    /*
	     *	This is a node.  The entries are
	     *	BTree downpointers.
	     *
	     *	Set up a pointer to the first record, and then
	     *	verify and recover each of the records.
	     */
	    ptr = AB_START (&mem->blk);

	    if ( (mem->blk.a.alb.cUsed == 0) ||
		 (mem->blk.a.alb.cUsed > NODEPERSEC) ) {

		/*
		 *  The count of used fields is not reasonable--
		 *  it is either zero or too large.  Inform the
		 *  caller that this ALSEC is bad.
		 */
		rc = TRUE;

	    } else {

		for (i=0; i<mem->blk.a.alb.cUsed; ) {

		    /* fetch rec address */
		    l = *(ptr+1);

		    if (chk_sector_usage (l,
					  (ULONG)SECTORS_PER_AB,
					  (UCHAR)(chk_deleted_this_pass ?
						DISK_KNOWN:DISK_UNKNOWN))) {

			if (chk_lookup_child (mem, l, ABSIGVAL)) {

			    /*	child is good--advance to next.  */
			    ptr += 2;
			    i++;
			}
			else {

			    /* child crosslinked, delete ref.
			     *
			     * inc offset for
			     * sectors lost.Only subtract
			     * previous LOF value if this
			     * is not the first ALNODE
			     */
			    chk_lof_offset += *ptr - (i ? *(ptr-2) : 0);

			    /* remove reference to it  */
			    if (i != mem->blk.a.alb.cUsed-1) {

				memmove (ptr,
					 ptr+2,
					 (mem->blk.a.alb.cUsed - i - 1)
						* sizeof (ALNODE));

			    }

			    if (!(--mem->blk.a.alb.cUsed)) {

				/*  Killed last run;  tell caller
				 *  ALSEC's bad.
				 */
				rc = TRUE;
			    }
			    else {

				mem->blk.a.alb.cFree++;
				mem->blk.a.alb.oFree -= sizeof (ALNODE);
			    }
			}
		    }
		    else if ((p=chk_get_child (l, mem, top, TYPE_AB)) != NULL) {

			if ((p->blk.a.sig != ABSIGVAL) ||
			    chk_gather_structure (l, p, top, TYPE_AB)) {

			    /*
			     *	Remove the child from the recovery tree.
			     *	If it was rejected because of an incorrect
			     *	signature, zero out its psn so it won't
			     *	be deallocated.
			     */
			    if ( p->blk.a.sig != ABSIGVAL ) {
				p->psn = 0;
			    }
			    chk_kill_child (p, mem, SECTORS_PER_AB);

			    /*
			     * inc offset for
			     * sectors lost.Only subtract
			     * previous LOF value if this
			     * is not the first ALNODE
			     */
			    chk_lof_offset += *ptr - (i ? *(ptr-2) : 0);

			    if (i != mem->blk.a.alb.cUsed-1) {

				memmove (ptr,
					 ptr+2,
					 (mem->blk.a.alb.cUsed - i - 1)
						* sizeof (ALNODE));
			    }

			    if (!(--mem->blk.a.alb.cUsed)){/* killed last run */

				/*
				 *  We killed the last run;  tell
				 *  caller it's a bad ALSEC and
				 *  break out.
				 */
				rc = TRUE;
				break;
			    }
			    else {

				mem->blk.a.alb.cFree++;
				mem->blk.a.alb.oFree -= sizeof (ALNODE);
			    }
			}
			else {

			    /*	Adjust LOF to reflect removal
			     *	of cross-linked runs.
			     */

			    if (*ptr != 0xffffffff)
				*ptr -= chk_lof_offset;

			    ptr += 2;
			    i++;
			}
		    }
		    else {

			/* node rec is 2 dwords */
			ptr += 2;
			i++;
		    }
		} /* for */
	    } /* OCNT is reasonable */
	} /* if ABF_NODE */

    } /* TYPE_AB */

    chk_exit_recursive (GS_STACK);
    return (rc);

} /* chk_gather_structure */
