Source to bsd/hfs/hfscommon/Misc/VolumeCheck.c


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @[email protected]
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @[email protected]
 */

/*
	File:		VolumeCheck.c

	Contains:	Consistency checking code for HFS and HFS Plus volumes.
				This code was based off MtCheck (in TFSVol.a).

	Version:	HFS Plus 1.0

	Copyright:	 1995-1998 by Apple Computer, Inc., all rights reserved.

	File Ownership:

		DRI:				Don Brady

		Other Contact:		xxx put other contact here xxx

		Technology:			xxx put technology here xxx

	Writers:

		(NG)	Nitin Ganatra
		(DSH)	Deric Horn
		(msd)	Mark Day
		(djb)	Don Brady

	Change History (most recent first):

	  <Rhap>	  6/3/98	djb		Switch RenameCatalogNode to MoveRenameCatalogNode.
	  <Rhap>	  4/2/98	djb		FixOrphanFileThreads only works with MacOS.
	  <Rhap>	03/31/98	djb		Sync up with final HFSVolumes.h header file.
	  <Rhap>	03/26/98	djb		Minor cleanup.
	  <Rhap>	03/20/98	djb		Add leaf-order checking back in.  Set USE_BTREE_SCANNER to true
	  								to enable disk order checking.  Add VOLUME_CHECK_LOG messages.
	  								Use PascalBinaryCompare to compare volume names.

	  <CS16>	11/26/97	djb		Radar #2003656 (and others) - remove B-tree header fixing.
		<15>	10/31/97	DSH		Modify so DFA can call without actually writing to the disk,
									added consistencyStatus parameter to MountCheck and fixed bug in
									freeBlocks calculation in CheckBitmapAndHeader().
	  <CS14>	10/23/97	msd		Bug 1685113. Fix incorrect volume header creation dates.
	  <CS13>	 9/18/97	NG		Don't call Gestalt(osAttr) to check if Temp Mem is available.
									Instead, use GetExpandMemProcessMgrExists
	  <CS12>	  9/7/97	djb		Turn off DebugStr at MountCheck's ErrorExit.
	  <CS11>	  9/5/97	msd		Added CheckBitmapAndHeader to account for space used by the
									volume header, alternate volume header, and the allocation file
									(bitmap) itself.
	  <CS10>	  9/4/97	msd		Added CheckAttributes to account for space used by large
									attribute values (the ones that occupy extents).
	   <CS9>	 8/29/97	djb		Fixed bug in CheckExtentsOverflow (radar #1675472 and #1675417).
	   <CS8>	 8/19/97	msd		In CheckCatalog, don't increment the counts associated with the
									root directory twice; only do it in the bulk scan. When counting
									folders, don't count the root directory itself.
	   <CS7>	 8/11/97	djb		Changed vcb check of largestCNID from "<" to "<=" (radar
									#1670614). Use thread records when establishing the largest
									CNID.
	   <CS6>	 7/30/97	DSH		Casting for SC, needed for DFA compiles
	   <CS5>	 7/28/97	msd		Use fast new B-Tree scanner. CheckCatalog was using a variable
									before it was defined; the valance computations should have been
									wrong.
	   <CS4>	 7/16/97	DSH		FilesInternal.i renamed FileMgrInternal.i to avoid name
									collision
	   <CS3>	  7/8/97	DSH		Loading PrecompiledHeaders from define passed in on C line
	   <CS2>	 6/12/97	djb		Get in sync with HFS Plus format changes.
	   <CS1>	 4/25/97	djb		first checked in

	 <HFS17>	 4/16/97	djb		Remove unused variables to prevent warnings.
	 <HFS16>	 4/16/97	djb		Always use new B-tree code.
	 <HFS15>	 4/11/97	DSH		use extended VCB fields catalogRefNum, and extentsRefNum.
	 <HFS14>	  4/4/97	djb		Get in sync with volume format changes.
	 <HFS13>	 2/19/97	djb		Use real B-tree control blocks. Removed caching code.
	 <HFS12>	 1/17/97	msd		Fix an illegal cast from one structure type to another in
									CheckExtentsOverflow.
	 <HFS11>	 1/16/97	djb		Fixing extents overflow check for HFS+ volumes.
	 <HFS10>	 1/15/97	djb		Added support for checking HFS+ volumes.
	  <HFS9>	  1/3/97	djb		Added support for large keys.Integrated HFSVolumesPriv.h
									changes.
	  <HFS8>	12/23/96	djb		Fixed bug in CheckExtentsOverflow (bad key).
	  <HFS8>	12/23/96	djb		Fixed bug in CheckExtentsOverflow (key was wrong).
	  <HFS7>	12/19/96	DSH		All refs to VCB are now refs to ExtendedVCB
	  <HFS6>	12/19/96	djb		Updated for new B-tree Manager interface.
	  <HFS5>	12/13/96	djb		Use new Catalog SPI for GetCatalogNode.
	  <HFS4>	 12/4/96	DSH		Precompiled Headers
	  <HFS3>	11/21/96	msd		Removed definition of kHFSSignature since it is now in
									FilesInternal.h.
	  <HFS2>	11/19/96	DSH		Removed FilesPriv.h, moved MountCheck prototype to
									FilesInternal.h.
	  <HFS1>	11/13/96	djb		first checked in

	To do:		
			- If a cnid of 5 is found in extents btree make sure vcb attribute is set correctly
*/
#define	USE_BTREE_SCANNER 0

#if ( PRAGMA_LOAD_SUPPORTED )
        #pragma	load	PrecompiledHeaders
#else
        #if TARGET_OS_MAC
	#include	<Gestalt.h>
	#include	<Errors.h>
	#include	<ExpandMemPriv.h>
	#include	<Files.h>
	#include	<StringComparePriv.h>
	#include	<Types.h>
        #else
	#include "../headers/system/MacOSTypes.h"
    #include "../headers/system/MacOSStubs.h"
        #endif 	/* TARGET_OS_MAC */
#endif	/* PRAGMA_LOAD_SUPPORTED */

#include	"../headers/FileMgrInternal.h"
#include	"../headers/HFSBtreesPriv.h"
#include	"../headers/HFSVolumes.h"
#include	"../headers/BTreesPrivate.h"
#include	"../headers/BTreeScanner.h"


enum {
	kTypeDataFork				= 0x00,
	kTypeResourceFork			= 0xFF,	
	
	kVCBAttrLocked				= 0x8080,			// volume locked (software or hardware)
	kVCBFlagsVolumeDirty		= 0x8000
};


enum {
	kBTreePrevious		= -1,
	kBTreeCurrent		= 0,
	kBTreeNext			= 1
};


enum {
	kHFSOrphanedExtents				= 0x00000001,
	kHFSOverlapingExtents			= 0x00000002,
	kHFSCatalogBTreeLoop			= 0x00000004,
	kHFSExtentsBTreeLoop			= 0x00000008,
	kHFSOrphanedThreadRecords		= 0x00000010,
	kHFSMissingThreadRecords		= 0x00000020,
	kHFSInvalidPEOF					= 0x00000040,
	kHFSInvalidLEOF					= 0x00000080,
	kHFSInvalidValence				= 0x00000100,
	kHFSInvalidBTreeHeader			= 0x00000200,
	kHFSInvalidCatalogRecordType	= 0x00000400,	
	kHFSInconsistentVolumeName		= 0x00000800,

