/*static char *SCCSID = "@(#)chkstruc.c	6.3 92/02/19";*/
/*static char *SCCSID = "@(#)chkstruc.c	6.3 92/02/19";*/
/***	CHKSTRUC.C - chk_validate_structure routine to validate different
 *						disk objects
 *
 *	CHKDSK
 *	Peter A. Williams
 *	Copyright 1988 Microsoft Corporation
 *
 *	Modification history:
 *	P.A. Williams	07/12/89	Chk_validate_structure was taken out
 *					of check.c and put in this new file.
 *	P.A. Williams	08/01/89	Added checking of ulVLen parameter
 *					using new function chk_check_file_size.
 *	P.A. Williams	08/03/89	Added new function chk_check_end_dirent
 *					to check and fix the end dirent.
 *
 *	W.P. McJohn	10/03/89	Add check of NEW NAMES bit in DIRENT
 *
 */

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


#ifdef TESTING
UCHAR ch='\0', chr='\0';
#endif

#ifdef ALIGN
void align_fnode (ULONG lsn,  FNODE *nf)
{
  register  OLDFNODE *f;
  USHORT i;

  f = ALLOCATE_BLOCK (SECTORS_PER_FNODE);
  memcpy (f, nf, sizeof ( FNODE));

  nf->sig = FNSIGVAL;
  nf->ulSRHist = f->ulSRHist;
  nf->ulFRHist = f->ulFRHist;
  memcpy (nf->achName, f->achName, sizeof (f->achName));
  nf->lsnContDir = f->lsnContDir;
  nf->aiACL = f->aiACL;
  nf->cHistBits = f->cHistBits;
  nf->aiEA = f->aiEA;
  nf->bFlag = f->bFlag;
  nf->fst.alb = f->fst.alb;
  for (i=0; i<LEAFPERFNODE; i++)
    nf->fst.a.aall [i] = f->fst.a.aall [i];
  nf->fst.ulVLen = f->fst.ulVLen;
  nf->ulRefCount = f->ulRefCount;
  memcpy (nf->achUID, f->achUID, sizeof (f->achUID));
  nf->usACLBase =  f->usACLBase +
		   (SHORT)(FIELDOFFSET ( FNODE, abFree [0]) -
			   FIELDOFFSET ( OLDFNODE, abFree [0]));
  memcpy (nf->abFree, f->abFree, sizeof (f->abFree));
  FREE_BLOCK (f);
  /*  always save, even level 0	 */
  write_scratch (lsn, nf, SECTORS_PER_FNODE);
}
#endif

/*
 ***    chk_TFA -- truncate file allocation
 *
 *      Truncates the allocation for a file to csec sectors.
 *
 *      ENTRY   lsn -- lsn of FNode or Allocation Sector to truncate
 *              pblk -- pointer to FNode or Allocation Sector
 *              csec -- new file size.
 *
 *      RETURNS TRUE if successful (and disk changed) else FALSE.
 */
BOOL
chk_TFA( ULONG lsn, PBLK pblk, ULONG csec ) {

    PBLK pblkChild;
    ALBLK *palb;
    ALLEAF *pall;
    ALNODE *paln;
    USHORT i;
    BOOL usRet, fFNode;

    if( !chk_delete_inconsistency )
        return FALSE;


    /*
     *  Find the ALBLK.  If the block is neither an
     *  FNODE nor an ALSEC, bomb out.
     */
              
    if( pblk->f.sig == FNSIGVAL ) {

        fFNode = TRUE;
        palb = &(pblk->f.fst.alb);
                       
    } else if ( pblk->a.sig == ABSIGVAL ) {

        fFNode = FALSE;
        palb = &(pblk->a.alb);                      //PCR R205 56479

    } else {

        return FALSE;
    }

    if( csec == 0 ) {

        /*
         *  Truncate the entire file.  Note that any children
         *  will be orphaned.
         */
        palb->bFlag &= ~ABF_NODE;

        if( fFNode ) {
                          
            palb->cFree = LEAFPERFNODE;

        } else {

            palb->cFree = LEAFPERSEC;
        }                    
        palb->oFree -= palb->cUsed * sizeof( *pall );
        palb->cUsed = 0;

        if( fFNode )
            write_scratch( lsn, pblk, SECTORS_PER_FNODE );
        else
            write_scratch( lsn, pblk, SECTORS_PER_AB );

    }

    if( !(palb->bFlag & ABF_NODE) ){

        /*
         *  The ALBLK is followed by an array of leaves.  Find
         *  the truncation point, and adjust the count of used
         *  and free leaves accordingly.  (The run length of the
         *  last leaf may also need to be truncated.)
         *
         */
        pall = (ALLEAF *)((PBYTE)palb + sizeof(*palb));

        i = 0;                               
        while( i < palb->cUsed && csec > pall->lsnLog ) {

            i += 1;
            pall += 1;
        }

        /*
         *  pall now points at the leaf after the truncation point;
         *  i is the new count of used leaves.
         */
        pall -= 1;
              
        pall->csecRun = csec - pall->lsnLog;
        palb->cFree += (palb->cUsed - i);
        palb->oFree -= (palb->cUsed - i) * sizeof(*pall);
        palb->cUsed = (unsigned char) i;

        /*  BUGBUG -- should it mark the remaining sectors free?  */

        /*  write the sector out  */
        if( fFNode )
            write_scratch( lsn, pblk, SECTORS_PER_FNODE );
        else
            write_scratch( lsn, pblk, SECTORS_PER_AB );

    } else {

        /*
         *  This is an allocation node.
         */
        paln = (ALNODE *)((PBYTE)palb + sizeof(*palb));
        i = 0;
                                                
        while ( i < palb->cUsed && csec > paln->lsnLog ) {

            i+= 1;
            paln += 1;
        }

        /*
         *  paln now points at the last node to keep, and i is
         *  one less than the number of nodes to keep.
         */
        i += 1;

        if( paln->lsnLog != csec ) {

            /*
             *  Our truncation point does not lie on a node boundary,
             *  so we'll need to visit the child.
             */

            pblkChild = (PBLK)ALLOCATE_BLOCK( SECTORS_PER_FNODE );
                                   
            if( read_scratch(paln->lsnPhys, pblkChild,
                                 SECTORS_PER_AB) ){

                usRet = FALSE;

            } else {

                usRet = chk_TFA( paln->lsnPhys, pblkChild, csec );
            }

            FREE_BLOCK( pblkChild );

            if (!usRet)
                return usRet;
        }


        paln->lsnLog = 0xffffffffL;

        palb->cFree += (palb->cUsed - i);
        palb->oFree -= (palb->cUsed - i) * sizeof(*paln);
        palb->cUsed = (unsigned char) i;

        /*  write the sector out  */
        if( fFNode )
            write_scratch( lsn, pblk, SECTORS_PER_FNODE );
        else
            write_scratch( lsn, pblk, SECTORS_PER_AB );
    }

    return TRUE;
}


