Source to bsd/hfs/hfscommon/Catalog/FileIDsServices.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:		FileIDServices.c

	Contains:	File ID manipulating routines.

	Version:	HFS Plus 1.0

	Written by:	Deric Horn

	Copyright:	 1996-1999 by Apple Computer, Inc., all rights reserved.

	File Ownership:

		DRI:				Deric Horn

		Other Contact:		xxx put other contact here xxx

		Technology:			xxx put technology here xxx

	Writers:

		(JL)	Jim Luther
		(msd)	Mark Day
		(djb)	Don Brady
		(DSH)	Deric Horn

	Change History (most recent first):
	  <Rhap>	 3/2/98		djb		Fix extents corruption bug in MoveExtents (radar #2309434).
	  <Rhap>	11/20/98	djb		Add support for UTF-8 names.
	  <Rhap>	  4/2/98	djb		Switch over to real BTree interface in MoveExtents and DeleteExtents.
	  <Rhap>	 3/31/98	djb		Sync up with final HFSVolumes.h header file.

	  <CS21>	11/17/97	djb		PrepareInputName routine now returns an error.
	  <CS20>	11/13/97	djb		Radar #2001699 ResolveFileID needs to use CMNotFound error.
	  <CS19>	10/31/97	JL		#2000184 - CreateFileThreadID and ExchangeFiles now return the
									WDCBRecPtr or NULL for external file systems. ExchangeFiles no
									longer returns length of FCB table to caller since that wasn't
									ever needed.
		<18>	10/23/97	DSH		1685058, Fix ExchangeFiles by invalidating the node cache before
									switching the files.
	  <CS17>	10/19/97	msd		Bug 1684586. GetCatInfo and SetCatInfo use only contentModDate.
	  <CS16>	10/16/97	DSH		Return badFidErr in ResolveFileID if LocateCatalogThread fails
	  <CS15>	10/15/97	DSH		CreateFileThreadID(), remap btExists to fidExists.
	  <CS14>	  9/7/97	djb		Turn off some DebugStr calls.
	  <CS13>	  9/4/97	msd		Remove call to PropertyExchangeObjects.
	  <CS12>	 8/14/97	djb		Remove hard link support.
	  <CS11>	 7/18/97	msd		Include LowMemPriv.h.
	  <CS10>	 7/16/97	DSH		FilesInternal.i renamed FileMgrInternal.i to avoid name
									collision
	   <CS9>	  7/8/97	DSH		Loading PrecompiledHeaders from define passed in on C line
	   <CS8>	 6/24/97	djb		Add hard link support to ResolveFileID and CreateFileIDRef.
	   <CS7>	 6/20/97	msd		Use contentModDate and attributeModDate fields instead of
									modifyDate.
	   <CS6>	 6/13/97	djb		Switch over from PrepareOutputName to ConvertUnicodeToHFSName.
									PrepareInputName now takes an encoding.
	   <CS5>	 5/28/97	msd		Move the declaration of FindFileName to FilesInternal.i.
	   <CS4>	 5/19/97	djb		No longer need to invalidate vcbDirIDM field.
	   <CS3>	 5/16/97	msd		In ExchangeFiles, change srcNamePtr from char * to StringPtr
									(fixes warnings).
	   <CS2>	 4/28/97	djb		(DSH) Added VolumeWritable check back into CreateFileIDThread.
	   <CS1>	 4/24/97	djb		first checked in
	 <HFS23>	 4/11/97	DSH		Use extended VCB fields catalogRefNum, and extentsRefNum.
	 <HFS22>	  4/9/97	msd		Rewrite CreateFileThreadID so that it properly handles
									pathnames, and doesn't overwrite the ioNamePtr. The data field
									of FindFileNameGlueRec points to a CatalogNodeData, not
									CatalogRecord.
	 <HFS21>	  4/4/97	djb		Get in sync with volume format changes.
	 <HFS20>	 3/31/97	djb		Change ClearMem to ClearMemory.
	 <HFS19>	 3/17/97	DSH		C_FlushCache prototype to FilesInternal.h
	 <HFS18>	  3/5/97	msd		ExchangeFiles needs to call PropertyExchangeObjects.
	 <HFS17>	 2/13/97	msd		Fix MoveExtents and DeleteExtents to work with HFS+ extent
									records.
	 <HFS16>	 1/31/97	msd		In MoveExtents, when a record isn't found and you want the next
									record in order, use the "next record" offset = 1 instead of
									"current record" offset = 0. DeleteExtents would always exit
									without doing anything because it was searching for an invalid
									key. Removed several DebugStrs that were used as cheap code
									coverage.
	 <HFS15>	 1/15/97	DSH		Resolve wasn't passing the name back for HFS
	 <HFS14>	 1/13/97	djb		LocateCatalogThread now passes back the thread record size.
	 <HFS13>	 1/11/97	DSH		HFS+, fixed some Unicode/Pascal strings related bugs for use on
									HFS+ volumes.
	 <HFS12>	  1/9/97	DSH		Fix ExchangeFiles extents
	 <HFS11>	  1/6/97	DSH		pass VCB in CloseFile() routine.
	 <HFS10>	  1/6/97	djb		Fixed ResolveFileID - it was not returning a directory ID!
	  <HFS9>	  1/3/97	msd		Fix prototype for C_FlushCache. Fix prototype for
									TrashFileBlocks.
	  <HFS8>	  1/3/97	djb		Integrate latest HFSVolumesPriv.h changes.
	  <HFS7>	  1/2/97	DSH		C port of ExchangeFileIDs
	  <HFS6>	12/20/96	djb		Fixed bug in CreateFileID.
	  <HFS5>	12/19/96	DSH		All refs to VCB are now refs to ExtendedVCB
	  <HFS4>	12/19/96	msd		Use kFileThreadExistsMask (from HFSVolumesPriv.h) instead of
									kFileThreadMask (from FilesInternal.h) since the latter was
									incorrectly defined and has now been removed.
	  <HFS3>	12/19/96	djb		Updated for new B-tree Manager interface.
	  <HFS2>	12/18/96	msd		GetFileThreadID was using a bitwise-OR (|) instead of
									bitwise-AND (&) to test for a bit being set.
	  <HFS1>	12/12/96	DSH		first checked in

*/

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

#include	"../headers/FileMgrInternal.h"
#include	"../headers/HFSVolumes.h"
#include	"../headers/system/HFSUnicodeWrappers.h"
#include	"../headers/CatalogPrivate.h"


struct ExtentsRecBuffer {
	ExtentKey		extentKey;
	ExtentRecord	extentData;
};
typedef struct ExtentsRecBuffer ExtentsRecBuffer;


OSErr	CreateFileID( ExtendedVCB *vcb, HFSCatalogNodeID fileID, CatalogName *name, HFSCatalogNodeID *threadID );
OSErr	GetFileThreadID( ExtendedVCB *vcb, HFSCatalogNodeID id, const CatalogName *name, Boolean isHFSPlus, UInt32 *threadID );

UInt32	CheckExtents( void *extents, UInt32 blocks, Boolean isHFSPlus );
OSErr	DeleteExtents( ExtendedVCB *vcb, UInt32 fileNumber, Boolean isHFSPlus );
OSErr	MoveExtents( ExtendedVCB *vcb, UInt32 srcFileID, UInt32 destFileID, Boolean isHFSPlus );
void	CopyCatalogNodeInfo( CatalogRecord *src, CatalogRecord *dest );
void	CopyBigCatalogNodeInfo( CatalogRecord *src, CatalogRecord *dest );

void	CopyExtentInfo( ExtentKey *key, ExtentRecord *data, ExtentsRecBuffer *buffer, UInt16 bufferCount );
extern	void	TrashFileBlocks( ExtendedVCB *vcb, UInt32 fileNumber );



//
//	Routine:	ResolveFileID from Asm : FIDResolveID
//
//	Function: 	ResolveFileID finds the dirID and CName for a file, given it's thread.
//
// 	Output:		OSErr				err
//				Str31				name - resolved name
//				HFSCatalogNodeID	parentID - resolved parent ID
//

#if TARGET_OS_MAC

OSErr	ResolveFileID( ExtendedVCB *vcb, HFSCatalogNodeID fileID, HFSCatalogNodeID *parentID, Str31 name )
{
	OSErr			err;
	CatalogRecord	threadData;
	CatalogKey		key;
	CatalogRecord	data;
	UInt32 			threadHint;
	UInt32 			tempHint;
	UInt16			threadSize;
	Boolean			isHFSPlus;
	
	
	err = CheckVolumeOffLine( vcb );
	ReturnIfError( err );

	isHFSPlus = ( vcb->vcbSigWord == kHFSPlusSigWord );
	
	//-- if it's not an HFS Plus volume, make sure it's an HFS volume
	if ( (! isHFSPlus) && (vcb->vcbSigWord != kHFSSigWord) )
		return( wrgVolTypErr );

	threadHint = kNoHint;
	
	//-- Locate the file thread
	
	err = LocateCatalogThread( vcb, fileID, &threadData, &threadSize, &threadHint );	//	This call returns nodeName as either Str31 or HFSUniStr255, no need to call PrepareInputName()
	if ( err != noErr )
		return( cmNotFound );	//	treat it as fidNotFound error
	

	if ( (threadData.recordType != kHFSFileThreadRecord) && (threadData.recordType != kHFSPlusFileThreadRecord) )
		return( cmFThdDirErr );
		
		
	isHFSPlus = ( vcb->vcbSigWord == kHFSPlusSigWord );

	if ( isHFSPlus )
	{
		TextEncoding	encoding;
		ByteCount actualDstLen;

		//-- Check if the file exists

		err = LocateCatalogNode( vcb, threadData.hfsPlusThread.parentID, (const CatalogName *) &(threadData.hfsPlusThread.nodeName), kNoHint, &key, &data, &tempHint);

		//-- We found the file, but is this the file we really want?

		if ( err || data.hfsPlusFile.fileID != fileID )
			return( badFidErr );

		encoding = data.hfsPlusFile.textEncoding;	// note: text encoding and fileID is at same offset for folders

		//	Copy the name back for output
		err = ConvertUnicodeToUTF8(	threadData.hfsPlusThread.nodeName.length * sizeof(UniChar),
									threadData.hfsPlusThread.nodeName.unicode,
									NAME_MAX+1,
					 				&actualDstLen,
									name);
		*parentID = threadData.hfsPlusThread.parentID;
	}
	else
	{
		//-- Check if the file exists
		//		No need to prepare name for in/output, it will stay a Str31 in HFS
	
		err = LocateCatalogNode( vcb, threadData.hfsThread.parentID, (const CatalogName *) &(threadData.hfsThread.nodeName), kNoHint, &key, &data, &tempHint );

		//-- We found the file, but is this the file we really want?
		
		if ( err || data.hfsFile.fileID != fileID )
			return( badFidErr );

		//	Copy the name back for output
		BlockMoveData( threadData.hfsThread.nodeName, name, StrLength(threadData.hfsThread.nodeName) + 1 );
		*parentID = threadData.hfsThread.parentID;
	}
	
	return( noErr );
}

#endif	/* TARGET_OS_MAC */


//
//	Routine:	GetFileThreadID from Asm : FIDGetID
//
//	Function: 	GetFileThreadID returns a file thread to a file if it exists.
//
//	Input:		ExtendedVCB*		-  VCB pointer
//				HFSCatalogNodeID	-  DirID
//				CatalogName			-  CName pointer
//
//	Output:		OSErr	-  result code
//							 0 = ok
//							 CMnotfound = CNode not found
//							 cmFThdGone = File thread not found
//							 cmFThdDirErr = file is a directory, not a file
//							 -n = IO Error
//				UInt32	-  file thread ID
//

#if TARGET_OS_MAC

OSErr	GetFileThreadID( ExtendedVCB *vcb, HFSCatalogNodeID id, const CatalogName *name, Boolean isHFSPlus, UInt32 *threadID )
{
	OSErr			err;
	CatalogKey		key;
	CatalogRecord	data;
	UInt32 			hint;
	

	*threadID = 0;
	
	//-- Locate the file thread
	
	err = LocateCatalogNode( vcb, id, name, kNoHint, &key, &data, &hint );
	ReturnIfError( err );
	
	if ( isHFSPlus )
	{
		if ( data.recordType != kHFSPlusFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"
		
		*threadID = data.hfsPlusFile.fileID;
	}
	else
	{
		if ( data.recordType != kHFSFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"

		if ( (data.hfsFile.flags & kHFSThreadExistsMask) != 0 )
			*threadID = data.hfsFile.fileID;
		else
			err = cmFThdGone;
	}
	
	return( err );
}
#endif	/* TARGET_OS_MAC */



#if TARGET_OS_MAC
OSErr	CreateFileThreadID( FIDParam *filePB, WDCBRecPtr *wdcbPtr )
{
	OSErr				err;
	Boolean				isHFSPlus;
	UInt32				fileHint;
	UInt32				threadHint;
	Str31				name;
	FindFileNameGlueRec	fileInfo;
	HFSCatalogKey		threadKey;
	HFSCatalogThread	threadData;
	CatalogKey			fileKey;
	CatalogRecord		fileData;
	
	
	err = FindFileName( (ParamBlockRec *) filePB, &fileInfo );
	*wdcbPtr = fileInfo.wdcb;	// return the WDCB pointer to the glue so it can set up A3.
	if (err != noErr)	goto ErrorExit;
	
	isHFSPlus = ( fileInfo.vcb->vcbSigWord == kHFSPlusSigWord );

	//
	//	Make sure we just found a file record.
	//
	if (fileInfo.data->nodeType != kCatalogFileNode) {
		err = notAFileErr;
		goto ErrorExit;
	}
	
	//
	//	Figure out which FileID this is, and return it.
	//
	filePB->ioFileID = fileInfo.data->nodeID;
	
	//
	//	If the volume is HFS, see if we need to create the thread record.  For HFS Plus, there
	//	is always a thread record.
	//

	if (isHFSPlus || fileInfo.data->nodeFlags & kFileThreadExistsMask) {
		err = fidExists;
		goto ErrorExit;
	}

	err = VolumeWritable( fileInfo.vcb );
	if ( err != noErr )
		goto ErrorExit;

	
	//
	//	If we get here, we need to create a thread record on an HFS volume.
	//	Start by building finding the original catalog record, so we can get
	//	its key with correct capitalization, etc.
	//
	// Is there any way to get the actual key back from FindFileName?
	//
	name[0] = fileInfo.nameLength;
	BlockMoveData(fileInfo.nameBuffer, &name[1], fileInfo.nameLength);
	err = LocateCatalogNode( fileInfo.vcb, fileInfo.id, (CatalogName *) &name, fileInfo.hint, &fileKey, &fileData, &fileHint );
	if (err != noErr)	goto ErrorExit;
		
	//
	//	Build the key and data for the thread record.
	//

	BuildCatalogKey( filePB->ioFileID, NULL, false, (CatalogKey *) &threadKey );

	ClearMemory( (Ptr)&threadData, sizeof(HFSCatalogThread) );
	threadData.recordType	= kHFSFileThreadRecord;
	threadData.parentID	= fileKey.hfs.parentID;
	BlockMoveData(&fileKey.hfs.nodeName[0], &threadData.nodeName[0], fileKey.hfs.nodeName[0]+1);

	//
	//	Insert the thread record
	//
	err = InsertBTreeRecord( fileInfo.vcb->catalogRefNum, &threadKey, &threadData, sizeof(HFSCatalogThread), &threadHint );
	if (err != noErr)
	{
		if ( err == btExists )	err = fidExists;		// Remap btExists to fidExists
		goto ErrorExit;
	}

	//
	//	Finally, set the flag in the file record to say this file has a thread record.
	//
	fileData.hfsFile.flags |= kFileThreadExistsMask;
	err = ReplaceBTreeRecord( fileInfo.vcb->catalogRefNum, &fileKey, fileHint, &fileData, sizeof(HFSCatalogFile), &fileHint );
	if (err != noErr)	goto ErrorExit;

	FlushCatalog( fileInfo.vcb );
	
ErrorExit:

	return err;
}
#endif	/* TARGET_OS_MAC */


//
//	Routine:	DeleteFileID from Asm : FIDDeleteID
//
//	Function: 	DeleteFileID invalidates a file id, by removing the thread from the 
//				from the cat file and by turning off the file CNode link flag.
//
// 	Output:		OSErr	err
//
//
//	We don't know why anyone would use this routine, so in HFS Plus we want to obsolete it
//
//

#if( 0 )
	OSErr	DeleteFileID( ExtendedVCB *vcb, HFSCatalogNodeID fileID )
	{
		OSErr			err;
		OSErr			btError;
		Boolean			isHFSPlus;
		CatalogRecord	*threadData;
		CatalogKey		key;
		CatalogRecord	record;
		UInt32 			threadHint;
		UInt16			threadSize;
		CatalogKey		key;
	
		
		//-- Locate the file thread
	
		threadHint = kNoHint;
		
		err = LocateCatalogThread( vcb, fileID, &threadData, &threadSize, &threadHint );
		
		if ( err != noErr )
		{		//	translate the error so callee knows it wasn't a fnfErr
			if ( err != cmNotFound )
				err = cmFThdGone;
			return( err );
		}
	
		if ( (threadData->recordType != kHFSFileThreadRecord) && (threadData->recordType != kHFSPlussFileThreadRecord) )
			return( cmFThdDirErr );
	
		//-- Find the file
	
		isHFSPlus = ( vcb->vcbSigWord == kHFSPlusSigWord );
	
		if ( isHFSPlus )
		{
			err = LocateCatalogNode( vcb, threadData->hfsPlusThread.parentID, (const CatalogName *) &(threadData->hfsPlusThread.nodeName), kNoHint, &key, &record, &threadHint );
	
			if ( err == noErr )
			{		//	Turn off thread flag in file CNode
				record.hfsPlusFile.flags &= ~kFileThreadExistsMask;		//	clear the fThreadFlag bit
				err = ReplaceBTreeRecord( vcb->catalogRefNum, &key, threadHint, &record, sizeof(CatalogThreadRecord), &threadHint);	//	tell the B-Tree
				ReturnIfError( err );
			}
		}
		else
		{
			err = LocateCatalogNode( vcb, threadData->hfsThread.parentID, (const CatalogName *) &(threadData->hfsThread.nodeName), kNoHint, &key, &record, &threadHint );
	
			if ( err == noErr )
			{		//	Turn off thread flag in file CNode
				record.hfsFile.flags &= ~kFileThreadExistsMask;			//	clear the fThreadFlag bit
				err = ReplaceBTreeRecord( vcb->catalogRefNum, &key, threadHint, &record, sizeof(CatThdRec), &threadHint);			//	tell the B-Tree
				ReturnIfError( err );
			}
		}
	
		//--	Delete the file thread in the catalog btree
			
		BuildCatalogKey( fileID, nil, isHFSPlus, &key );
		
		btError = DeleteBTreeRecord( vcb->catalogRefNum, &key );
		ReturnIfError( btError );										//	we know it exists, so must be IOError...
																		//	...Oh well, leave the cnode's fthread flag off.
		//-- Get it out to disk. 
		(void) FlushCatalog( vcb );		
		
		return( err );
	}



#if TARGET_OS_MAC
OSErr	ExchangeFiles( FIDParam *filePB, WDCBRecPtr *wdcbPtr )
{
	FindFileNameGlueRec		srcFileInfo;
	FindFileNameGlueRec		destFileInfo;
	OSErr					err;
	ExtendedVCB				*vcb;
	CatalogName				srcName;
	CatalogName				destName;
	Str31					pSrcName;
	Str31					pDestName;
	CatalogKey				srcKey;
	CatalogRecord			srcData;
	CatalogKey				destKey;
	CatalogRecord			destData;
	HFSCatalogNodeID 		srcFileID;
	HFSCatalogNodeID 		destFileID;
	UInt32					srcHint;
	UInt32					destHint;
	UInt32					srcDirID;
	StringPtr				srcNamePtr;
	Boolean					isHFSPlus;
	Ptr						fcbs;
	UInt16					fcbIndex;
	FCB						*fcb;
	
	err = FindFileName( (ParamBlockRec *) filePB, &srcFileInfo );
	*wdcbPtr = srcFileInfo.wdcb;	// return the WDCB pointer to the glue so it can set up A3.
	ReturnIfError( err );

	srcNamePtr	= filePB->ioNamePtr;
	srcDirID	= filePB->ioSrcDirID;
	
	filePB->ioNamePtr	= filePB->ioDestNamePtr;
	filePB->ioSrcDirID	= filePB->ioDestDirID;

	err = FindFileName( (ParamBlockRec *) filePB, &destFileInfo );

	filePB->ioNamePtr	= srcNamePtr;
	filePB->ioSrcDirID	= srcDirID;

	ReturnIfError( err );

	if ( srcFileInfo.vcb != destFileInfo.vcb )
		return( diffVolErr );

	//-- since the vcb's are the same, let's use a local variable
	vcb = srcFileInfo.vcb;

	err = VolumeWritable( vcb );
	ReturnIfError( err );

	err = CheckVolumeOffLine( vcb );
	ReturnIfError( err );

	isHFSPlus = ( vcb->vcbSigWord == kHFSPlusSigWord );
	
	//-- if it's not an HFS Plus volume, make sure it's an HFS volume
	if ( (! isHFSPlus) && (vcb->vcbSigWord != kHFSSigWord) )
		return( wrgVolTypErr );

	//-- Set up the names into pascal strings
	pDestName[0] = destFileInfo.nameLength;
	BlockMoveData( destFileInfo.nameBuffer, &(pDestName[1]), pDestName[0] );
	
	pSrcName[0] = srcFileInfo.nameLength;
	BlockMoveData( srcFileInfo.nameBuffer, &(pSrcName[1]), pSrcName[0] );
	
	//-- Now prepare the pascal names for input
	err = PrepareInputName( pSrcName, isHFSPlus, GetTextEncodingFromHint(srcFileInfo.hint), &srcName );
	if ( err != noErr ) goto ParseError;

	err = PrepareInputName( pDestName, isHFSPlus, GetTextEncodingFromHint(destFileInfo.hint), &destName );
	if ( err != noErr ) goto ParseError;

	//--	locate the source file
	err = LocateCatalogNode( vcb, srcFileInfo.id, &srcName, srcFileInfo.hint, &srcKey, &srcData, &srcHint );
	if ( err != noErr ) goto ParseError;

	//--	locate the dest file
	err = LocateCatalogNode( vcb, destFileInfo.id, &destName, destFileInfo.hint, &destKey, &destData, &destHint );
	if ( err != noErr ) goto ParseError;

	//--	Are we trying to exchange the file with itself?
	if ( isHFSPlus )
	{
		srcFileID = srcData.hfsPlusFile.fileID;
		destFileID = destData.hfsPlusFile.fileID;
	}
	else
	{
		srcFileID = srcData.hfsFile.fileID;
		destFileID = destData.hfsFile.fileID;
	}
	
	if ( srcFileID == destFileID )
		return( sameFileErr );

		
	//	Flush any open paths with the files in question! (release cache buffers)							<KSCT>
	
	//	Note: The cache takes fcb refnum as an argument for flushing. The only way to get
	//	this value is to walk the fcb array and find matches of the file number. If a match
	//	is found, that fcb refnum can be used for flushing. BUT, the fcb walk continues, since
	//	there can be more than one open path per file.
	
	//	FINE TUNING: Flag if either file is open. THEN after FidExchangeFiles, if none, skip fcb walking.

	Get1stFileControlBlock( &fcbIndex, &fcbs );
	do
	{
		fcb = GetFileControlBlock( fcbIndex );
		if ( fcb->fcbVPtr == (ExtendedVCB *) vcb )						//	make sure we're on the right volume
		{
			if ( (fcb->fcbFlNm == srcFileID) || (fcb->fcbFlNm == destFileID) )
			{
				//--	Flush and continue
				LMSetFlushOnly( 0xFF );
				err = CloseFile( vcb, fcbIndex, fcbs );
				ReturnIfError( err );
				
				LMSetCacheFlag( 0xFF );

				(void) C_FlushCache( vcb, fcTrashBit, fcbIndex );
			}
		}
	} while ( GetNextFileControlBlock( &fcbIndex, fcbs ) == false );	//	while there's still some left

	//	valid buffer in the free queue has a file number tag to it in the header, however,
	//	after FIDExchangeFiles the file number is no longer valid. So we have to call TrashFBlocks
	//	to invalidate all the buffer associated with the files.					<16Apr91 #12 ksct>

	TrashCatalogIterator( vcb, srcFileInfo.id );					//	invalidate any iterators for this parentID
	InvalidateCatalogNodeCache( vcb, srcFileInfo.id );				//	invalidate node cache
	if ( srcFileInfo.id != destFileInfo.id )
	{
		TrashCatalogIterator( vcb, destFileInfo.id );				//	invalidate any iterators for this parentID
		InvalidateCatalogNodeCache( vcb, destFileInfo.id );			//	invalidate node cache
	}
	TrashFileBlocks( vcb, destFileID );
	TrashFileBlocks( vcb, srcFileID );
	
	
	//--	Do the stuff!
	err = ExchangeFileIDs( vcb, &srcName, &destName, srcFileInfo.id, destFileInfo.id, srcHint, destHint );
	if ( DEBUG_BUILD && err != noErr)
	{
		DebugStr("\pError from ExchangeFileIDs");
	}

	//--	Now get the names ready for output
	if ( isHFSPlus )
	{
		err = ConvertUnicodeToHFSName( &srcName.ustr, srcData.hfsPlusFile.textEncoding, srcData.hfsPlusFile.fileID, pSrcName );
		if ( err != noErr )
			goto ParseError;

		err = ConvertUnicodeToHFSName( &destName.ustr, destData.hfsPlusFile.textEncoding, srcData.hfsPlusFile.fileID, pDestName );
	}
	else
	{
		BlockMoveData( srcName.pstr, pSrcName, StrLength(srcName.pstr) + 1 );
		BlockMoveData( destName.pstr, pDestName, StrLength(srcName.pstr) + 1 );
	}

	if ( err != noErr )
		goto ParseError;
		
	//--	Exchange fcb info (file number, file name, and file parID only!)
	Get1stFileControlBlock( &fcbIndex, &fcbs );
	do
	{
		fcb = GetFileControlBlock( fcbIndex );
		if ( fcb->fcbVPtr == vcb )
		{
			if ( fcb->fcbFlNm == srcFileID )
			{
				fcb->fcbCatPos	= 0;
				fcb->fcbFlNm	= destFileID;
				fcb->fcbDirID	= destFileInfo.id;
				
				BlockMoveData( pDestName, fcb->fcbCName, pDestName[0]+1 );
			}
			else if ( fcb->fcbFlNm == destFileID )
			{
				fcb->fcbCatPos	= 0;
				fcb->fcbFlNm	= srcFileID;
				fcb->fcbDirID	= srcFileInfo.id;
				
				BlockMoveData( pSrcName, fcb->fcbCName, pSrcName[0]+1 );
			}
		}
	} while ( GetNextFileControlBlock( &fcbIndex, fcbs ) == false );	//	while there's still some left
		
	return( err );

ParseError:
	if		( err == cmNotFound )	return( fnfErr );
	else if	( err == cmFThdDirErr )	return( notAFileErr );
	else							return( err );
}
#endif	/* TARGET_OS_MAC */
#endif


OSErr ExchangeFileIDs( ExtendedVCB *vcb, ConstUTF8Param srcName, ConstUTF8Param destName, HFSCatalogNodeID srcID, HFSCatalogNodeID destID, UInt32 srcHint, UInt32 destHint )
{
	CatalogKey		srcKey;		// 518 bytes
	CatalogRecord	srcData;	// 520 bytes
	CatalogKey		destKey;	// 518 bytes
	CatalogRecord	destData;	// 520 bytes
	CatalogRecord	swapData;	// 520 bytes
	SInt16			numSrcExtentBlocks;
	SInt16			numDestExtentBlocks;
	UInt32			textEncoding;
	OSErr			err;
	Boolean			isHFSPlus = ( vcb->vcbSigWord == kHFSPlusSigWord );

    TrashCatalogIterator(vcb, srcID);	//	invalidate any iterators for this parentID
    TrashCatalogIterator(vcb, destID);	//	invalidate any iterators for this parentID

	err = BuildCatalogKeyUTF8(srcID, srcName, isHFSPlus, &srcKey, &textEncoding);
	ReturnIfError(err);

	err = BuildCatalogKeyUTF8(destID, destName, isHFSPlus, &destKey, &textEncoding);
	ReturnIfError(err);

	if ( isHFSPlus )
	{
		//--	Step 1: Check the catalog nodes for extents
		
		//--	locate the source file, test for extents in extent file, and copy the cat record for later
		err = LocateCatalogNodeByKey( vcb, srcHint, &srcKey, &srcData, &srcHint );
		ReturnIfError( err );
	
		if ( srcData.recordType != kHFSPlusFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"
			
		//--	Check if there are any extents in the source file
		//	I am only checling the extents in the low 32 bits, routine will fail if files extents after 2 gig are in overflow
		numSrcExtentBlocks = CheckExtents( srcData.hfsPlusFile.dataFork.extents, srcData.hfsPlusFile.dataFork.totalBlocks, isHFSPlus );
		if ( numSrcExtentBlocks == 0 )					//	then check the resource fork extents
			numSrcExtentBlocks = CheckExtents( srcData.hfsPlusFile.resourceFork.extents, srcData.hfsPlusFile.resourceFork.totalBlocks, isHFSPlus );

		//--	Check if there are any extents in the destination file
		err = LocateCatalogNodeByKey( vcb, destHint, &destKey, &destData, &destHint );
		ReturnIfError( err );
	
		if ( destData.recordType != kHFSPlusFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"

		numDestExtentBlocks = CheckExtents( destData.hfsPlusFile.dataFork.extents, destData.hfsPlusFile.dataFork.totalBlocks, isHFSPlus );
		if ( numDestExtentBlocks == 0 )					//	then check the resource fork extents
			numDestExtentBlocks = CheckExtents( destData.hfsPlusFile.resourceFork.extents, destData.hfsPlusFile.resourceFork.totalBlocks, isHFSPlus );

		//--	Step 2: Exchange the Extent key in the extent file
		
		//--	Exchange the extents key in the extent file
		err = DeleteExtents( vcb, kHFSBogusExtentFileID, isHFSPlus );
		ReturnIfError( err );
		
		if ( numSrcExtentBlocks && numDestExtentBlocks )	//	if both files have extents
		{
			//--	Change the source extents file ids to our known bogus value
			err = MoveExtents( vcb, srcData.hfsPlusFile.fileID, kHFSBogusExtentFileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );
				else
					goto ExUndo1a;
			}
			
			//--	Change the destination extents file id's to the source id's
			err = MoveExtents( vcb, destData.hfsPlusFile.fileID, srcData.hfsPlusFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

ExUndo2aPlus:	err = DeleteExtents( vcb, srcData.hfsPlusFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

                err = MoveExtents( vcb, kHFSBogusExtentFileID, srcData.hfsPlusFile.fileID, isHFSPlus );	//	Move the extents back
				ReturnIfError( err );					//	we are doomed. Just QUIT!
					
				goto ExUndo1a;
			}
			
			//--	Change the bogus extents file id's to the dest id's
            err = MoveExtents( vcb, kHFSBogusExtentFileID, destData.hfsPlusFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, destData.hfsPlusFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				err = MoveExtents( vcb, srcData.hfsPlusFile.fileID, destData.hfsPlusFile.fileID, isHFSPlus );	//	Move the extents back
				ReturnIfError( err );					//	we are doomed. Just QUIT!
					
				goto ExUndo2aPlus;
			}
			
		}
		else if ( numSrcExtentBlocks )	//	just the source file has extents
		{
			err = MoveExtents( vcb, srcData.hfsPlusFile.fileID, destData.hfsPlusFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, srcData.hfsPlusFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				goto FlushAndReturn;
			}
		}
		else if ( numDestExtentBlocks )	//	just the destination file has extents
		{
			err = MoveExtents( vcb, destData.hfsPlusFile.fileID, srcData.hfsPlusFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, destData.hfsPlusFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				goto FlushAndReturn;
			}
		}

		//--	Step 3: Change the data in the catalog nodes
		
		//--	find the source cnode and put dest info in it
		err = LocateCatalogNodeByKey( vcb, srcHint, &srcKey, &srcData, &srcHint );
		if ( err != noErr )
			return( cmBadNews );
		
		BlockMoveData( &srcData, &swapData, sizeof(CatalogRecord) );
		CopyBigCatalogNodeInfo( &destData, &srcData );
		
		err = ReplaceBTreeRecord( vcb->catalogRefNum, &srcKey, srcHint, &srcData, sizeof(HFSPlusCatalogFile), &srcHint );
		ReturnIfError( err );

		//	find the destination cnode and put source info in it		
		err = LocateCatalogNodeByKey( vcb, destHint, &destKey, &destData, &destHint );
		if ( err != noErr )
			return( cmBadNews );
			
		CopyBigCatalogNodeInfo( &swapData, &destData );
		err = ReplaceBTreeRecord( vcb->catalogRefNum, &destKey, destHint, &destData, sizeof(HFSPlusCatalogFile), &destHint );
		ReturnIfError( err );
	}
	else		//	HFS	//
	{
		//--	Step 1: Check the catalog nodes for extents
		
		//--	locate the source file, test for extents in extent file, and copy the cat record for later
		err = LocateCatalogNodeByKey( vcb, srcHint, &srcKey, &srcData, &srcHint );
		ReturnIfError( err );
	
		if ( srcData.recordType != kHFSFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"
			
		//--	Check if there are any extents in the source file
		numSrcExtentBlocks = CheckExtents( srcData.hfsFile.dataExtents, srcData.hfsFile.dataPhysicalSize / vcb->blockSize, isHFSPlus );
		if ( numSrcExtentBlocks == 0 )					//	then check the resource fork extents
			numSrcExtentBlocks = CheckExtents( srcData.hfsFile.rsrcExtents, srcData.hfsFile.rsrcPhysicalSize / vcb->blockSize, isHFSPlus );
		
		
		//	Do we save the found source node for later use?
		
				
		//--	Check if there are any extents in the destination file
		err = LocateCatalogNodeByKey( vcb, destHint, &destKey, &destData, &destHint );
		ReturnIfError( err );
	
		if ( destData.recordType != kHFSFileRecord )
			return( cmFThdDirErr );					//	Error "cmFThdDirErr = it is a directory"

		numDestExtentBlocks = CheckExtents( destData.hfsFile.dataExtents, destData.hfsFile.dataPhysicalSize / vcb->blockSize, isHFSPlus );
		if ( numDestExtentBlocks == 0 )					//	then check the resource fork extents
			numDestExtentBlocks = CheckExtents( destData.hfsFile.rsrcExtents, destData.hfsFile.rsrcPhysicalSize / vcb->blockSize, isHFSPlus );
			
		//	Do we save the found destination node for later use?


		//--	Step 2: Exchange the Extent key in the extent file
		
		//--	Exchange the extents key in the extent file
        err = DeleteExtents( vcb, kHFSBogusExtentFileID, isHFSPlus );
		ReturnIfError( err );
		
		if ( numSrcExtentBlocks && numDestExtentBlocks )	//	if both files have extents
		{
			//--	Change the source extents file ids to our known bogus value
        err = MoveExtents( vcb, srcData.hfsFile.fileID, kHFSBogusExtentFileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

ExUndo1a:		err = DeleteExtents( vcb, kHFSBogusExtentFileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				err = FlushCatalog( vcb );   			//	flush the catalog
				err = FlushExtentFile( vcb );			//	flush the extent file (unneeded for common case, but it's cheap)			
				return( dskFulErr );
			}
			
			//--	Change the destination extents file id's to the source id's
			err = MoveExtents( vcb, destData.hfsFile.fileID, srcData.hfsFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

ExUndo2a:		err = DeleteExtents( vcb, srcData.hfsFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

                err = MoveExtents( vcb, kHFSBogusExtentFileID, srcData.hfsFile.fileID, isHFSPlus );	//	Move the extents back
				ReturnIfError( err );					//	we are doomed. Just QUIT!
					
				goto ExUndo1a;
			}
			
			//--	Change the bogus extents file id's to the dest id's
            err = MoveExtents( vcb, kHFSBogusExtentFileID, destData.hfsFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, destData.hfsFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				err = MoveExtents( vcb, srcData.hfsFile.fileID, destData.hfsFile.fileID, isHFSPlus );	//	Move the extents back
				ReturnIfError( err );					//	we are doomed. Just QUIT!
					
				goto ExUndo2a;
			}
			
		}
		else if ( numSrcExtentBlocks )	//	just the source file has extents
		{
			err = MoveExtents( vcb, srcData.hfsFile.fileID, destData.hfsFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, srcData.hfsFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				goto FlushAndReturn;
			}
		}
		else if ( numDestExtentBlocks )	//	just the destination file has extents
		{
			err = MoveExtents( vcb, destData.hfsFile.fileID, srcData.hfsFile.fileID, isHFSPlus );
			if ( err != noErr )
			{
				if ( err != dskFulErr )
					return( err );

				err = DeleteExtents( vcb, destData.hfsFile.fileID, isHFSPlus );
				ReturnIfError( err );					//	we are doomed. Just QUIT!

				goto FlushAndReturn;
			}
		}

		//--	Step 3: Change the data in the catalog nodes
		
		//--	find the source cnode and put dest info in it
		err = LocateCatalogNodeByKey( vcb, srcHint, &srcKey, &srcData, &srcHint );
		if ( err != noErr )
			return( cmBadNews );
		
		BlockMoveData( &srcData, &swapData, sizeof(CatalogRecord) );
		//	Asm source copies from the saved dest catalog node
		CopyCatalogNodeInfo( &destData, &srcData );
		
		err = ReplaceBTreeRecord( vcb->catalogRefNum, &srcKey, srcHint, &srcData, sizeof(HFSCatalogFile), &srcHint );
		ReturnIfError( err );

		
		//	find the destination cnode and put source info in it		
		err = LocateCatalogNodeByKey( vcb, destHint, &destKey, &destData, &destHint );
		if ( err != noErr )
			return( cmBadNews );
			
		CopyCatalogNodeInfo( &swapData, &destData );
		err = ReplaceBTreeRecord( vcb->catalogRefNum, &destKey, destHint, &destData, sizeof(HFSCatalogFile), &destHint );
		ReturnIfError( err );
	}
	
	err = noErr;

	//--	Step 4: Error Handling section


FlushAndReturn:
	err = FlushCatalog( vcb );   			//	flush the catalog
	err = FlushExtentFile( vcb );			//	flush the extent file (unneeded for common case, but it's cheap)			
	return( err );
}


void	CopyCatalogNodeInfo( CatalogRecord *src, CatalogRecord *dest )
{
//	dest->hfsFile.filStBlk = src->hfsFile.filStBlk;
	dest->hfsFile.dataLogicalSize	= src->hfsFile.dataLogicalSize;
	dest->hfsFile.dataPhysicalSize = src->hfsFile.dataPhysicalSize;
//	dest->hfsFile.filRStBlk = src->hfsFile.filRStBlk;
	dest->hfsFile.rsrcLogicalSize	= src->hfsFile.rsrcLogicalSize;
	dest->hfsFile.rsrcPhysicalSize = src->hfsFile.rsrcPhysicalSize;
	dest->hfsFile.modifyDate = src->hfsFile.modifyDate;
	BlockMoveData( src->hfsFile.dataExtents, dest->hfsFile.dataExtents, sizeof(HFSExtentRecord) );
	BlockMoveData( src->hfsFile.rsrcExtents, dest->hfsFile.rsrcExtents, sizeof(HFSExtentRecord) );
}

void	CopyBigCatalogNodeInfo( CatalogRecord *src, CatalogRecord *dest )
{
	BlockMoveData( &src->hfsPlusFile.dataFork, &dest->hfsPlusFile.dataFork, sizeof(HFSPlusForkData) );
	BlockMoveData( &src->hfsPlusFile.resourceFork, &dest->hfsPlusFile.resourceFork, sizeof(HFSPlusForkData) );
	dest->hfsPlusFile.contentModDate = src->hfsPlusFile.contentModDate;
}