	kHFSMinorRepairsWereMade		= 0x80000000
};

enum {
	kMinimumBtreeNodesForCache		= 100
};

#define	kMDBValidAttributesMask		0x8780

#define	badCatalogErr	-1311


typedef struct {
	Handle	tempBufferHand;
	UInt32	tempBufferSize;
	Ptr		savedBufPtr;
	Ptr		savedGetNode;
	Ptr		savedRelNode;
	Ptr		savedNodeCache;
} BTreeState;


// Macros

//void M_ExitOnError(OSErr result);
//#define		M_ExitOnError( result )		if ( ( result ) != noErr )	goto ErrorExit; else ;

BTreeControlBlock* GetBTreeControlBlock(short refNum);
#define		GetBTreeControlBlock(refNum)	((BTreeControlBlock*) GetFileControlBlock((refNum))->fcbBTCBPtr)

Ptr LMGetVector(Ptr vector);
#define 	LMGetVector(V)					(*(Ptr*) (V))

void LMSetVector(Ptr vector, Ptr address);
#define		LMSetVector(V,A)				(*(Ptr *) (V) = (A))

Boolean HFSEqualString(ConstStr255Param str1, ConstStr255Param str2);
#define		HFSEqualString(s1, s2)			PascalBinaryCompare((s1), (s2))

#if	DIAGNOSTIC
	#ifdef KERNEL
        #include <sys/systm.h>
        #define PRINTIT kprintf
    #else /* !KERNEL */
        #define PRINTIT printf
    #endif /* KERNEL */
	#define VOLUME_CHECK_LOG(s)		PRINTIT("   MountCheck: %s\n",(s))
#else
	#define VOLUME_CHECK_LOG(s)
#endif


#if TARGET_OS_MAC
#define	GET_SCANNER_BUFFER()		LMGetFSVars()->gAttributesBuffer
#define	GET_SCANNER_BUFFER_SIZE()	LMGetFSVars()->gAttributesBufferSize
#endif


#if TARGET_OS_RHAPSODY
#define	GET_SCANNER_BUFFER()		NULL
#define	GET_SCANNER_BUFFER_SIZE()	0
#endif


// local prototypes

static OSErr	CheckCatalog (ExtendedVCB* volume, UInt32* freeBlocks, UInt32 xCatalogBlocks, UInt32* consistencyStatus );

static OSErr	CheckExtentsOverflow ( ExtendedVCB* volume, UInt32 *freeBlocks, UInt32 *xCatalogBlocks, UInt32* consistencyStatus );

static OSErr	CheckAttributes ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 *consistencyStatus );

static OSErr	CheckBitmapAndHeader ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 *consistencyStatus );

static void		VolumeBitMapCheckExtents ( ExtendedVCB* volume, HFSPlusExtentDescriptor* theExtents, UInt32* consistencyStatus );

static OSErr	CheckCreateDate (ExtendedVCB* volume, UInt32* consistencyStatus );

static Boolean	ValidPEOF (	ExtendedVCB* volume, CatalogRecord *file );

#if TARGET_OS_MAC
	static OSErr	FixOrphanFileThreads ( ExtendedVCB* volume, UInt32 maxRecords, UInt32 *repairsDone);
#else
	#define FixOrphanFileThreads(v, m, f)	0
#endif

#if TARGET_OS_RHAPSOY
static Handle	GetTempBuffer (Size size);
#endif /* TARGET_OS_MAC */

static UInt32	CountExtentBlocks (HFSPlusExtentRecord extents, Boolean hfsPlus);

static void		ConvertToHFSPlusExtent (const HFSExtentRecord oldExtents, HFSPlusExtentRecord newExtents);

static OSErr	GetFileExtentRecord( const ExtendedVCB	*vcb, const FCB *fcb, HFSPlusExtentRecord extents);

// glue prototypes

extern OSErr CacheReadInPlace(ExtendedVCB *volume, HIOParam *iopb, UInt32 currentPosition, UInt32 maximumBytes, UInt32 *actualBytes);

extern pascal Ptr LowerBufPtr(long size);


// 68K prototypes

extern OSErr FastGetNode(void);

extern OSErr FastRelNode(void);


/*-------------------------------------------------------------------------------

Routine:	MountCheck

Function:	Do a quick check of the volume meta-data to make sure that
			it is consistent. Minor inconsistencies in the MDB or volume
			bitmap will be repaired all other inconsistencies will be
			detected and reported in the consistencyStatus output.
			
			This check will terminated if any unexpected BTree errors
			are encountered.

Input:		volume				- pointer to volume control block
			consistencyStatus	- ????

Output:		none

Result:		noErr				- success

-------------------------------------------------------------------------------*/
OSErr
MountCheck( ExtendedVCB* volume, UInt32 *consistencyStatus )
{
	OSErr					result;
	UInt32					freeBlocks;
	UInt32					bitmapFreeBlocks;
	UInt32					xCatalogBlocks;


	*consistencyStatus = 0;
	freeBlocks = volume->totalBlocks;	// initially all blocks are free

	if (volume->vcbSigWord == kHFSPlusSigWord)
	{
		//--- make sure the volume header's create date matches the MDB's create date
		VOLUME_CHECK_LOG("checking HFS Plus wrapper's create date...");
		result = CheckCreateDate (volume, consistencyStatus);
		M_ExitOnError(result);
	}

	//--- make sure the allocation file, volume header, and alternate volume header are allocated
	VOLUME_CHECK_LOG("checking allocation file...");
	result = CheckBitmapAndHeader ( volume, &freeBlocks, consistencyStatus );
	M_ExitOnError(result);

	//--- do a quick consistency check of the Extents Overflow b-tree ----------------------------
	VOLUME_CHECK_LOG("checking extents overflow b-tree...");
	result = CheckExtentsOverflow(volume, &freeBlocks, &xCatalogBlocks, consistencyStatus);
	M_ExitOnError(result);

	//--- do a quick consistency check of the Catalog b-tree ------------------------------------
	VOLUME_CHECK_LOG("checking catalog b-tree...");
	result = CheckCatalog( volume, &freeBlocks, xCatalogBlocks, consistencyStatus );
	M_ExitOnError( result );

	//--- do a quick consistency check of the Attributes b-tree ------------------------------------
	result = CheckAttributes( volume, &freeBlocks, consistencyStatus );
	M_ExitOnError( result );

	if( freeBlocks != volume->freeBlocks && volume->vcbAtrb & kVCBAttrLocked )
	{
		*consistencyStatus |= kHFSMinorRepairsWereMade;
		VOLUME_CHECK_LOG("repairing free block count");
	}

	(void) UpdateFreeCount( volume );	// update freeBlocks from volume bitmap

	// make sure there are not any doubly allocated blocks...
	// we are not concerned with orphaned blocks (since this is not fatal)
	
	bitmapFreeBlocks = volume->freeBlocks;
	
	if ( freeBlocks < bitmapFreeBlocks)			// there are doubly allocated blocks!
	{
		*consistencyStatus |= kHFSOverlapingExtents;
		VOLUME_CHECK_LOG("uh oh, there are doubly allocated file blocks!");
		goto ErrorExit;
	}
	else if ( freeBlocks > bitmapFreeBlocks)	// there are orphaned blocks
	{
		*consistencyStatus |= kHFSOrphanedExtents;
		VOLUME_CHECK_LOG("orphaned volume bitmap blocks detected");
	}

	return noErr;
	
ErrorExit:

	VOLUME_CHECK_LOG("a fatal inconsistency was found! :( ");

	return badMDBErr;
}