/***	chk_check_file_size - check and fix an incorrect cchFSize or ulVLen
 *
 *
 *	This function is called to spot-fix the cchFSize field of a
 *	directory entry.  It is passed a memory image of the DIRBLK,
 *	the entry to be updated, and the correct file size.  The memory
 *	image will always be updated;  if command line parameters allow,
 *	the DIRBLK will be written back to the disk.
 *
 *	chk_fix_size (mem, entry, size)
 *
 *	ENTRY		mem   - memory image of the DIRBLK to be fixed
 *			entry - pointer to the entry to be fixed
 *			size  - correct file size
 *
 *	EXIT		No return value
 *
 *	CALLS		chk_no_writing
 *			write_scratch
 *
 *	WARNINGS	Incorrect cchFSize may confuse the file system
 *
 *	EFFECTS		May update disk
 */
void chk_check_file_size (mem, dirent, fnode)
	PBLK mem;
 PDIRENT dirent;
 PFNODE fnode;
{
    USHORT len;

    /*	BUGBUG - could we optimize this check?  */

    if ( (chk_accumsize < fnode->fst.ulVLen) ||
         (chk_accumsize > fnode->fst.ulVLen + SECSIZE - 1) ||
         (dirent->cchFSize != fnode->fst.ulVLen)) {

       strcpy (scratchbuf, chk_curpath);
       len = strlen (scratchbuf);
       memcpy (scratchbuf + len, dirent->bName, dirent->cchName);
       scratchbuf [len + dirent->cchName] = '\0';

       /*  fix the incorrect fields  */

        if ( chk_accumsize < fnode->fst.ulVLen) {

            /*  bad ulVLen */
            fnode->fst.ulVLen = chk_accumsize;
            if (!chk_recovering &&
                !chk_no_writing (CHK_WRONG_SIZE_STR, &scratchptr)) {

                    write_scratch (dirent->lsnFNode, fnode, SECTORS_PER_FNODE);
            }

        } else if( chk_accumsize > fnode->fst.ulVLen + SECSIZE - 1) {


            /*
             *  Truncate allocation.
             */
            if( !chk_no_writing(CHK_WRONG_SIZE_STR, &scratchptr) ) {

                chk_TFA( dirent->lsnFNode, (PBLK) fnode,
                         (ULONG)(fnode->fst.ulVLen + SECSIZE - 1)/
                            (ULONG)SECSIZE );
            }

        }

        if (dirent->cchFSize != fnode->fst.ulVLen) {

            /*
	         * bad ulVLen (note: we recheck
	         * actual value because we could
	         * have fixed cchFSize
	         */
	         dirent->cchFSize = fnode->fst.ulVLen;
	         if (!chk_recovering &&
		        !chk_no_writing (CHK_WRONG_SIZE_STR, &scratchptr)) {

		          write_scratch (mem->d.lsnThisDir, mem, SECTORS_PER_DIRBLK);
	         }
	     }
    }
}

/***	chk_create_end_DIRENT - create an end DIRENT in the given memory.
 *
 *	This function is called to store the pathname for an inconsistent
 *	file, so that it can be deleted later.	The file cannot be deleted
 *	during the checking phase, since BTREE reshuffling would cause us
 *	to lose our place -- we'd check some files twice, some not at all,
 *	etc.
 *
 *	xx ()
 *
 *	ENTRY		path - directory the name is in
 *			dp   - pointer to DIRENT containing name to delete
 *
 *	EXIT		No return value
 *
 *	CALLS		malloc
 *			realloc
 *
 *	WARNINGS	If insufficient memory, the name will not be added
 *			to the list.  It will thus not be deleted during
 *			the deletion phase;  the user can still run CHKDSK
 *			again to catch more files the second time around.
 *
 *	EFFECTS		Allocates memory
 */

void chk_create_end_DIRENT (dirent)
uDIRENT dirent;
{
#ifdef TRACE
   fprintf (stderr, "chk_create_end_DIRENT ()\n");
   fflush (stderr);
#endif

   /*
	* Create an end DIRENT. Memset the DIRENT to zeroes and then set
	* all non-zero fields.
	*/

   memset (dirent.p, '\0', sizeof (DIRENT));
   dirent.d->cchThisEntry = sizeof (DIRENT);
   dirent.d->fFlags  = DF_END;
   dirent.d->fAttr   = 0;
   dirent.d->cchName = 1;
   dirent.d->bName[0] = 0xff;

}


/***	chk_truncate_dirblk - truncate a DIRBLK at the given DIRENT
 *
 *	This function is called to store the pathname for an inconsistent
 *	file, so that it can be deleted later.	The file cannot be deleted
 *	during the checking phase, since BTREE reshuffling would cause us
 *	to lose our place -- we'd check some files twice, some not at all,
 *	etc.
 *
 *	xx ()
 *
 *	ENTRY		path - directory the name is in
 *			dp   - pointer to DIRENT containing name to delete
 *
 *	EXIT		No return value
 *
 *	CALLS		malloc
 *			realloc
 *
 *	WARNINGS	If insufficient memory, the name will not be added
 *			to the list.  It will thus not be deleted during
 *			the deletion phase;  the user can still run CHKDSK
 *			again to catch more files the second time around.
 *
 *	EFFECTS		Allocates memory
 */
void chk_truncate_dirblk (lsn, dirblk, dirent)
ULONG lsn;
PBLK dirblk;
uDIRENT dirent;
{
/* BUGBUG - we have to ensure we have enough room to force an end entry. */

#ifdef TRACE
   fprintf (stderr, "chk_truncate_dirblk ()\n");
   fflush (stderr);
#endif

   /*
    * Truncation of the DIRBLK is done by replacing the given
    * DIRENT with an end DIRENT.
    */

   chk_create_end_DIRENT (dirent);

   /*
    *  The new offset of first free is offset to last DIRENT plus
    *  length of that DIRENT.
    */
   dirblk->d.offulFirstFree = dirent.p - (PUCHAR)dirblk + dirent.d->cchThisEntry;

   if (chk_delete_inconsistency)
	/*  Save the new DIRBLK	 */
	write_scratch (lsn, dirblk, SECTORS_PER_DIRBLK);

}


/***	chk_bad_DIR_ELEN - make sure cchThisEntry field valid for DIRENT
 *
 *	This function checks the validity of the cchThisEntry field by
 *	calculating what it should be from cchName and by doing some
 *	general sanity checks.
 *
 *	chk_bad_DIRE_ELEN ()
 *
 *	ENTRY		dirblk - a ptr to the dirblk the dirent is within.
 *				dirent - a ptr to the dirent to check.
 *
 *	EXIT		TRUE if cchThisEntry is bad, FALSE otherwise.
 *
 *	CALLS		None
 *
 *	WARNINGS	This check is very stringent. Since cchThisEntry is checked
 *				by using the cchName value, if cchName is bad,
 * cchThisEntry will be deemed bad.
 *
 *	EFFECTS		None
 */
USHORT chk_bad_DIR_ELEN (dirblk, dirent)
	PBLK dirblk;
	uDIRENT dirent;
{
	USHORT dir_elen;

/*
 *  Maximum DIRENT length is sum of:
 *	sizeof dirent
 *	max. name length
 *	down pointer
 *	aces
 */
#define MAX_DIRENT_LENGTH (LONG_ALIGN(sizeof(DIRENT) + \
		MAX_NAME_LENGTH - 1 + sizeof(ULONG) + 3 * sizeof (ULONG)))