/*-------------------------------------------------------------------------------

Routine:	VolumeCheckCatalog

Function:	Make sure the name of the root matches the volume name in the MDB.
			Make sure the volume bitmap reflects the allocations of all file
			extents.

Assumption:	

Input:		volume			- pointer to volume control block
			freeBlocks
			xCatalogBlocks
			consistencyStatus

Output:		none

Result:		noErr			- success

-------------------------------------------------------------------------------*/
static OSErr
CheckCatalog ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 xCatalogBlocks, UInt32 *consistencyStatus )
{
	UInt32				maxRecords;
	UInt32				largestCNID;
	UInt32				fileCount;
	UInt16				rootFileCount;			//XXX can this overflow on HFS Plus volumes?
	UInt16				rootDirectoryCount;		//XXX can this overflow on HFS Plus volumes?
	UInt32				directoryCount;
	UInt32				fileThreads, directoryThreads;
	UInt32				fileThreadsExpected;
	UInt32				catalogValence;
	UInt32				recordsFound;
	UInt32				catalogBlocks;
	OSErr				result;
	CatalogNodeData		nodeData;
	CatalogRecord		*record;
	CatalogKey			*key;
	FSSpec				spec;
	UInt16				recordSize;
	BTreeControlBlock*	btree;
	UInt32				hint;
	FCB*				file;
	HFSPlusExtentRecord	extentRecord;
	BTScanState			scanState;
	Boolean				hfsPlus = (volume->vcbSigWord == kHFSPlusSigWord);
	UInt16				operation;
	BTreeIterator		btreeIterator;
	FSBufferDescriptor	btRecord;
	CatalogRecord		catalogRecord;

	fileThreads = fileThreadsExpected = directoryThreads = 0;
	fileCount = directoryCount = 0;
	rootFileCount = rootDirectoryCount = 0;
	catalogValence = 0;
	recordsFound = 0;
	largestCNID = kHFSFirstUserCatalogNodeID;

	file = GetFileControlBlock(volume->catalogRefNum);
	
	result = GetFileExtentRecord(volume, file, extentRecord);
	M_ExitOnError(result);

	catalogBlocks = CountExtentBlocks(extentRecord, hfsPlus);	
	*freeBlocks -= catalogBlocks;
	VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);


	//	Read in the root directory's record so we can perform some special checks	
	hint = 0;
	result = GetCatalogNode(volume, kHFSRootFolderID, NULL, kNoHint, &spec, &nodeData, &hint);
	M_ExitOnError(result);

	if ( DEBUG_BUILD && nodeData.nodeID != kHFSRootFolderID )	// works for small and large folders
		DebugStr("\p CheckCatalog: nodeData.nodeID != kHFSRootFolderID");

//	We'll see the root directory's record later, during the full BTree scan, so don't
//	update the counts here.
//	++recordsFound;
//	catalogValence += nodeData.valence;
	
	/* UUU */
	// make sure the name of the root directory matches the volume name (HFS only)
	if ( !hfsPlus && false /*&& !HFSEqualString( spec.name, volume->vcbVN )*/ )
	{
		if ( volume->vcbAtrb & kVCBAttrLocked )
		{
			*consistencyStatus |= kHFSInconsistentVolumeName;
		}
		else // try and repair it
		{
			if (strlen( volume->vcbVN ) > 0)	// check length of volume name
			{
				if (volume->vcbVN[0] > kHFSMaxVolumeNameChars)	// if name is too long truncate it
					volume->vcbVN[0] = kHFSMaxVolumeNameChars;
					
				if ( (!FORDISKFIRSTAID) || (GetDFAStage() == kRepairStage) )
				{
					// the length of volume name is OK, rename the root directory
					result = MoveRenameCatalogNode(volume, kHFSRootFolderID, NULL, hint, kHFSRootFolderID, volume->vcbVN, &hint);
					M_ExitOnError(result);
				}
				*consistencyStatus |= kHFSMinorRepairsWereMade;
				
				// now we must reset for btree search below!
				result = GetCatalogNode(volume, kHFSRootFolderID, NULL, kNoHint, &spec, &nodeData, &hint);
				M_ExitOnError(result);
			}
			else if ( strlen( spec.name ) > 0 )	// check root name length
			{
				if ( spec.name[0] > kHFSMaxVolumeNameChars )	// if name is too long truncate it
					 spec.name[0] = kHFSMaxVolumeNameChars;
	
				// the root name length is OK, change the volume name to match root name
				BlockMoveData( spec.name, volume->vcbVN, spec.name[0]+1);
				volume->vcbFlags |= kVCBFlagsVolumeDirty;
				*consistencyStatus |= kHFSMinorRepairsWereMade;
			}
		}
		VOLUME_CHECK_LOG("inconsistent volume name (hfs disk)");
	}

	// make sure name locked bit is not set
//	if (record.hfsFolder.finderDirInfo.frFlags & kNameLocked)  //XXX need to use node data, not "record"
//	{
//	}
	
	// Make a conservative estimate of the upper limit of B*-Tree records that
	// could conceivably be encountered in a scan of all the leaf nodes.
	maxRecords = (file->fcbEOF) / sizeof(HFSCatalogFolder);

	if (USE_BTREE_SCANNER)	// scan the tree in physical order (faster)
	{
		result = BTScanInitialize(file, 0, 0, 0, GET_SCANNER_BUFFER(), GET_SCANNER_BUFFER_SIZE(), &scanState);
		if (result != noErr)
			goto ErrorExit;
	}
	else // scan in leaf node order (slower but more reliable)
	{
		operation = kBTreeFirstRecord;	// first leaf record

		key = (CatalogKey*) &btreeIterator.key;

		btRecord.bufferAddress	= record = &catalogRecord;
		btRecord.itemCount		= 1;
		btRecord.itemSize		= sizeof(catalogRecord);
	}


	// visit all the leaf node data records in the catalog
	while ( --maxRecords > 0 )
	{
		if (USE_BTREE_SCANNER)
			result = BTScanNextRecord(&scanState, false, (void**)&key, (void**)&record, (UInt32*) &recordSize);
		else
			result = BTIterateRecord(file, operation, &btreeIterator, &btRecord, &recordSize);
					
		if ( result != noErr )
			break;

		++recordsFound;
		operation = kBTreeNextRecord;

		switch (record->recordType)
		{
			case kHFSFolderRecord:
			{
				catalogValence += record->hfsFolder.valence;
	
				//	Count all directories except the root itself
				if (key->hfs.parentID != kHFSRootParentID)
				++directoryCount;

				//	Count directories directly inside the root
				if (key->hfs.parentID == kHFSRootFolderID)
					++rootDirectoryCount;
	
				if (record->hfsFolder.folderID > largestCNID)
					largestCNID = record->hfsFolder.folderID;
				break;
			}

			case kHFSPlusFolderRecord:
			{
				catalogValence += record->hfsPlusFolder.valence;
	
				//	Count all directories except the root itself
				if (key->hfsPlus.parentID != kHFSRootParentID)
					++directoryCount;

				//	Count directories directly inside the root
				if (key->hfsPlus.parentID == kHFSRootFolderID)
					++rootDirectoryCount;
					
				if (record->hfsPlusFolder.folderID > largestCNID)
					largestCNID = record->hfsPlusFolder.folderID;
				break;
			}

			case kHFSFileRecord:
			{
				++fileCount;
				if ( key->hfs.parentID == kHFSRootFolderID )
					++rootFileCount;
	
				if ( record->hfsFile.fileID > largestCNID )
					largestCNID = record->hfsFile.fileID;
	
				if ( record->hfsFile.flags & kHFSThreadExistsMask )
					++fileThreadsExpected;
	
				// check the blocks allocated to this file (both forks)
				
				ConvertToHFSPlusExtent(record->hfsFile.dataExtents, extentRecord);	
				*freeBlocks -= CountExtentBlocks(extentRecord, false);		
				VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);
		
				ConvertToHFSPlusExtent(record->hfsFile.rsrcExtents, extentRecord);
				*freeBlocks -= CountExtentBlocks(extentRecord, false);		
				VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);

				// check the LEOF and PEOF (both forks)
								
				if ( (record->hfsFile.rsrcLogicalSize > record->hfsFile.rsrcPhysicalSize)	||
					 (record->hfsFile.dataLogicalSize > record->hfsFile.dataPhysicalSize) )
				{
					*consistencyStatus |= kHFSInvalidLEOF;
					VOLUME_CHECK_LOG("inconsistent file leof (not fixed)");
				}

				if ( !ValidPEOF(volume, record) )
				{
					*consistencyStatus |= kHFSInvalidPEOF;
					VOLUME_CHECK_LOG("inconsistent file peof (not fixed)");
				}
				break;
			}

			case kHFSPlusFileRecord:
			{
				UInt32	blockSize = volume->blockSize;

				++fileCount;
				++fileThreadsExpected;	// HFS Plus files always have thread records

				if ( key->hfsPlus.parentID == kHFSRootFolderID )
					++rootFileCount;
	
				if ( record->hfsPlusFile.fileID > largestCNID )
					largestCNID = record->hfsPlusFile.fileID;
		
				// check the blocks allocated to this file (both forks)
				
				*freeBlocks -= CountExtentBlocks(record->hfsPlusFile.dataFork.extents, true);		
				VolumeBitMapCheckExtents(volume, record->hfsPlusFile.dataFork.extents, consistencyStatus);
		
				*freeBlocks -= CountExtentBlocks(record->hfsPlusFile.resourceFork.extents, true);		
				VolumeBitMapCheckExtents(volume, record->hfsPlusFile.resourceFork.extents, consistencyStatus);

				// check the LEOF and PEOF (both forks)
								
				if ( (record->hfsPlusFile.resourceFork.logicalSize.lo > record->hfsPlusFile.resourceFork.totalBlocks * blockSize) ||
					 (record->hfsPlusFile.dataFork.logicalSize.lo > record->hfsPlusFile.dataFork.totalBlocks * blockSize) )
				{
					*consistencyStatus |= kHFSInvalidLEOF;
					VOLUME_CHECK_LOG("inconsistent file leof (not fixed)");
				}

				if ( !ValidPEOF(volume, record) )
				{
					*consistencyStatus |= kHFSInvalidPEOF;
					VOLUME_CHECK_LOG("inconsistent file peof (not fixed)");
				}
				break;
			}

			case kHFSFolderThreadRecord:
			{
				++directoryThreads;
				if ( key->hfs.parentID > largestCNID )		// <CS7>
					largestCNID = key->hfs.parentID;				
				break;
			}

			case kHFSPlusFolderThreadRecord:
			{
				++directoryThreads;
				if ( key->hfsPlus.parentID > largestCNID )		// <CS7>
					largestCNID = key->hfsPlus.parentID;
				break;
			}

			case kHFSFileThreadRecord:
			{
				++fileThreads;
				if ( key->hfs.parentID > largestCNID )		// <CS7>
					largestCNID = key->hfs.parentID;				
				break;
			}

			case kHFSPlusFileThreadRecord:
			{
				++fileThreads;
				if ( key->hfsPlus.parentID > largestCNID )		// <CS7>
					largestCNID = key->hfsPlus.parentID;
				break;
			}
	
			default:
				*consistencyStatus |= kHFSInvalidCatalogRecordType;
				VOLUME_CHECK_LOG("unknown catalog record");
				break;
		}
	
	} // end while
	
	if ( result == noErr )
	{
		*consistencyStatus |= kHFSCatalogBTreeLoop;
		goto ErrorExit;		// punt on loop error
	}
	
	if ( result != fsBTRecordNotFoundErr )
		goto ErrorExit;		// punt on BTree errors


	// Check if calculated totals match the Volume Control Block
	
	if (volume->vcbNxtCNID <= largestCNID)			// <CS7>
	{
		volume->vcbNxtCNID = largestCNID + 1;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		VOLUME_CHECK_LOG("fixed vcbNxtCNID");
	}

	if (volume->vcbFilCnt != fileCount)
	{
		volume->vcbFilCnt = fileCount;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		VOLUME_CHECK_LOG("fixed vcbFilCnt (volume file count)");
	}

	if (volume->vcbDirCnt != directoryCount)
	{
		volume->vcbDirCnt = directoryCount;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		VOLUME_CHECK_LOG("fixed vcbDirCnt (volume directory count)");
	}

	if (!hfsPlus && volume->vcbNmFls != rootFileCount)
	{
		volume->vcbNmFls = rootFileCount;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		VOLUME_CHECK_LOG("fixed vcbNmFls (root file count)");
	}

	if (!hfsPlus && volume->vcbNmRtDirs != rootDirectoryCount)
	{
		volume->vcbNmRtDirs = rootDirectoryCount;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		VOLUME_CHECK_LOG("fixed vcbNmRtDirs (root directory count)");
	}
	
	if ( catalogValence != (fileCount + directoryCount) )
	{
		*consistencyStatus |= kHFSInvalidValence;			// valence inconsistency detected
		VOLUME_CHECK_LOG("valence inconsistency detected (not fixed)");
	}

#if 0	
	if (volume->vcbAtrb & ~kMDBValidAttributesMask)	// check for invalid bits
	{
		volume->vcbAtrb &= kMDBValidAttributesMask;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
	}