#define MAX_NAME_LENGTH 254
#define LONG_ALIGN(Expression) (((Expression) + 3) & ~3)
#define DF_BTP_SET(pDirent) (pDirent->fFlags & DF_BTP)
#define DF_ACL_SET(pDirent) (pDirent->fFlags & DF_ACL)

    /*
     * We make three checks to determine if the cchThisEntry field is bad:
     *
     * 1) cchThisEntry is not correct from calculation using cchName, DF_ACL
     *	  and DF_BTP.
     * 2) cchThisEntry is greater than the maximum possible value.
     * 3) cchThisEntry isn't smaller than the minimum possible value.
     * 4) cchThisEntry implies the DIRENT extends past the end of the DIRBLK.
     */

     dir_elen = LONG_ALIGN(sizeof(DIRENT) + dirent.d->cchName - 1);

     if (DF_BTP_SET(dirent.d)) {

	    /*	Take down ptr into account  */
	    dir_elen += sizeof(ULONG);
     }

     if (DF_ACL_SET(dirent.d)) {

	    /*	take acls into account	*/
	    dir_elen +=	(dirent.d->fFlex & 3) * sizeof (ULONG);
     }
    else if (dirent.d->fFlex & 3) {
        if (dirent.d->cchThisEntry == dir_elen) {           /* length otherwise OK */
            dirent.d->fFlex &= ~3;                   /* just kill the ACE count */
            write_scratch (dirblk->d.lsnThisDir, (PVOID)dirblk, SECTORS_PER_DIRBLK);
        }
        else return (TRUE);                             /* bogus length - kill entry */
    }

     /*	 Now check the length of the DIRENT  */
     if ((dirent.d->cchThisEntry != dir_elen) ||
	 (dirent.d->cchThisEntry > MAX_DIRENT_LENGTH) ||
	 (dirent.d->cchThisEntry < sizeof (DIRENT)) ||
	 (dirent.p - (PUCHAR)dirblk + dirent.d->cchThisEntry > BYTES_PER_DIRBLK)) {

#ifdef TRACE
	    fprintf (stderr, "chk_bad_DIR_ELEN: dirent has bad cchThisEntry\n");
	    fflush (stderr);
#endif

	    return (TRUE);
    }
    else
	    return (FALSE);
}

/***	chk_check_end_dirent - check consistency of end dirent
 *
 *	This function checks an end DIRENT and fixes it if necessary.
 *
 *	chk_check_end_dirent (dirblk, lsn, dirent, has_DF_BTP)
 *
 *	ENTRY		dirblk - the dirblk the dirent is within
 *			lsn - the lsn of the dirblk
 *			dirent - the end dirent
 *			has_DF_BTP - TRUE if the end dirent should have a down
 *							 pointer.
 *
 *	EXIT		No return value
 *
 *	CALLS		chk_no_writing
 *				write_scratch
 *
 *	WARNINGS	None
 *
 *	EFFECTS		If the dirent was fixed, we attempt to write the dirblk
 *				to disk.
 */
void chk_check_end_dirent (dirblk, lsn, dirent, has_DF_BTP)
 PDIRBLK dirblk;
	ULONG lsn;
 PDIRENT dirent;
	USHORT has_DF_BTP;
{
    USHORT save = FALSE;
    BYTE dir_flags, dir_elen;

    /*	Determine the valid field values based on
     *	the presence or absence of a down pointer.
     */
    if (has_DF_BTP) {
	    dir_flags = DF_END | DF_BTP;
	    dir_elen  = sizeof (DIRENT) + sizeof (ULONG);
    } else {
	    dir_flags = DF_END;
	    dir_elen  = sizeof (DIRENT);
    }

    /*	Check the length  */
    if (dirent->cchThisEntry != dir_elen) {
	    dirent->cchThisEntry = dir_elen;
	    save = TRUE;
    }


    /*	The end flag should be set  */
    if ((dirent->fFlags & dir_flags) != dir_flags) {
	    dirent->fFlags |= dir_flags;
	    save = TRUE;
    }

    /*  The number of ACEs in the DIRENT should be zero */
    if (dirent->fFlex & 3) {
            dirent->fFlex &= ~3;
            save = TRUE;
    }

    /*	The length of the name should be 1  */
    if (dirent->cchName != 1) {
	    dirent->cchName = 1;
	    save = TRUE;
    }

    /*	The name should be 0xff.  */
    if (dirent->bName[0] != 0xff) {
	    dirent->bName[0] = 0xff;
	    save = TRUE;
    }

    if (dirent->bCodePage != 0) {
	    dirent->bCodePage = 0;
	    save = TRUE;
    }

#ifdef TRACE
    if (save) {
	    fprintf (stderr, "chk_check_end_dirent: a field was corrupt.\n");
	    fflush (stderr);
    }
#endif

    if (save && !chk_no_writing (CHK_FIX_DIRFLAG_STR, NULL))
    write_scratch (lsn, dirblk, SECTORS_PER_DIRBLK);

} /* chk_check_end_dirent */


/*
 ***	chk_new_name -- determine whether a name requires new names
 *
 *	This function is called to determine whether a directory
 *	entry should have the NEW NAMES bit set.
 *
 *	This bit is set under either of the following conditions:
 *
 *	    1)	The name does not fit into the 8.3 format, or
 *	    2)	The name uses a previously forbidden character
 *		(ie. one of comma, semicolon, '[', ']', '+', or '=').
 *
 *	ENTRY pc -- pointer to the length-byte of a DIRENT name
 *
 *	RETURNS TRUE if the name is a new name
 *		FALSE if it does not meet either of the above criteria.
 *
 */
BOOL
chk_new_name( PCHAR pc, USHORT cp_index )
{
    USHORT cb;		    /*	Length of the name	 */
    BOOL fDot = FALSE;	    /*	have we seen a dot yet?  */
    USHORT i;
    BOOL fDBCS = FALSE;     /*  previous byte was DBCS leading byte  */


    /*
     *	Filter out any special '..' entries that accidentally
     *	show up here.
     */
    if( (*pc == 2) && (*(pc+1) == '\001') && (*(pc+2) == '\001') ) {

	/* '..' does not require the NEW NAME bit to be set.  */
	return FALSE;
    }


    /*
     *	Get the length of the name and advance the pointer
     *	to the first character of the name proper.
     */
    cb = (USHORT)(*pc++);


    if( cb > 12 ) {

	/*  This is obviously longer than 8.3  */
	return TRUE;
    }


    i = 0;

    while( cb-- ) {

	/*  Increment number of characters seen	 */
	i += 1;

        if( fDBCS ) {

            /*
             *  This is the trailing byte of a DBCS character.
             *  Don't check it.
             */
            fDBCS = FALSE;

        } else {

            if( *pc == '.' ) {

                if (fDot) {

            		  /*  This is the second dot--it's a new name.  */
                    return TRUE;

                } else {

                    /*
                     *  Note the fact that we've seen a dot, and
                     *  start counting characters after the dot.
                     *  If this is the first characters the name
                     *  is a new name (leading dot).
                     */
                    if( i <= 1 )
                        return TRUE;

                    i = 0;
                    fDot = TRUE;
                }

            } else {

                if( chk_check_dbcs( *pc, cp_index ) ) {

                    /*
                     *  This is the lead byte of a DBCS-character;
                     *  Set the fDBCS flag for the next iteration.
                     */
                    fDBCS = TRUE;

                } else {
                  /*
                   * If the current character is in the list of
	                * new characters, then it's a new name.
	                */
                   if ( strchr(new_chars, *pc ) )
                     return TRUE;
                }
            }
        }

	    /*
	     *	If we've seen more than eight characters before
	     *	the first dot, or more than three characters after
	     *	it, then the name is a new name.
	     */
	    if( !fDot && i > 8 )
		return TRUE;

	    if( fDot && i > 3 )
		return TRUE;

	/*  go on to the next character	*/
	pc++;
    }

}

/***	chk_validate_structure - check out a file system structure
 *
 *	This function is called to check the consistency of a single
 *	file system structure -- an FNODE, a DIRBLK, or an allocation
 *	block.	It validates certain fields of the structure, and then
 *	calls itself recursively to validate child structures.	If any
 *	problems are found that can't be spot-fixed, the offending file's
 *	name is stored for later deletion and eventual recovery.  Things
 *	like corrupt parent pointers and self-pointers can be fixed
 *	on the spot without having to delete the entire structure and
 *	recover it later.
 *
 *	This function also checks that names of directory entries and
 *	also lsn's in allocation blocks are in ascending order.  If they
 *	aren't, they will be fixed directly.  Resorting is also the
 *	solution to finding a DIRBLK with only an end record or with
 *	mixed "downpointer"/"no downpointer" records.
 *
 *	chk_validate_structure (lsn, parent, fnode, type, name, b)
 *
 *	ENTRY		lsn	- sector where we found this structure
 *			parent	- sector of this structure's parent
 *			fnode	- sector of this structure's FNODE parent
 *				  (since parent pointers of BTREE nodes
 *				   point to the DIRBLK above, but FNODE
 *				   parent pointers point to the parent FNODE)
 *			type	- what kind of structure this is
 *			name	- pointer to previous file name or indirect
 *				  block lsn
 *			b	- memory image of the block to be checked
 *
 *	EXIT		OK	  if structure is consistent
 *			NOT_OK	  if structure is badly inconsistent --
 *				  i.e., bad signature
 *			DELETE_ME if parent structure should be deleted
 *			SORT_ME	  if this directory needs to be resorted
 *
 *	CALLS		chk_delete_btree
 *			chk_delete_name
 *			chk_fix_current
 *			chk_fix_endrec
 *			chk_fix_frep
 *			chk_fix_parent
 *			free
 *			chk_hotfix
 *			malloc
 *			chk_read_sectors
 *			chk_sort_directory
 *			use_sectors
 *			chk_validate_structure (recursive)
 *			write_scratch
 *
 *	EFFECTS		Changes global disk map
 *			May update disk structures
 *			Allocates and deallocates memory
 *
 *	WARNINGS	If command line allows, may delete inconsistent stuff
 *			May terminate program if insufficient memory
 */

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

#define VS_STACK 54

/*
 * The following macro is designed to make it easy to return out of
 * chk_validate_structure prematurely, adjusting our internal stack
 * monitor.  The exit_recursive call is included inside the return's
 * parens so that the whole thing is a single statement and therefore
 * doesn't need braces, and doesn't require that there be no semicolon
 * after it.
 */

#define RET(val)  return (chk_exit_recursive (VS_STACK), val)