#endif
	
	if ( (volume->vcbFlags & kVCBFlagsVolumeDirty) && (volume->vcbAtrb & kVCBAttrLocked) == 0 )
		*consistencyStatus |= kHFSMinorRepairsWereMade;

	if (directoryThreads > (directoryCount + 1))
	{
		*consistencyStatus |= kHFSOrphanedThreadRecords;		// too many directory thread records
		VOLUME_CHECK_LOG("orphaned directory thread records detected (not fixed)");
	}
		
	if (fileThreads > fileThreadsExpected)
	{
		if ( volume->vcbAtrb & kVCBAttrLocked )
		{
			*consistencyStatus |= kHFSOrphanedThreadRecords;	// too many file thread records
			VOLUME_CHECK_LOG("orphaned file thread records detected (not fixed)");
		}
		else if ( !hfsPlus ) // try and repair orphaned thread records
		{
			// If we fix up some threads, see if we can come out even without counting all the file
			// threads again. If we don't, then looks like we have to keep count and do a second
			// pass of file records (gotta do that anyway when we fix the KHFSMissingThreadRecords
			// case below) and to count thread records again with the FixOrphanFileThreads call.
	
			if ( (!FORDISKFIRSTAID) || (GetDFAStage() == kRepairStage) )
			{
				UInt32 repairsDone = 0;
					
				(void) FixOrphanFileThreads(volume, recordsFound, &repairsDone);
				if (repairsDone)
				{
					*consistencyStatus |= kHFSMinorRepairsWereMade;
					VOLUME_CHECK_LOG("orphaned file thread records detected (fixed)");
				}
	
				if (fileThreads > (fileThreadsExpected + repairsDone) )
				{
					*consistencyStatus |= kHFSOrphanedThreadRecords;	// still too many thread records
					VOLUME_CHECK_LOG("orphaned file thread records detected (fix attempt failed)");
				}
			}
		}
	}

	if ((directoryThreads < (directoryCount + 1)) || (fileThreads < fileThreadsExpected))
	{
		*consistencyStatus |= kHFSMissingThreadRecords;		// not enough thread records
		VOLUME_CHECK_LOG("some catalog thread records are missing (not fixed)");
	}

	btree = GetBTreeControlBlock(volume->catalogRefNum);

	// okay, lets see if the blocks used by the catalog looks right
	catalogBlocks += xCatalogBlocks;
	if ( (catalogBlocks * volume->blockSize) != file->fcbPLen )
	{
		*consistencyStatus |= kHFSInvalidPEOF;
		VOLUME_CHECK_LOG("inconsistent catalog file size detected (not fixed)");
	}

	if ( btree->totalNodes != (file->fcbPLen / btree->nodeSize) )
	{
		*consistencyStatus |= kHFSInvalidBTreeHeader;
		VOLUME_CHECK_LOG("inconsistent catalog b-tree node count detected (not fixed)");
	}

	if (btree->leafRecords != recordsFound)
	{
		// We now use the B-tree scanner instead of iterating through all
		// the leaf nodes in order.  So let's be more conservative and not
		// fix the leafRecords field until we can do adequate testing of
		// this new method of visiting nodes. -DJB

		if (USE_BTREE_SCANNER || volume->vcbAtrb & kVCBAttrLocked)
		{
			*consistencyStatus |= kHFSInvalidBTreeHeader;		// invalid record count
			VOLUME_CHECK_LOG("inconsistent catalog b-tree record count detected (not fixed)");
		}
		else // go ahead and make this simple repair!
		{		
			btree->leafRecords = recordsFound;
			btree->flags |= kBTHeaderDirty;						// mark BTreeControlBlock dirty
			*consistencyStatus |= kHFSMinorRepairsWereMade;
			VOLUME_CHECK_LOG("inconsistent catalog b-tree record count detected (fixed)");
		}
	}
	
	if ( (!FORDISKFIRSTAID) || (GetDFAStage() == kRepairStage) )
	{
		if ( btree->flags & kBTHeaderDirty )	// if we made changes, write them out
			(void) BTFlushPath(file);
	}

	return noErr;
	
ErrorExit:
	return badCatalogErr;

} // end CheckCatalog


/*-------------------------------------------------------------------------------

Routine:	CheckExtentsOverflow

Function:	Make sure the volume bitmap reflects the allocations of all extents.

Assumption:	

Input:		volume			- pointer to volume control block
			freeBlocks
			xCatalogBlocks
			consistencyStatus

Output:		none

Result:		noErr			- success

-------------------------------------------------------------------------------*/
static OSErr
CheckExtentsOverflow ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 *xCatalogBlocks, UInt32 *consistencyStatus )
{
	OSErr				result;
	UInt32				maxRecords;
	UInt32				fileID;
	UInt32				largestCNID;
	UInt32				recordsFound; 
	UInt32				extentFileBlocks;
	UInt32				extentRecordBlocks;
	HFSPlusExtentKey	*extentKeyPtr;
	HFSPlusExtentRecord	*extentDataPtr;
	HFSPlusExtentRecord	extentRecord;
	UInt16				recordSize;
	BTreeControlBlock	*btree;
	FCB*				file;
	BTScanState			scanState;
	Boolean				hfsPlus = (volume->vcbSigWord == kHFSPlusSigWord);
	UInt16				operation;
	BTreeIterator		btreeIterator;
	FSBufferDescriptor	btRecord;

	*xCatalogBlocks = 0;  // set this early since its a return value
    largestCNID = kHFSFirstUserCatalogNodeID;
	
	file = GetFileControlBlock(volume->extentsRefNum);

	result = GetFileExtentRecord(volume, file, extentRecord);
	ReturnIfError(result);

	extentFileBlocks = CountExtentBlocks(extentRecord, hfsPlus);
	*freeBlocks -= extentFileBlocks;
	VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);

	// now look at the extent tree...

	// Make a conservative estimate of the upper limit of B*-Tree records that
	// could conceivably be encountered in a scan of all the leaf nodes.
	recordsFound = 0;
	maxRecords = (file->fcbEOF) / (hfsPlus ? sizeof(HFSPlusExtentRecord) : sizeof(HFSExtentRecord));

	if (USE_BTREE_SCANNER)	// scan the tree in physical order (faster)
	{
		result = BTScanInitialize(file, 0, 0, 0, GET_SCANNER_BUFFER(), GET_SCANNER_BUFFER_SIZE(), &scanState);
		if (result != noErr)
			return badMDBErr;
	}
	else // scan in leaf node order (slower but more reliable)
	{
		operation = kBTreeFirstRecord;	// first leaf record
		extentKeyPtr = (HFSPlusExtentKey*) &btreeIterator.key;

		btRecord.bufferAddress	= &extentRecord;
		btRecord.itemCount		= 1;
		btRecord.itemSize		= sizeof(extentRecord);
	}

	// visit all the leaf node data records in the extents B*-Tree
	while ( recordsFound < maxRecords )
	{
		if (USE_BTREE_SCANNER)
			result = BTScanNextRecord(&scanState, false, (void **) &extentKeyPtr, (void **) &extentDataPtr, (UInt32*) &recordSize);
		else
			result = BTIterateRecord(file, operation, &btreeIterator, &btRecord, &recordSize);

		if ( result != noErr )
			break;

		++recordsFound;

		// check the blocks allocated to this extent record
		
		if (USE_BTREE_SCANNER)
		{
			if ( hfsPlus )
				BlockMoveData(extentDataPtr, &extentRecord, sizeof(extentRecord));				// just make a copy
			else
				ConvertToHFSPlusExtent(*((HFSExtentRecord *) extentDataPtr), extentRecord);		// convert it
			}
		else
		{
			operation = kBTreeNextRecord;
			if ( !hfsPlus )
				ConvertToHFSPlusExtent(*(HFSExtentRecord*) &extentRecord, extentRecord);	// convert it in place (since its a copy)
		}

		extentRecordBlocks = CountExtentBlocks(extentRecord, hfsPlus);
		
		*freeBlocks -= extentRecordBlocks;		
		VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);
		
		if ( hfsPlus )
			fileID = extentKeyPtr->fileID;
		else
			fileID = ((HFSExtentKey*) extentKeyPtr)->fileID;
		
		// if these were catalog blocks, we need to keep track 
		if ( fileID == kHFSCatalogFileID )
			*xCatalogBlocks += extentRecordBlocks;
			
		if ( fileID > largestCNID )
			largestCNID = fileID;

	} // end while

	if ( result != fsBTRecordNotFoundErr) 
	{
		VOLUME_CHECK_LOG("extents b-tree has serious problems!");
		
		return badMDBErr;		// punt on error (loop error or BTree error)
	}

	if (volume->vcbNxtCNID < largestCNID)
	{
		volume->vcbNxtCNID = largestCNID + 1;
		volume->vcbFlags |= kVCBFlagsVolumeDirty;
		volume->vcbAtrb |= 1;	// need a constant for this!
	}	
	
	btree = GetBTreeControlBlock(volume->extentsRefNum);

	if ( (extentFileBlocks * volume->blockSize) != file->fcbPLen )
	{
		*consistencyStatus |= kHFSInvalidPEOF;
		VOLUME_CHECK_LOG("inconsistent extents file size detected (not fixed)");
	}

	if ( btree->totalNodes != (file->fcbPLen / btree->nodeSize) )
	{
		*consistencyStatus |= kHFSInvalidBTreeHeader;
		VOLUME_CHECK_LOG("inconsistent extents b-tree node count detected (not fixed)");
	}
	
	if (btree->leafRecords != recordsFound)
	{
		// We now use the B-tree scanner instead of iterating through all
		// the leaf nodes in order.  So let's be more conservative and not
		// fix the leafRecords field until we can do adequate testing of
		// this new method of visiting nodes. -DJB
	
		if (USE_BTREE_SCANNER || volume->vcbAtrb & kVCBAttrLocked)
		{
			*consistencyStatus |= kHFSInvalidBTreeHeader;
			VOLUME_CHECK_LOG("inconsistent extents b-tree record count detected (not fixed)");
		}
		else // go ahead and make this simple repair!
		{
			btree->leafRecords = recordsFound;
			btree->flags |= kBTHeaderDirty;				// mark BTreeControlBlock dirty
			*consistencyStatus |= kHFSMinorRepairsWereMade;
			VOLUME_CHECK_LOG("inconsistent extents b-tree record count detected (fixed)");
		}
	}
	
	if ( (!FORDISKFIRSTAID) || (GetDFAStage() == kRepairStage) )
	{
		if ( btree->flags & kBTHeaderDirty )	// if we made changes, write them out
			(void) BTFlushPath(file);
	}

	return noErr;

} // end CheckExtentsOverflow



/*-------------------------------------------------------------------------------

Routine:	CheckAttributes

Function:	Make sure the volume bitmap reflects the allocations of all attributes
			that occupy extents.

Assumption:	

Input:		volume			- pointer to volume control block
			freeBlocks
			consistencyStatus

Output:		none

Result:		noErr			- success

-------------------------------------------------------------------------------*/
static OSErr
CheckAttributes ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 *consistencyStatus )
{
	OSErr				result;
	UInt32				maxRecords;
	UInt32				recordsFound; 
	UInt32				attributesFileBlocks;
	UInt32				extentRecordBlocks;
	AttributeKey		*attributeKey;
	HFSPlusAttrRecord	*attributeData;
	HFSPlusExtentRecord	extentRecord;
	UInt32				recordSize;
	BTreeControlBlock	*btree;
	FCB*				file;
	BTScanState			scanState;
	Boolean				hfsPlus = (volume->vcbSigWord == kHFSPlusSigWord);

	//
	//	If there isn't an attributes B-tree, there's no work to do
	//
	if (volume->attributesRefNum == 0)
		return noErr;

	file = GetFileControlBlock(volume->attributesRefNum);

	result = GetFileExtentRecord(volume, file, extentRecord);
	ReturnIfError(result);

	attributesFileBlocks = CountExtentBlocks(extentRecord, hfsPlus);
	*freeBlocks -= attributesFileBlocks;
	VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);


	// now look at the attributes tree...

	// Make a conservative estimate of the upper limit of B*-Tree records that
	// could conceivably be encountered in a scan of all the leaf nodes.
	recordsFound = 0;
	maxRecords = (file->fcbEOF) / 8;		// 8 = minimum data length (for zero length inline attribute)

	//	Prepare to scan the tree in physical order
	result = BTScanInitialize(file, 0, 0, 0, GET_SCANNER_BUFFER(), GET_SCANNER_BUFFER_SIZE(), &scanState);
	if (result != noErr)
		return badMDBErr;

	// visit all the leaf node data records in the extents B*-Tree
	while ( recordsFound < maxRecords )
	{
		result = BTScanNextRecord(&scanState, false, (void **) &attributeKey, (void **) &attributeData, &recordSize);

		if ( result != noErr )
			break;

		++recordsFound;

		//	If this block has information about extents, make sure they're allocated
		switch (attributeData->recordType) {
			case kHFSPlusAttrInlineData:
				break;
            case kHFSPlusAttrForkData:
				extentRecordBlocks = CountExtentBlocks(attributeData->forkData.theFork.extents, true);
				*freeBlocks -= extentRecordBlocks;
				VolumeBitMapCheckExtents(volume, attributeData->forkData.theFork.extents, consistencyStatus);
				break;
            case kHFSPlusAttrExtents:
				extentRecordBlocks = CountExtentBlocks(attributeData->overflowExtents.extents, true);
				*freeBlocks -= extentRecordBlocks;
				VolumeBitMapCheckExtents(volume, attributeData->overflowExtents.extents, consistencyStatus);
				break;
			default:
				if (DEBUG_BUILD)
					DebugStr("\pHFS+: Unknown attribute record");
				break;
		}	// end switch
	} // end while

	if ( result != fsBTRecordNotFoundErr )
	{
		VOLUME_CHECK_LOG("extents b-tree has serious problems!");
		return badMDBErr;		// punt on error (loop error or BTree error)
	}

	btree = GetBTreeControlBlock(volume->attributesRefNum);

	if ( (attributesFileBlocks * volume->blockSize) != file->fcbPLen )
		*consistencyStatus |= kHFSInvalidPEOF;

	if ( btree->totalNodes != (file->fcbPLen / btree->nodeSize) )
		*consistencyStatus |= kHFSInvalidBTreeHeader;
	
	if (btree->leafRecords != recordsFound)
	{
		// We now use the B-tree scanner instead of iterating through all
		// the leaf nodes in order.  So let's be more conservative and not
		// fix the leafRecords field until we can do adequate testing of
		// this new method of visiting nodes. -DJB

		if (USE_BTREE_SCANNER || volume->vcbAtrb & kVCBAttrLocked)
		{
			*consistencyStatus |= kHFSInvalidBTreeHeader;
		}
		else // go ahead and make this simple repair!
		{
			btree->leafRecords = recordsFound;
			btree->flags |= kBTHeaderDirty;				// mark BTreeControlBlock dirty
			*consistencyStatus |= kHFSMinorRepairsWereMade;
		}
	}
	
	if ( (!FORDISKFIRSTAID) || (GetDFAStage() == kRepairStage) )
	{
		if ( btree->flags & kBTHeaderDirty )	// if we made changes, write them out
			(void) BTFlushPath(file);
	}
	return noErr;

} // end CheckAttributes