USHORT chk_validate_structure (lsn, parent, fnode, type, name, b)
ULONG lsn;
ULONG parent;
ULONG fnode;
USHORT type;
PUCHAR name;
PBLK b;
{
    union dp dp;
    register int i, j;
    USHORT retcode=OK, have_downptrs;
    ULONG l, sect, ptr=0L, *lptr, save;
    union blk *child;
    PALNODE palnode;                            // CP13 B730753 - BJH OS/2 Change Team
    PALLEAF palleaf;                            // CP13 B730753 - BJH OS/2 Change Team

#ifdef TRACE
    fprintf (stderr, "chk_validate_structure (%lx, %lx, %lx, TYPE_%s, `%s')\n",
    lsn, parent, fnode, chk_vstypes [type],
    (name != NULL) ? name : "(no name)");
    fflush (stderr);
#endif

    if (chk_enter_recursive (VS_STACK)) {

	/*
	/*  There isn't enough stack to continue. Just assume that
	 *  everything is OK.  (Display a helpful message, though.)
	 */
	CHK_ERROR_MSG( CHK_INSF_MEM_CONTINUING_STR);
	chk_exit_recursive (VS_STACK);
	return (OK);
    }

    switch (type) {

    case TYPE_DIRBLK:

	/*
	 *  We expect this block to be a DIRBLK.  Check that it really is.
	 */

	if (b->d.sig != DBSIGVAL) {
#ifdef TRACE
	    fprintf (stderr, "v_s:  dirblk has bad signature\n");
	    fflush (stderr);
#endif
	    RET (NOT_OK);
	}


	/*
	 * Check that the offulFirstFree field is neither impossibly small
	 * or large. Note: it could still be invalid.
	 */

	if ((b->d.offulFirstFree < 2 * sizeof ( DIRENT) + 1+
		 FIELDOFFSET ( DIRBLK, bFirst)) ||
	    (b->d.offulFirstFree > SECTORS_PER_DIRBLK * SECSIZE)) {
#ifdef TRACE
	    fprintf (stderr, "v_s:  dirblk has bad offulFirstFree value\n");
	    fflush (stderr);
#endif
	    if (chk_delete_inconsistency) {

		/*
		 *  This block is so corrupted that we don't
		 *  want it recovered.
		 */
		b->d.sig = 0x0000ffff;
		write_scratch (lsn, b, SECTORS_PER_DIRBLK);
	    }
	    RET (NOT_OK);
	}

	if (use_sectors (lsn, (ULONG)SECTORS_PER_DIRBLK, DISK_DIRBLK)) {
#ifdef TRACE
		fprintf (stderr, "v_s:  dirblk is cross-linked\n");
		fflush (stderr);
#endif
	    /*	The DIRBLK sectors are cross-linked;
	     *	return the error
	     */
	    RET (NOT_OK);
	}

	/*
	 *  Check (and spot-fix, if necessary) the parent
	 *  and self fields.
	 */
	if (parent != b->d.lsnParent)
		chk_fix_parent (lsn, b, parent);
	if (lsn != b->d.lsnThisDir)
		chk_fix_current (lsn, b);

	/*  Set dp.p to the first DIRENT:  */
	dp.p = DIR_START (b->d);

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

	    /*
	     *	The only DIRENT is an end entry;
	     *	this directory needs to be sorted.
	     */
	    retcode = SORT_ME;
	}

	have_downptrs = (dp.d->fFlags & DF_BTP) ? TRUE : FALSE;

	/*
	 * If we've gotten to a leaf block, check the tree depth.  If this is
	 * a leaf block and *chk_maxdepth is still zero, then this is lexically
	 * the first DIRBLK in the directory.  If its first DIRENT isn't
	 * the ".." entry, force a resort.
	 */

	if (!have_downptrs) {

	    /* this is a leaf block */
	    if (!*chk_maxdepth) {

		/*
		 *  the maximum depth hasn't been computed
		 *  yet, so we're it.
		 */
		*chk_maxdepth = *chk_depth;
		if (memcmp (dp.d->bName, "\001\001", 2) ||
		    (dp.d->cchName != 2))	{

		    /*
		     *	The first entry isn't "..";
		     *	the directory has to be resorted.
		     */
		    retcode = SORT_ME;
		}
	    } else if (*chk_depth != *chk_maxdepth) {

		/*
		 *  The depth of this node isn't the same as
		 *  the depth of a previously-visited leaf.
		 *  Sort the directory.
		 */
		retcode = SORT_ME;
	    }
	}

	/*
	 *  The lowest bit of culChange is the topmost-dirblk
	 *  flag.  If depth is presently zero, this DIRBLK is
	 *  at the top of the directory, so that bit should
	 *  be set;  if depth is not set, that bit must be zero.
	 */
	if ((*chk_depth && (b->d.culChange & 1)) ||
	    (!*chk_depth && !(b->d.culChange & 1))) {

		/*  topmost-dirblk flag is in wrong state.  */
		chk_fix_ccnt (b, !*chk_depth);
	}

	/*  We're now one level deeper.  */
	(*chk_depth)++;

	/*
	 * Get one block to read all child DIRBLKs and FNODEs into.
	 */
	if (!(child = (union blk *)ALLOCATE_BLOCK (SECTORS_PER_DIRBLK))) {

	    /*
	     *	Couldn't get a memory block.  Assume
	     *	everything is OK (since we can't check it)
	     *	and return.
	     */
	    if (!chk_insf_mem) {
		chk_insf_mem++;
		CHK_ERROR_MSG( CHK_INSF_MEM_CONTINUING_STR);
	    }
	    RET (OK);
	}

	/*
	 * CHECK THE DIRBLK B+TREE. We recurse down through the tree using
	 * the DIR_BTP pointers of the DIRENTS until we reach the leaf
	 * DIRBLK. We then process the DIRENTS of the leaf DIRBLK. We
	 * then recurse up, process the next DIRENT of the previous DIRBLK
	 * and take the downptr if it exists. Thus, we do an inorder
	 * traversal of the B+TREE, processing DIRENTs.
	 */

	while (TRUE) {

	    /*
	     * we break out manually when
	     * the last DIRENT is seen.
	     */

	    /*
	     * CHECK FIELDS NECESSARY FOR RECURSING DOWN DIRBLKS.
	     * At this point we check all fields of a DIRENT that must be
	     * correct in order to use the down ptr (if any), to get to
	     * the child DIRBLK. The remaining DIRENT fields are checked
	     * on the way back up.
	     */

	    if (chk_bad_DIR_ELEN (b, *((PuDIRENT)&dp))) {

		/*
		 *  This entry has a bad length byte.
		 *  Truncate the dirblk and ask that
		 *  the directory be resorted.
		 */
	       chk_truncate_dirblk (lsn, b, *((PuDIRENT)&dp));
	       retcode = SORT_ME;
	    }


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

		/*
		 *  This entry has a down pointer.  If the
		 *  first entry did not have a down pointer,
		 *  this tree needs to be sorted.
		 */
		if (!have_downptrs)
		    retcode = SORT_ME;


		ptr = DOWN_PTR (dp);

		if ((l=chk_hotfix (ptr, SECTORS_PER_DIRBLK, child)) != ptr) {

		    /*
		     *	The sector number returned by reading-with-hotfixing
		     *	is different than the one we requested, so either
		     *	it could not be read or hotfixed (l == 0) or
		     *	it was successfully hotfixed to the new sector
		     *	number.
		     */
		    if (l == 0) {
			/* hotfixing wasn't possible */
			/*
			 *  The block could neither be read nor hotfixed.
			 *  This DIRBLK must be deleted from the b-tree,
			 *  which means the directory has to be sorted.
			 */
			chk_delete_btree (lsn, b, dp);
			retcode = SORT_ME;
		    } else {

			/*
			 *  The requested sector has been hotfixed.
			 *  Update the down pointer to the new
			 *  sector number and write the DIRBLK.
			 */
			DOWN_PTR (dp) = l;
			write_scratch (lsn, b, SECTORS_PER_DIRBLK);
			ptr = l;
		    }
		}

		if ((i=chk_validate_structure (l, lsn,
					       fnode, TYPE_DIRBLK,
					       name, child)) != OK) {

		    if (i == SORT_ME)
			retcode = SORT_ME;
		    else {
			/*
			 *  There's some other problem, so we'll delete
			 *  the down pointer.
			 */
			chk_delete_btree (lsn, b, dp);
			retcode = SORT_ME;
		    }
		}
	    } else if (have_downptrs) {

		/*
		 *  This entry lacks a downpointer, although
		 *  another entry in this DIRBLK has one,
		 *  so the directory needs to be sorted.
		 */
		retcode = SORT_ME;
	    }


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

		/*  We've reached the end entry, so we can stop.  */
		    chk_check_end_dirent (&b->d, lsn, dp.d, have_downptrs);
		    break;
	    }

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

	    if( dp.d->bCodePage == 0xff ) {

		/*
		 *  Set the codepage to zero
		 */
		chk_fix_dir_codepage( b, dp, 0 );

	    }

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

		/*
		 *  The dirent's name is invalid.  The directory 
		 *  will need to be sorted so that it is in
		 *  lexicographical order--B783383.
		 */
		retcode = SORT_ME;
		GETNEXT_ENTRY (dp);
		continue;
	    }


	    if (name != NULL) {

		/*
		 *  Compare the name of this dirent with
		 *  the previous dirent's name
		 */

		if ((i=chk_strnicmp (dp.d->bName, dp.d->bCodePage,
				     name, PREMAPPED, dp.d->cchName)) <= 0) {

		    /*	The directory needs to be sorted.  */
		    retcode = SORT_ME;
		}

		/*
		 *  Save the name of this dirent (upcased) to
		 *  be compared with the next dirent's name.
		 */
		memcpy (name, dp.d->bName, dp.d->cchName);
		name [dp.d->cchName] = 0;
		chk_strnupr(name, dp.d->bCodePage, dp.d->cchName);
	    }

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

		/*
		 *  This is the special ".." entry;  check its flags
		 *  and attributes.  If it's the root dir, PIA also
		 *  requires that the FNODE number be set.  (If this
		 *  is wrong, we'll fix it silently).
		 *
		 *  The SPECIAL flag and the DIRECTORY attribute may
		 *  be set;  DF_ACL and DF_XACL may be set, as may
		 *  ATTR_ARCHIVE.  All other bits must be clear.
		 */
		if (((dp.d->fFlags & ~(DF_ACL | DF_XACL)) != DF_SPEC) ||
		    ((dp.d->fAttr & ~ATTR_ARCHIVE) != ATTR_DIRECTORY))
			chk_fix_dirflag (b, dp);

		if (fnode == root_fnode && dp.d->lsnFNode != fnode) {
			dp.d->lsnFNode = fnode;
         write_scratch (lsn, &b->d, SECTORS_PER_DIRBLK);
		}

		GETNEXT_ENTRY (dp);
		continue;
	    } else if ((dp.d->fFlags & ~LEGAL_FLAG_BITS) ||
		       (dp.d->fAttr   & ~LEGAL_ATTR_BITS)){

		/*  flags need to be fixed.  */
		chk_fix_dirflag (b, dp);
	    }

	    /*
	     *	Check the NEW_NAMES bit
	     */
	    if( chk_new_name( &dp.d->cchName, dp.d->bCodePage ) ) {

		/*
		 *  The new name flag should be set
		 */
		if( !(dp.d->fAttr & ATTR_NEWNAME) )
		    chk_fix_new_name_bit( b, dp, TRUE );

	    } else {

		/*
		 *  The new name flag should not be set
		 */
		if( dp.d->fAttr & ATTR_NEWNAME )
		    chk_fix_new_name_bit( b, dp, FALSE );
	    }


	    if (chk_verbose_display)
		chk_display_name (chk_curpath, dp.d);


	    /*
	     *	Read the FNODE, with hotfixing if needed.
	     */
	    if ((sect=chk_hotfix (dp.d->lsnFNode,
				  SECTORS_PER_FNODE,
				  child)) != dp.d->lsnFNode) {

		/*
		 *  Since the sector number returned by the hotifx-read
		 *  isn't the same as we gave it, either it can't be
		 *  read or it has been hotfixed to another sector.
		 */
		if (!sect) {

		    /*
		     *	hotfixing FNODE sector didn't work.
		     *	delete this file and move on to
		     *	the next entry.
		     */
		    chk_delete_name (chk_curpath, dp.d);
		    GETNEXT_ENTRY (dp);
		    continue;
		} else {

		    /*
		     *	The FNODE has been hotfixed to a new
		     *	sector.  Update the pointer and save
		     *	the DIRBLK.
		     */
		    dp.d->lsnFNode = sect;
		    write_scratch (lsn, b, SECTORS_PER_DIRBLK);
		}
	    }

#ifdef TESTING
	    printf ("Delete file `%s%s':\n", chk_curpath, name);
	    if ((ch != 'x') && ((chr=getch ()) == 'y')) {
		printf ("Okay, deleting that file now.\n");
		chk_delete_name (chk_curpath, dp.d);
	    }
	    else
#endif

	    chk_easize = dp.d->ulEALen;

	    if (dp.d->fAttr & ATTR_DIRECTORY) {

		/*
		 *  The entry refers to a subdirectory.
		 *  Descend to it through the FNODE.
		 */
		if ((i=chk_validate_subdir (sect, lsn, fnode, TYPE_SUBDIR,
						name, child)) == SORT_ME) {

			/*
			 *  The subdirectory needs to be sorted.
			 */
			chk_sort_directory (dp.d->lsnFNode, &dp.d->cchName);
		} else if (i != OK) {

			/*
			 *  There's something seriously wrong with
			 *  the subdirectory.  Delete the reference
			 *  to it.
			 */
			chk_delete_name (chk_curpath, dp.d);
		}

	    } else {

		/*
		 *  The dirent refers to a file.  Descend to
		 *  the FNODE and verify that the file is
		 *  consistent.
		 */
		chk_accumsize = 0L;
		if ((i=chk_validate_structure (sect,
					       lsn,
					       fnode,
					       TYPE_FNODE,
					       name,
					       child)) == CLEAN_ME) {

			/*  The file has hotfixed runs;
			 *  clean it up.
			 */
			chk_fixup_fnode (sect, 0U, (USHORT)spb.culHotFixes);

		} else if (i != OK) {

			/*
			 *  There's something seriously wrong with
			 *  this file.  Add it to the delete list.
			 */
			chk_delete_name (chk_curpath, dp.d);

		} else {

		    /*
		     *	The file checks out OK; check
		     *	the cchFSize and ulVLen fields.
		     */
		    chk_check_file_size (b, dp.d, &child->f);

		}
	    }

	    /*
	     * Fix up EA information in the DIRENT if required. We
	     * check the ulEALen field and the "need" EA bit in fFlags
	     */

	    save = (ULONG)FALSE;

	    if (chk_easize != dp.d->ulEALen) {
		dp.d->ulEALen = chk_easize;
		save = !chk_no_writing (CHK_EA_SIZE_STR, NULL);
	    }

	    if ((!chk_neacnt &&	 (dp.d->fFlags & DF_NEEDEAS)) ||
		(chk_neacnt && !(dp.d->fFlags & DF_NEEDEAS))) {

		if (chk_neacnt) {
			dp.d->fFlags |= DF_NEEDEAS;
		} else {
			dp.d->fFlags &= ~DF_NEEDEAS;
		}

		save = !chk_no_writing (CHK_EA_NEEDEAS_STR, NULL);
	    }

	    /*	Save the updated EA info, if necessary.  */
	    if (save)
		write_scratch (lsn, b, SECTORS_PER_DIRBLK);


	    /*	Go on to the next entry.  */
	    GETNEXT_ENTRY (dp);

#ifdef TESTING
	    ch = chr;
#endif
	}

	FREE_BLOCK (child);

	/*
	 *  If the offset of the first free byte is incorrect,
	 *  fix it.
	 */
	if ((ULONG)((dp.p - (PUCHAR)b) + dp.d->cchThisEntry) != b->d.offulFirstFree)
		chk_fix_frep (b, dp.p - (UCHAR *)b + dp.d->cchThisEntry);

	/*
	 *  We're about to go back up a level
	 */
	(*chk_depth)--;

	if ((retcode != OK) && (retcode != SORT_ME))
		chk_mark_deleted (lsn, (ULONG)SECTORS_PER_DIRBLK);

	break;

    case TYPE_FNODE:
    case TYPE_SUBDIR:

	/*
	 * This block should be an FNODE for a file or subdirectory
	 */

#ifdef ALIGN
	if (b->f.sig == OLDFNSIGVAL)
		align_fnode (lsn, &b->f);