/*-------------------------------------------------------------------------------

Routine:	CheckBitmapAndHeader

Function:	Make sure the volume header, alternate volume header, and the bitmap
			itself are marked as used in the bitmap.

Assumption:	

Input:		volume			- pointer to volume control block
			freeBlocks
			consistencyStatus

Output:		none

Result:		noErr			- success

-------------------------------------------------------------------------------*/
static OSErr
CheckBitmapAndHeader ( ExtendedVCB *volume, UInt32 *freeBlocks, UInt32 *consistencyStatus )
{
	OSErr				result;
	UInt32				blocksUsed;
	FCB*				file;
	HFSPlusExtentRecord	extentRecord;

	//
	//	If there isn't an allocation file, there's no work to do
	//
	if (volume->allocationsRefNum == 0)
		return noErr;

	file = GetFileControlBlock(volume->allocationsRefNum);

	result = GetFileExtentRecord(volume, file, extentRecord);
	ReturnIfError(result);

	//	Mark the space used by the allocation file (bitmap) itself
	blocksUsed = CountExtentBlocks(extentRecord, true);
	*freeBlocks -= blocksUsed;
	VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);

	//	Now mark sectors zero through two (boot blocks and volume header)
	//	and the last two sectors (alternate volume header and a reserved
	//	sector).  The number of allocation blocks depends on the size of
	//	an allocation block.  What we do is create a fake extent record
	//	for these blocks, and call VolumeBitMapCheckExtents as usual.

	ClearMemory(&extentRecord, sizeof(extentRecord));
	
	extentRecord[0].startBlock = 0;
	extentRecord[0].blockCount = 1 + (1024 / volume->blockSize);	// sectors 0-2

	if (volume->blockSize == 512) {
		extentRecord[1].startBlock = volume->totalBlocks - 2;
		extentRecord[1].blockCount = 2;
	} else {
		extentRecord[1].startBlock = volume->totalBlocks - 1;
		extentRecord[1].blockCount = 1;
	}
	
	*freeBlocks -= extentRecord[0].blockCount + extentRecord[1].blockCount;
	VolumeBitMapCheckExtents(volume, extentRecord, consistencyStatus);

	return noErr;
}


//	For Disk First Aid, this routine will not update the BitMap, but will set the
//	kHFSMinorRepairsWereMade bit to indicate the bitmap needs updating.
static void VolumeBitMapCheckExtents (
							ExtendedVCB*			volume,
							HFSPlusExtentDescriptor*	theExtents,
							UInt32*					consistencyStatus )
{
	if ( BlockCheck(volume, theExtents) == -1 )
	{
		*consistencyStatus |= kHFSMinorRepairsWereMade;
		VOLUME_CHECK_LOG("repairing volume bitmap");
	}
}


/*-------------------------------------------------------------------------------

Routine:	ValidPEOF

Function:	Make sure the local extents match the PEOF.
			Currently this only checks the local extent.
			We could also check the oveflow extents for
			a more complete (but slower) test.

Assumption:	

Input:		

Output:		

Result:	

-------------------------------------------------------------------------------*/

static Boolean
ValidPEOF( ExtendedVCB* volume, CatalogRecord *file )
{
	UInt32	minPEOF;
	UInt32 	allocBlkSize = volume->blockSize;
	
	if (file->recordType == kHFSPlusFileRecord)
	{
		UInt32	minBlocks;

		minBlocks = CountExtentBlocks(file->hfsPlusFile.resourceFork.extents, true);
		
		if ( file->hfsPlusFile.resourceFork.totalBlocks < minBlocks )
			return false;
		else if ( (file->hfsPlusFile.resourceFork.totalBlocks > minBlocks) && (file->hfsPlusFile.resourceFork.extents[kHFSPlusExtentDensity-1].blockCount == 0) )
			return false;
	
		minBlocks = CountExtentBlocks(file->hfsPlusFile.dataFork.extents, true);
		
		if ( file->hfsPlusFile.dataFork.totalBlocks < minBlocks )
			return false;
		else if ( (file->hfsPlusFile.dataFork.totalBlocks > minBlocks) && (file->hfsPlusFile.dataFork.extents[kHFSPlusExtentDensity-1].blockCount == 0) )
			return false;
	}
	else
	{
		HFSPlusExtentRecord	extentRecord;
		
		ConvertToHFSPlusExtent(file->hfsFile.rsrcExtents, extentRecord);	
		minPEOF = CountExtentBlocks(extentRecord, false) * allocBlkSize;
		
		if ( file->hfsFile.rsrcPhysicalSize < minPEOF )
			return false;
		else if ( (file->hfsFile.rsrcPhysicalSize > minPEOF) && (extentRecord[kHFSExtentDensity-1].blockCount == 0) )
			return false;
	

		ConvertToHFSPlusExtent(file->hfsFile.dataExtents, extentRecord);	
		minPEOF = CountExtentBlocks(extentRecord, false) * allocBlkSize;
		
		if ( file->hfsFile.dataPhysicalSize < minPEOF )
			return false;
		else if ( (file->hfsFile.dataPhysicalSize > minPEOF) && (extentRecord[kHFSExtentDensity-1].blockCount == 0) )
			return false;
	}

		
	return true;
}


/*-------------------------------------------------------------------------------

Routine:	FixOrphanFileThreads

Function:	For orphan thread records during VolumeCheckConsistency have the catalog
			searched again for file records that match. If their thread record flag
			is off, mark it. If it is on, do nothing. If the file record doesn't exist,
			delete the thread record.

Assumption:	This is a function to be called only if the thread records detected during
			CheckCatalog was greater than the file records that had their flag set for
			file thread existance. I've thought of doing this differently, like keeping
			a table of all file records or threads scanned in the first pass, but the
			table gets large. You can't predict how large so may to hold upto the max
			fileIDs possible. (2**32). Using two such tables to match file threads
			against file records would be bad. Less space would be keeping a bitmap of
			all fileIDS (to 2**32) and marking those with threads. Then a separate
			bitmap for those file records with thread flags on marked.You do a byte by
			byte comparison across the whole bitmap. When they differ, you know if you're
			missing
			threads or orphans threads. (Note 0s would also mean IDs represented by that
			bit in the bitmap that is non-existant). Though, this sounds clever, it still
			seems it would take 8bits/byte * 512 bytes/sector * 8 sectors/ vmpage = 2**15
			bits per vmpage and 2**17 vmpages to cover the bitmap! Too large. SO, let's
			just scan the catalog again. Yep, it's timeconsuming, but we are only taking
			the hit in repair mode for that particular problem. No extra memory for tables
			or bitmaps are even required during the original scan.

Input:		
								
Output:		

Result:		E_NoError				- success
			!= E_NoError			- failure

-------------------------------------------------------------------------------*/
#if TARGET_OS_MAC
static OSErr FixOrphanFileThreads(ExtendedVCB* volume, UInt32 maxRecords, UInt32 *repairsDone)
{
	UInt16				recordSize;
	OSErr				tempResult, result = noErr;
	CatalogKey			key;
	FSSpec				spec;
	CatalogRecord		record;
	CatalogNodeData		nodeData;
	UInt32				hint = 0;
		
	// let's iterate the catalog looking for file thread records. As we hit each one, 
	// do a search for the file record. If it exists and the thread flag is off,set
	// it. If it exists, and the thread flag is on, all is fine and dandy. It it
	// doesn't exist, remove the orphaned thread record. 
	
	*repairsDone = 0;
	
	result = GetCatalogNode(volume, kHFSRootFolderID, NULL, kNoHint, &spec, &nodeData, &hint);
	M_ExitOnError(result);
	
	// visit all the leaf node data records in the catalog
	while ( --maxRecords > 0 )  // starts out at recordsFound value passed in
	{
		tempResult = GetBTreeRecord(volume->catalogRefNum, kBTreeNext, &key, &record,
								 &recordSize, &hint );
		if ( tempResult )
		{
			//	DebugStr("\p FixOrphanFileThreads: did we run out of records?");
			break;
		}
		
		// if this is a file thread record, check it out...
		if (record.recordType == kHFSFileThreadRecord)
		{
			CatalogKey	currentKey;
			CatalogKey	fileKey;
			UInt32		fileHint;
				
			// save our current position
			currentKey = key;	
			
			// create a key for the file record
			fileHint = 0;
			fileKey.hfs.parentID = record.hfsThread.parentID;
			BlockMoveData (	record.hfsThread.nodeName, fileKey.hfs.nodeName,
							record.hfsThread.nodeName[0] + 1);
			
			// try and get the file record for this thread record...
			tempResult = SearchBTreeRecord ( volume->catalogRefNum, &fileKey, fileHint, &key, &record, &recordSize, &fileHint );
				
			if (tempResult == noErr)
			{		
				if ((record.hfsFile.flags & kHFSThreadExistsMask) == 0)
				{
					// thread record exists but file record doesn't think so....set it
					record.hfsFile.flags |= kHFSThreadExistsMask;
					
					// update the file record on disk
					if ( ReplaceBTreeRecord(volume->catalogRefNum, &key, fileHint, &record, recordSize, &fileHint) == noErr )		
						++(*repairsDone);
				}
			}		
			else if (tempResult == btNotFound)
			{
				// BTDelete invalidates the current node mark so before we delete the thread record,
				// point currentKey and hint at something valid (ie the previous record)
				
				(void) SearchBTreeRecord( volume->catalogRefNum, &currentKey, hint, &key, &record, &recordSize, &hint );
				(void) GetBTreeRecord( volume->catalogRefNum, kBTreePrevious, &key, &record, &recordSize, &hint );

				// file record doesn't exist. Delete the thread record.
				if ( DeleteBTreeRecord( volume->catalogRefNum, &currentKey) == noErr )
					++(*repairsDone);  
				
				currentKey = key;	// now point to the record before the thread record
			}

			// now we need to reset the current node mark for GetBTreeRecord iteration...
			(void) SearchBTreeRecord( volume->catalogRefNum, &currentKey, hint, &key, &record, &recordSize, &hint );

		} // end if thread
	
	} // end while
	
	return( result );
ErrorExit:
	return ( result ); //XXX which error should it be ???
} // end FixOrphanFileThreads
#endif /* TARGET_OS_MAC */

//_______________________________________________________________________
//
//	GetTempBuffer
//	
//	This routine allocates a temporary buffer.
//
//	Note: At startup TempNewHandle may not be available.
//_______________________________________________________________________
#if TARGET_OS_MAC
static Handle GetTempBuffer (Size size)
{
	OSErr	error;
	Handle	tempBuffer;

	if ( GetExpandMemProcessMgrExists() )
		tempBuffer = TempNewHandle(size, &error);
	else
		tempBuffer = NewHandleSys(size);
	
	return tempBuffer;
}
#endif 	/* TARGET_OS_MAC */



static UInt32
CountExtentBlocks(HFSPlusExtentRecord extents, Boolean hfsPlus)
{
	UInt32	blocks;
	
	// grab the first 3
	blocks = extents[0].blockCount + extents[1].blockCount + extents[2].blockCount;

	if (hfsPlus)
	{
		UInt32	i;
		
		for (i = 3; i < kHFSPlusExtentDensity; ++i)
			blocks += extents[i].blockCount;		// HFS Plus has additional extents
	}

	return blocks;
}


static void
ConvertToHFSPlusExtent( const HFSExtentRecord oldExtents, HFSPlusExtentRecord newExtents)
{
	UInt16	i;

	// go backwards so we can convert in place!
	
	for (i = kHFSPlusExtentDensity-1; i > 2; --i)
	{
		newExtents[i].blockCount = 0;
		newExtents[i].startBlock = 0;
	}

	newExtents[2].blockCount = oldExtents[2].blockCount;
	newExtents[2].startBlock = oldExtents[2].startBlock;
	newExtents[1].blockCount = oldExtents[1].blockCount;
	newExtents[1].startBlock = oldExtents[1].startBlock;
	newExtents[0].blockCount = oldExtents[0].blockCount;
	newExtents[0].startBlock = oldExtents[0].startBlock;
}


static OSErr
GetFileExtentRecord( const ExtendedVCB	*vcb, const FCB *fcb, HFSPlusExtentRecord extents)
{
	OSErr				err;
	ExtendedFCB			*extendedFCB;

	err = noErr;
	
	if (vcb->vcbSigWord == kHFSSigWord)
	{
		ConvertToHFSPlusExtent(fcb->fcbExtRec, extents);
	}
	else
	{
/* XXX PPD: Is there any reason this isn't the same for EVERY platform? */
#if TARGET_OS_MAC
		extendedFCB = ParallelFCBFromRefnum( GetFileRefNumFromFCB(fcb) );
#else
		extendedFCB = GetParallelFCB( GetFileRefNumFromFCB(fcb) );
#endif

		if (extendedFCB == NULL)
		{
			if ( DEBUG_BUILD )
				DebugStr("\pFATAL: Extended FCB not found!");
			err = fsDSIntErr;
		}
		else
		{
			BlockMoveData(extendedFCB->extents, extents, sizeof(HFSPlusExtentRecord));
		}
	}
	
	return err;
}



//_______________________________________________________________________
//
//	CheckCreateDate
//	
//	For HFS Plus volumes, make sure the createDate in the VolumeHeader
//	is the same as in the MDB.  If not, just fix it.
//
//_______________________________________________________________________

static OSErr CheckCreateDate (ExtendedVCB* volume, UInt32* consistencyStatus )
{
	OSErr					err;
	HFSMasterDirectoryBlock	*mdb;
	UInt32					createDate;

	return noErr;
	
	//
	//	Read in the MDB
	//
#if TARGET_OS_MAC
        err = GetBlock_glue(gbReleaseMask, 2, (Ptr *) &mdb, volume->vcbVRefNum, volume);
#else
        err = GetBlock_glue(gbReleaseMask, 2, (Ptr *) &mdb, kNoFileReference, volume);
#endif
	if (err != noErr) return err;
	
	//
	//	Make sure it really is a wrappered volume with an MDB
	//
	if (mdb->drSigWord != kHFSSigWord || mdb->drEmbedSigWord != kHFSPlusSigWord)
		return noErr;			// must not be a wrapper, so nothing to fix
	
	createDate = mdb->drCrDate;
	
	if (volume->vcbCrDate != createDate) {
		volume->vcbCrDate = createDate;					// fix create date in the VCB
		volume->vcbFlags |= kVCBFlagsVolumeDirty;		// make it dirty so change will be written to volume header
		*consistencyStatus |= kHFSMinorRepairsWereMade;	// the problem is now fixed
	}
	
	return noErr;
}