#endif

	/*
	 *  Check the signature;  if it's wrong,
	 *  return that the FNODE is not OK.
	 */

	if (b->f.sig != FNSIGVAL) {
#ifdef TRACE
		fprintf (stderr, "v_s:  fnode has bad signature\n");
		fflush (stderr);
#endif
		RET (NOT_OK);
	}

	if (use_sectors (lsn, (ULONG)SECTORS_PER_FNODE,
			 (type == TYPE_SUBDIR) ? DISK_SUBDIR : DISK_FNODE)) {
#ifdef TRACE
		fprintf (stderr, "v_s:  fnode is cross-linked\n");
		fflush (stderr);
#endif
		/*
		 *  The sector is already marked as used, or
		 *  it is marked in the bitmap as free.
		 */
		RET (NOT_OK);
	}

	/*  Check the parent pointer, fixing if necessary  */
	if (fnode != b->f.lsnContDir)
	    chk_fix_parent (lsn, b, fnode);

	/*  Check out the ACL  */
	if (b->f.aiACL.sp.lsn || b->f.aiACL.usFNL)
	    chk_check_acl (lsn, &b->f);

	/*  Check the EAs.  Initialize count of nead-EAs to zero.  */
	chk_neacnt = 0;

	if (b->f.aiEA.sp.cbRun || b->f.aiEA.usFNL)
	    chk_check_eas (lsn, &b->f);
	else chk_easize = 0;

	if (type == TYPE_SUBDIR) {

	    /*
	     *	This FNODE points to a subdirectory.  Allocate
	     *	a name comparison buffer and set it to the
	     *	minimum name (blank buffer).  If we can't
	     *	get memory for the buffer, don't bother with
	     *	the compare.
	     */
	    dp.p = malloc (MAX_NAME_LEN);
	    if (dp.p != NULL)
		memset (dp.p, '\0', MAX_NAME_LEN);

	    if (!(child = (union blk *)ALLOCATE_BLOCK (SECTORS_PER_DIRBLK))) {

		/*
		 *  Can't get enough memory to continue--assume it's
		 *  OK.
		 */
		if (!chk_insf_mem) {
			chk_insf_mem++;
			CHK_ERROR_MSG( CHK_INSF_MEM_CONTINUING_STR);
		}
		RET (OK);
	    }

	    /*	fetch lsn of top DIRBLK for subdirectory  */
	    l = b->f.fst.a.aall [0].lsnPhys;

	    /*
	     *	Read the top DIRBLK, hotfixing if needed.
	     */
	    if ((sect=chk_hotfix (l, SECTORS_PER_DIRBLK, child)) != l) {

		/*
		 *  Either we couldn't read the top DIRBLK at all,
		 *  or it got hotfixed to a new sector.
		 */
		if (sect == 0) {

			/*
			 *  Can't read the DIRBLK.  Delete this FNODE,
			 *  since it doesn't point at anything we can
			 *  use.
			 */
			FREE_BLOCK (child);
			chk_mark_deleted (l, 1L);
			RET (DELETE_ME);
		} else {

			/*
			 *  The top DIRBLK got hotfixed to a new sector.
			 *  Update the DIRBLK lsn in the FNODE and
			 *  write the FNODE to disk.
			 */
			b->f.fst.a.aall [0].lsnPhys = sect;
			write_scratch (lsn, b, SECTORS_PER_FNODE);
		}
	    }

	    /*
	     *	Save the length of the current path, the concatenate
	     *	the current subdir's name to the current path, with
	     *	a backslash at the end.
	     */
	    i = strlen (chk_curpath);
	    strcat (chk_curpath, name);
	    strcat (chk_curpath, "\\");

	    /* save EA size around subdir check */
	    ptr = chk_easize;
	    save = chk_neacnt;

	    /*
	     *	Descend to the top DIRBLK of the subdirectory.
	     */
	    retcode = chk_validate_structure (sect,
					      lsn,
					      lsn,
					      TYPE_DIRBLK,
					      dp.p,
					      child);
	    FREE_BLOCK (child);

	    chk_easize = ptr;	/* restore EA size */
	    chk_neacnt = save;

	    /*
	     *	hack current level from the current path.
	     */
	    chk_curpath [i] = '\0';

	    /* deallocate the name comparison buffer */
	    free (dp.p);

	    if ((retcode != OK) && (retcode != SORT_ME))
		chk_mark_deleted (lsn, 1L);

	    /* return subdir's code after freeing dp.p */
	    RET (retcode);
	}

	/*
	 * If we get here, we're processing an ordinary file, 
	 * not a subdirectory.  If the FNODE's pointers point 
	 * directly to runs, just mark them in the disk map.
	 * Otherwise, recursively validate the allocation blocks.
	 */

	if (!(b->f.fst.alb.bFlag & ABF_NODE)) {

	    /*
	     *	start at LOF 0
	     */
	    l = 0L;

	    if( b->f.fst.alb.cUsed + b->f.fst.alb.cFree != LEAFPERFNODE ) {

		/*
		 *  The FNODE is corrupt.  This file will cause
		 *  the file system problems, so we'll yank it.
		 */
		retcode = DELETE_ME;

	    } else {

         palleaf = (PALLEAF)&b->f.fst.a.aall [0];                 // CP13 B730753 - BJH OS/2 Change Team
   	   for (i=0; i < b->f.fst.alb.cUsed; i++) {

                  if ((j=use_sectors (palleaf->lsnPhys,          // CP13 B730753 - BJH OS/2 Change Team
                                      palleaf->csecRun,          // CP13 B730753 - BJH OS/2 Change Team
                                      DISK_DATA)) == DISK_FIXED) {

			    /*
			     *	Part of the run is hotfixed--clean it.
			     */
			    retcode = CLEAN_ME;

			} else if (j) {

				/*
				 *  Part of the run is cross-linked--
				 *  delete the file.
				 */
				chk_mark_deleted (lsn, 1L);
				retcode = DELETE_ME;
			}

           if (palleaf->lsnLog != l)
           chk_fix_lof (lsn, b, palleaf, l, 0);    // CP13 B730753 - BJH OS/2 Change Team

           l += palleaf->csecRun;                           // CP13 B730753 - BJH OS/2 Change Team
           chk_accumsize += palleaf->csecRun * SECSIZE;     // CP13 B730753 - BJH OS/2 Change Team
           palleaf++;
		}
	    }
	} else {

	    /*
	     *	The FNODE points to allocation sectors.  Get
	     *	a pointer to the first node and initialize
	     *	the LOF accumulator (l) to zero.
	     */
	    lptr = &b->f.fst.a.aall [0].lsnLog;
	    l = 0L;

	    if( b->f.fst.alb.cUsed + b->f.fst.alb.cFree != NODEPERFNODE ) {

		/*
		 *  The FNODE is corrupt.  This file will cause
		 *  the file system problems, so we'll yank it.
		 */
		retcode = DELETE_ME;

	    } else {
                /*
                 *  Get a pointer to the first node and initialize
                 *  the LOF accumulator (l) to zero.
                 */
                palnode = (PALNODE)&b->f.fst.a.aall [0];          // CP13 B730753 - BJH OS/2 Change Team
                l = 0L;


		if (!(child = (union blk *)ALLOCATE_BLOCK(SECTORS_PER_AB))) {

		    /*
		     *	Couldn't get enough memory, so we have to
		     *	assume everything below the FNODE is OK.
		     */
		    if (!chk_insf_mem) {
			chk_insf_mem++;
			CHK_ERROR_MSG( CHK_INSF_MEM_CONTINUING_STR);
		    }
		    RET (OK);
		}

		for (i=0; i < b->f.fst.alb.cUsed; i++) {

		    /*	Read the allocation sector, hotfixing if needed.  */
          ptr = palnode->lsnPhys;              // CP13 B730753 - BJH OS/2 Change Team

		    if ((sect=chk_hotfix (ptr,
					  SECTORS_PER_AB,
					  child)) != ptr) {

			/*
			 *  Either we couldn't read the allocation sector
			 *  at all, or it has been hotfixed to a new
			 *  sector.
			 */
			if (!sect) {

			/*
			 *  Can't read it.  Delete the file.
			 */
			chk_mark_deleted (lsn, 1L);
			retcode = DELETE_ME;
			} else {

			/*
			 *  It got hotfixed to a new location.
			 *  Update the FNODEs pointer, and
			 *  save the FNODE.
			 */
         palnode->lsnPhys = sect;         // CP13 B730753 - BJH OS/2 Change Team
			write_scratch (lsn, b, SECTORS_PER_FNODE);
			}
		    }

		    /*
		     *	Descend to the allocation sector and verify it.
		     */
		    if ((j=chk_validate_structure (palnode->lsnPhys,     // CP13 B730753 - BJH OS/2 Change Team
						   lsn, lsn,
						   TYPE_AB,
						   (PUCHAR)&l, child)) ==
				CLEAN_ME) {

			/*  The block contains hotfixed runs.  */
			retcode = CLEAN_ME;

		    } else if (j != OK) {

			/*  The block is corrupt--delete the file.  */
			chk_mark_deleted (lsn, 1L);
			retcode = DELETE_ME;
		    }

		    /*
		     * Check the lsnLog field of this block.  Most ALNODE
		     * records' lsnLogs should be the count of sectors up to
		     * and including that node;	 the last
		     * one should be 0xffffffff.
		     */
                    if ((i < b->f.fst.alb.cUsed-1) && (palnode->lsnLog != l))   // CP13 B730753 - BJH OS/2 Change Team
                        chk_fix_lof (lsn, b, palnode, l, ABF_NODE);             // CP13 B730753 - BJH OS/2 Change Team
                    else if ((i == b->f.fst.alb.cUsed-1) &&
                             (palnode->lsnLog != 0xffffffff))                   // CP13 B730753 - BJH OS/2 Change Team
                        chk_fix_lof (lsn, b, palnode, 0xffffffff, ABF_NODE);    // CP13 B730753 - BJH OS/2 Change Team
                    palnode++;                                                  // CP13 B730753 - BJH OS/2 Change Team

		}

		FREE_BLOCK (child);
	    }
	}

	break;

    case TYPE_AB:

	/*
	 * This block should be an allocation block
	 * Check the signature, and look for cross-linking.
	 */

	if ((b->a.sig != ABSIGVAL) ||
	    use_sectors (lsn, (ULONG)SECTORS_PER_AB, DISK_AB)) {
#ifdef TRACE
	    fprintf (stderr, "v_s:  AB has bad signature or is cross-linked\n");
	    fflush (stderr);
#endif
	    RET (NOT_OK);
	}

	/*  Check the parent pointer and self pointer,
	 *  fixing each on the fly if needed.
	 */
	if (parent != b->a.lsnRent)
		chk_fix_parent (lsn, b, parent);

	if (lsn != b->a.lsnSelf)
		chk_fix_current (lsn, b);

	/*
	 *  Start at the first record
	 */
	if (!(b->a.alb.bFlag & ABF_NODE)) {
            palleaf = (PALLEAF)AB_START (b);            // CP13 B730753 - BJH OS/2 Change Team

	    /*
	     *	The entries in this allocation sector are leaves.
	     *	Check each one.
	     */

	    for (i=0;
		(i<b->a.alb.cUsed) && (i < LEAFPERSEC);
		i++) {

                if ((j=use_sectors (palleaf->lsnPhys,    // CP13 B730753 - BJH OS/2 Change Team
                                    palleaf->csecRun,    // CP13 B730753 - BJH OS/2 Change Team
                                    DISK_DATA)) == DISK_FIXED) {

			/*  The run has a hotfixed sector  */
			retcode = CLEAN_ME;

		} else if (j) {

			/*
			 *  The run is bad.  The allocation sector
			 *  should be deleted.
			 */
#ifdef TRACE
			fprintf (stderr, "v_s:  AB has cross-linked run\n");
			fflush (stderr);
#endif
			retcode = DELETE_ME;
		}

      /*  Check the lsnLog, and fix it if need be.  */
      if (palleaf->lsnLog != *(ULONG *)name)                    // CP13 B730753 - BJH OS/2 Change Team
        chk_fix_lof (lsn, b, palleaf, *(ULONG *)name, 0);       // CP13 B730753 - BJH OS/2 Change Team

		/*  increment the LOF accumulator to reflect this run.  */
      *(ULONG *)name += palleaf->csecRun;              // CP13 B730753 - BJH OS/2 Change Team

		/*  Add this run to the file size  */
      chk_accumsize += palleaf->csecRun * SECSIZE;     // CP13 B730753 - BJH OS/2 Change Team

		/*  Move ahead to the next leaf--each leaf
		 *  is 3 dwords.
		 */
                palleaf++;                                      // CP13 B730753 - BJH OS/2 Change Team
	    }
	} else {
            palnode = (PALNODE)AB_START (b);                    // CP13 B730753 - BJH OS/2 Change Team

		/*
		 *  The entries in this allocation sector are
		 *  pointers to other allocation sectors.
		 */
		if (!(child = (union blk *)ALLOCATE_BLOCK (SECTORS_PER_AB))) {

		    /*
		     *	Can't get enough memory to verify children, so
		     *	we assume that they're OK.
		     */
		    if (!chk_insf_mem) {
			    chk_insf_mem++;
			    CHK_ERROR_MSG( CHK_INSF_MEM_CONTINUING_STR);
		    }
		    RET (OK);
		}

		for (i=0;
		     (i<b->a.alb.cUsed) && (i < NODEPERSEC);
		     i++) {

		    /*
		     *	Read the child allocation block, hotfixing
		     *	if necessary.
		     */
                    l = palnode->lsnPhys;                // CP13 B730753 - BJH OS/2 Change Team

		    if ((sect=chk_hotfix (l, SECTORS_PER_AB, child)) != l) {

			/*
			 *  Either we couldn't read the sector at all,
			 *  or it has been hotfixed to a new location.
			 */

			if (sect == 0) {

			    /*
			     *	Couldn't read the child.  Give up on
			     *	this allocation sector.
			     */
			    retcode = DELETE_ME;

			} else {

			    /*
			     *	The child has been hotfixed to a new
			     *	sector.  Update the reference to it,
			     *	and save the parent.
			     */
             palnode->lsnPhys = sect;             // CP13 B730753 - BJH OS/2 Change Team
			    write_scratch (lsn, b, SECTORS_PER_AB);
			}
		    }

		    /*
		     *	Validate the child.
		     */
		    if ((j=chk_validate_structure (sect,
						   lsn,
						   lsn,
						   TYPE_AB,
						   name,
						   child)) == CLEAN_ME) {

			    retcode = CLEAN_ME;

		    } else if (j != OK) {

			/*  The child is bad.  Delete the file.  */
    #ifdef TRACE
			fprintf (stderr,
				 "v_s:  AB has bad lsn, or child is bad\n");
			fflush (stderr);
    #endif
			    retcode = DELETE_ME;
		    }

		    /*
		     * Check the lsnLog field of this block.  Most
		     * ALNODE records' lsnLogs should be the count 
		     * of sectors up to and including that node;  the
		     * last one should be 0xffffffff.
		     */

          if ((i < b->a.alb.cUsed-1) &&
              (palnode->lsnLog != *(ULONG *)name)) {                          // CP13 B730753 - BJH OS/2 Change Team
                  chk_fix_lof (lsn, b, palnode, *(ULONG *)name, ABF_NODE);    // CP13 B730753 - BJH OS/2 Change Team
          } else if ((i == b->a.alb.cUsed-1) &&
                   (palnode->lsnLog != 0xffffffff)) {                         // CP13 B730753 - BJH OS/2 Change Team
                  chk_fix_lof (lsn, b, palnode, 0xffffffff, ABF_NODE);        // CP13 B730753 - BJH OS/2 Change Team
          }
		    /*	Move to the next record--node entries
		     *	are two dwords long.
		     */
           palnode++;                                                          // CP13 B730753 - BJH OS/2 Change Team
		}

		FREE_BLOCK (child);
	}
	break;
    }

    RET (retcode);
}
