Source to bsd/hfs/hfscommon/Misc/VolumeRequests.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:		VolumeRequests.c

	Contains:	MountVolume and related utility routines for HFS & HFS Plus

	Version:	HFS Plus 1.0

	Written by:	Deric Horn

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

	File Ownership:

		DRI:				Deric Horn

		Other Contacts:		Mark Day, Don Brady

		Technology:			File Systems

	Writers:

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

	Change History (most recent first):
	  <Rhap>	 7/28/98	djb		GetDiskBlocks is now implemented in MacOSStubs.c (radar #2258148).
	  <Rhap>	  4/3/98	djb		Conditionally remove FSVars reference from GetVolumeNameFromCatalog.
	  <Rhap>	 3/31/98	djb		Sync up with final HFSVolumes.h header file.
	  <CS47>	 1/29/98	DSH		TrashAllFSCaches is responsible for trashing all file system and
									disk caches. Called from FlushVol when the HFS bit is set.
	  <CS46>	12/12/97	DSH		2003877, when vcbAllocPtr was copied to nextAllocation it was
									getting sign extended.
	  <CS45>	11/26/97	DSH		2003459, fcbs was not being initialized if volume was offline
									and we are executing an unconditional unmount.
	  <CS44>	11/24/97	DSH		2005507, FlushVolumeControlBlock() keeps MDB drCrDate in sync
									with VolumeHeader createDate.
	  <CS43>	11/11/97	DSH		1685873, RemountWrappedVolumes was only remounting the first
									HFS+ volume in the queue, causing HFS wrappers to be mounted if
									multiple volumes had been mounted before InitHFSPlus.
	  <CS42>	 11/4/97	DSH		Clear FCB when getting a new one.
	  <CS41>	 11/3/97	JL		#2001483 - Removed unneeded parameters from MountVolume,
									MountHFSVolume, MountHFSPlusVolume, GetVolumeInformation,
									GetXVolumeInformation and AddVCB (and added local variables as
									needed). Return WDCBRecPtr from UnMountVolume. Set wdcb
									parameter to NULL in GetXVolumeInformation if working directory
									was not specified.
	  <CS40>	10/31/97	DSH		Added consistencyStatus parameter to MountCheck
	  <CS39>	10/23/97	msd		Bug 1685113. The VolumeHeader's createDate should be in local
									time (not GMT) and identical to the MDB's drCrDate (and VCB's
									vcbCrDate). When checking for a remount of an offline HFS Plus
									volume, compare write counts instead of mod dates (which could
									be fooled by the user changing time zones). Force MountCheck to
									run if the volume was last mounted by Bride 1.0b2 or earlier.
	  <CS38>	10/17/97	msd		Conditionalize DebugStrs.
	  <CS37>	10/13/97	djb		Update volumeNameEncodingHint when updating the volume name.
	  <CS36>	10/10/97	msd		Bug 1683571. The dates in the volume header are in GMT, so be
									sure to convert them when mounting a volume or flushing the
									volume header.
	  <CS35>	 10/2/97	DSH		In UnmountVolume() check that the drive is on line before
									determining if wrapper volume needs to be renamed causing IO.
	  <CS34>	 10/1/97	DSH		Run on disk version of MountCheck instead of ROM version for
									boot volumes1682475.
	  <CS33>	 10/1/97	djb		Add calls to InvalidateCatalogCache (part of radar #1678833).
	  <CS32>	 9/26/97	DSH		Removed debugging code: support for 'W' key wrapper mounting.
	  <CS31>	 9/17/97	DSH		hfsPlusIOPosOffset was uninitialized for Wrapperless volumes.
	  <CS30>	  9/5/97	djb		In MountVol initialize Catalog cache before calling Catalog!
	  <CS29>	  9/4/97	msd		PropertyCloseVolume renamed to AttributesCloseVolume. Remove
									call to AttributesOpenVolume (it no longer exists).
	  <CS28>	  9/2/97	DSH		VolumeHeader is now 3rd sector in partition, altVH is 2nd to
									last cor compatability.  Initial support for wrapperless
									volumes.
	  <CS27>	 8/26/97	djb		Only call CountRootFiles during MountVol.
	  <CS26>	 8/20/97	msd		If the HFS Plus volume version doesn't match, mount the wrapper
									instead.
	  <CS25>	 8/19/97	djb		Add error handling to RenameWrapperVolume.
	  <CS24>	 8/15/97	msd		Bug 1673999. In MakeVCBsExtendedVCBs, copy old VCB's vcbAllocPtr
									to new VCB's nextAllocation field.
	  <CS23>	 8/12/97	djb		Fixed GetXVolInfo to only use extended vcb fields for local
									volumes (radar# 1673177)
	  <CS22>	 8/11/97	DSH		vcbNmAlBlks is now taken from the embededExtent.blockCount
									(1669121).
	  <CS21>	 8/11/97	djb		Return actual count of files in root directory for HFS Plus
									volumes (Radar #1669118). Added local CountRootFiles routine.
									8/5/97 msd Make sure version field in VolumeHeader is exactly
									kHFSPlusVersion. 8/1/97 djb GetXVolumeInformation now returns
									extFSErr when FSID is nonzero (Radar #1649503).
	  <CS20>	 7/25/97	DSH		Init and Dispose of GenericMRUCache within ExtendedVCB.
	  <CS19>	 7/16/97	DSH		FilesInternal.x -> FileMgrInternal.x to avoid name collision
	  <CS18>	 7/15/97	DSH		Remount Wrapper volumes mounted before HFS+ initialization
									(166729)
	  <CS17>	 7/15/97	djb		Remove ioXVersion checking in GetXVolInfo (radar #1666217).
	  <CS16>	  7/8/97	DSH		Loading PrecompiledHeaders from define passed in on C line
	  <CS15>	  7/7/97	djb		Add GetVolumeNameFromCatalog routine.
	  <CS14>	  7/7/97	DSH		GetNewVRefNum now get's a recycled vRefNum. Bug 1664445 in
									Installer was cacheing the vRefNum while CheckDisk unmounts and
									remounts disk.
	  <CS13>	 6/30/97	DSH		shadowing values obsoleteVCBXTRef, and obsoleteVCBCTRef when
									HFS+ volume is mounted.
	  <CS12>	 6/26/97	DSH		GetVolInfo returns HFS signature for HFS+ volumes, GetXVolInfo
									returns real signature.
	  <CS11>	 6/24/97	DSH		MakeVCBsExtendedVCBs was using wdcb->count as count not byte
									count.
	  <CS10>	 6/18/97	djb		Set/get volume encodingsBitmap.
	   <CS9>	 6/16/97	msd		Include String.h and Disks.h.
	   <CS8>	 6/12/97	djb		Get in sync with HFS Plus format changes.
	   <CS7>	 6/11/97	msd		Make GetXVolumeInformation return true allocation block size. It
									now checks the ioXVersion field.
	   <CS6>	 5/28/97	msd		When flushing the volume header, write out the allocation file's
									clump size (from the FCB). When mounting an HFS Plus volume,
									zero the entire FCB extent record, not just the first extent,
									for the various volume control files.
	   <CS5>	 5/19/97	djb		Add calls to CreateVolumeCatalogCache,
									DisposeVolumeCatalogCache.
	   <CS4>	  5/9/97	djb		Get in sync with new FilesInternal.i
	   <CS3>	  5/8/97	DSH		Only mount HFS+ volumes with version < 2.0 in the VolumeHeader.
									Return wrgVolTypErr if too new.
	   <CS2>	  5/2/97	djb		Disable Manual Eject code since its buggy!
	   <CS1>	 4/25/97	djb		first checked in

	 <HFS32>	 4/11/97	DSH		MountHFSPlusVolume gets volume name from catalog, and
									UnmountVolume shadows the name back to the wrapper partition.
	 <HFS31>	  4/8/97	msd		Once volume is mounted, call AttributesOpenVolume to allow a
									buffer to be allocated.
	 <HFS30>	  4/7/97	msd		In FlushVolumeControlBlock, don't update the attributes BTree
									fields in the Volume Header unless an attributes BTree was
									already open.
	 <HFS29>	  4/7/97	msd		In SetupFCB, add case for attributes BTree. Add code to set up
									the attributes BTree. Remove call to PropertyOpenVolume. In
									FlushVolumeControlBlock, write out any changes to the attributes
									BTree.
	 <HFS28>	  4/4/97	djb		Get in sync with volume format changes.
	 <HFS27>	 3/31/97	djb		Added catalogDataCache to VCB; Remove ClearMem routine.
	 <HFS26>	 3/18/97	msd		In MountHFSPlusVolume, the free blocks calculation can overflow,
									setting vcbFreeBks to a too-small value.
	 <HFS25>	 3/17/97	DSH		Added some utility functions AddVCB, GetParallelFCBFromRefNum,
									casting for SC, and made some functions extern for DFA.
	 <HFS24>	  3/5/97	msd		Add calls to Property Manager to open and close the volume. When
									unmounting an HFS+ volume, the allocation (bitmap) file now gets
									closed.
	 <HFS23>	 2/19/97	djb		Update to 16-bit HFS Plus signature.
	 <HFS22>	 2/12/97	msd		In GetXVolumeInformation, the result code could be
									uninitialized.
	 <HFS21>	 1/23/97	DSH		UpdateAlternateVoumeControlBlock()
	 <HFS20>	 1/15/97	djb		Remove MountCheckStub. Add file names to fcbs for debugging.
	 <HFS19>	 1/13/97	DSH		Use ExtendedVCB nextAllocation instead of vcbAllocPtr through
									all code.
	 <HFS18>	  1/9/97	djb		Get in sync with new VolumeHeader and Extended VCB.
	 <HFS17>	  1/6/97	djb		Changed API to ParallelFCBFromRefnum (pragma parameter was
									broken).
	 <HFS16>	  1/6/97	msd		Set only the defined bits in the MDB drAtrb field (when copying
									from VCB vcbAtrb field).
	 <HFS15>	  1/6/97	DSH		CloseFile requires VCB to be passed in.
	 <HFS14>	  1/6/97	djb		FlushVolumeControlBlock was writing to absolute block 0 instead
									of to block zero of the embedded volume.
	 <HFS13>	12/20/96	msd		A comparison was using "=" instead of "=="; might have caused
									the wrong volume to be set as the default.
	 <HFS12>	12/19/96	DSH		Setting up ExtendedVCBs
	 <HFS11>	12/19/96	djb		Updated for new B-tree Manager interface.
	 <HFS10>	12/18/96	msd		Change GetVCBRefNum so it can actually return a VCB pointer.
	  <HFS9>	12/12/96	djb		Use new SPI for GetCatalogNode.
	  <HFS8>	12/12/96	msd		Fix a bunch of errors (mostly type mismatch) when compiling with
									Metrowerks.
	  <HFS7>	12/12/96	DSH		adding some util functions
	  <HFS6>	12/10/96	msd		Check PRAGMA_LOAD_SUPPORTED before loading precompiled headers.
	  <HFS5>	 12/4/96	DSH		Ported GetVolumeInformation & GetXVolumeInformation.
		<3*>	11/20/96	DSH		HFS Plus support to MountVolume
	  <HFS3>	11/20/96	DSH		Added UnmountVol and related routines, also backed out <2>
									because C_FXMKeyCmp is passed as a parameter from C but called
									from Asm in BTOpen so we need a Case ON Asm entry point.
	  <HFS2>	11/20/96	msd		Use CompareExtentKeys() instead of CFXMKeyCmp().
	  <HFS1>	11/19/96	DSH		first checked in
		 <1>	11/19/96	DSH		first checked in

*/

#if ( PRAGMA_LOAD_SUPPORTED )
        #pragma	load	PrecompiledHeaders
#else
        #if TARGET_OS_MAC
	#include	<Files.h>
        #include	<FSM.h>
        #include	<OSUtils.h>
        #include	<LowMemPriv.h>
        #include	<LowMem.h>
        #include	<StringCompare.h>
        #include	<Errors.h>
        #include	<Devices.h>
        #include	<SonyPriv.h>
        #include	<EDiskPriv.h>
        #include	<String.h>
        #include	<Disks.h>
        #else
	#include "../../hfs.h"
       	#include "../headers/system/MacOSStubs.h"
        #endif 	/* TARGET_OS_MAC */
#endif	/* PRAGMA_LOAD_SUPPORTED */

#if TARGET_OS_MAC
#include	<HardwarePriv.h>
#endif

#include	"../headers/FileMgrInternal.h"
#include	"../headers/HFSVolumes.h"


#define		kIDSectorOffset	2

// Exported routine prototypes
OSErr	MountVolume( VolumeParam *volume, Boolean forceHFSWrapperToMount );

OSErr	MountHFSVolume( short driveNumber, IOParam* lowMemParamBlock, OSErr err );

OSErr	MountHFSPlusVolume( short driveNumber, IOParam* lowMemParamBlock, OSErr err, Boolean pureHFSPlusVolume );

OSErr	GetVolumeInformation( HVolumeParam *volume, WDCBRecPtr *wdcb );

OSErr	GetXVolumeInformation( XVolumeParam *volume, WDCBRecPtr *wdcb );


//	Utility function prototypes
#if TARGET_OS_MAC
OSErr	FindFileControlBlock( File *index, Ptr *fcbsH );

Boolean	GetNextFileControlBlock( UInt16 *index, Ptr fcbsPtr );

void	Get1stFileControlBlock( UInt16 *index, Ptr *fcbsH );

void	DisposeFileControlBlocks( ExtendedVCB *vcb );
#else
OSErr	GetNewFCB( ExtendedVCB *vcb, FileReference* fRefPtr);
#endif

OSErr	AccessBTree( ExtendedVCB *vcb, FileReference refNum, UInt32 fileID, UInt32 fileClumpSize, void *CompareRoutine );

UInt16	DivUp( UInt32 byteRun, UInt32 blockSize );

Boolean	IsARamDiskDriver( void );

OSErr	GetVCBRefNum( ExtendedVCB **vcb, short vRefNum );

OSErr	ValidMasterDirectoryBlock( HFSMasterDirectoryBlock *mdb );

void	RenameWrapperVolume( Str27 newVolumeName, UInt16 driveNumber );

OSErr	CheckExternalFileSystem( ExtendedVCB *vcb );

OSErr	FlushVolume( ExtendedVCB *vcb );

FCB		*SetupFCB( ExtendedVCB *vcb, FileReference refNum, UInt32 fileID, UInt32 fileClumpSize );

OSErr	GetDiskBlocks( ExtendedVCB *vcb, unsigned long *numBlocks);

void	AddVCB( ExtendedVCB	*vcb, short driveNumber, short ioDRefNum );

short	IsPressed( unsigned short k );

FileReference	GetNewVRefNum();

OSErr	GetVolumeNameFromCatalog(ExtendedVCB *vcb);

#if TARGET_OS_MAC
static UInt16 CountRootFiles(ExtendedVCB *vcb);
#endif /* TARGET_OS_MAC */


#if ( hasHFSManualEject )
static void SetVCBManEject(ExtendedVCB *vcb);
#endif

// External routines

extern	OSErr	C_FlushMDB( ExtendedVCB *volume );

extern	OSErr	DisposeVolumeCacheBlocks( ExtendedVCB *vcb );

extern	void	DisposeVolumeControlBlock( ExtendedVCB *vcb );

extern	OSErr	DeturmineVolume1( VolumeParam *iopb, short *nameLength, Boolean *nameSpecified, ExtendedVCB **vcb, WDCBRec **wdcb, unsigned char **pathName );

extern	OSErr	DeturmineVolume3( VolumeParam *iopb, short *nameLength, Boolean *nameSpecified, ExtendedVCB **vcb, WDCBRec **wdcb, unsigned char **pathName );

extern	OSErr	FlushVolumeBuffers( ExtendedVCB *vcb );

extern	void	MultiplyUInt32IntoUInt64( UInt64 *wideResult, UInt32 num1, UInt32 num2 );

extern void		TrashCatalogNodeCache( void );



#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	MountVolume
//
// Function: 	The VCB pointers are checked to see if a volume in the
//				specified drive is already on-line (error if so).  Reads in the
//				directory master block and allocates memory for VCB and additional
//				data structures, depending on the volume type:
//
//				- For MFS volumes (handled in MFSVol), space is allocated for
//				  a volume buffer, as well as the block map, which is read in
//				  from disk).
//
//				- For TFS volumes, space is allocated for a bitmap cache, a volume
//				  control cache (for B*-Tree use), and a volume cache.	In addition,
//				  two FCBs are used by the B*-Trees.
//
//				No new VCB storage is allocated for remounts.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹

OSErr	MountVolume( VolumeParam *volume, Boolean forceHFSWrapperToMount )
{
	Byte					blockBuffer[512];
	IOParam					*lowMemParamBlock;
	short					ioRefNum;
	short					vRefNum;
	OSErr					err;
	DrvQEl					*dqe;
	Boolean					pureHFSPlusVolume;
	
	
	//--- FSQueueSync was already called by the MountVol glue.
	
	vRefNum = volume->ioVRefNum;
	err = FindDrive( &ioRefNum, &dqe, vRefNum );							//	Get driver refnum
	if ( err == noErr )
	{
		lowMemParamBlock				= (IOParam *) LMParamBlock;
		lowMemParamBlock->ioVRefNum		= vRefNum;
		lowMemParamBlock->ioRefNum		= ioRefNum;
		lowMemParamBlock->ioBuffer		= (char *)blockBuffer;
		lowMemParamBlock->ioPosMode		= fsFromStart;
		lowMemParamBlock->ioPosOffset	= kIDSectorOffset * 512;			//	MDB is always at block #2
		lowMemParamBlock->ioReqCount	= 512;								//	MDB is always 1 512 byte block
		
		err = PBReadSync( (ParmBlkPtr) lowMemParamBlock );					//	Read the MDB
		
		pureHFSPlusVolume	= ((HFSPlusVolumeHeader *)blockBuffer)->signature == kHFSPlusSigWord;
		
		if ( 	( ((HFSMasterDirectoryBlock *)blockBuffer)->drEmbedSigWord == kHFSPlusSigWord	//	Wrapped HFS+ volume
			||	pureHFSPlusVolume == true )									//	Pure HFS+ volume
			&&	err == noErr
			&&	forceHFSWrapperToMount == false )							//	if we don't want to force the wrapper to mount
		{
			err = MountHFSPlusVolume( vRefNum, lowMemParamBlock, err, pureHFSPlusVolume );
		}
		else																//	pass all other volume formats / errors to MountHFSVolume
		{
			err = MountHFSVolume( vRefNum, lowMemParamBlock, err );
		}
	}
	
	return( err );															//	pass the error back to CmdDone
}
#endif /* TARGET_OS_MAC */


#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	MountHFSPlusVolume
//
// Function: 	Called by MountVolume()
//
//	At this point we know that the MDB's Embeded signature indicates that
//	this is an HFS Plus disk with an HFS wrapper.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹

OSErr	MountHFSPlusVolume( short driveNumber, IOParam* lowMemParamBlock, OSErr err, Boolean pureHFSPlusVolume )
{
	HFSMasterDirectoryBlock	*mdb;
	Byte					blockBuffer[512];
	HFSPlusVolumeHeader		*volumeHeader;
	ExtendedVCB				*vcb;
	QHdr					*vcbQHdr;
	Byte					*flagsP;
	Byte					*pbFlagsP;
	SInt16					ioRefNum;
	FSVarsRec				*fsVars;
	DrvQEl					*dqe;
	IOParam					*currentIORequestPB;
	FCB						*fcb;
	Ptr 					fcbsP;
	ExtendedFCB				*extendedFCB;
	UInt32					hfsPlusIOPosOffset;
	UInt16					hfsBlockCount;
	UInt32					hfsBlockSize;
	UInt32					lastMountedVersion;
	UInt16					fcbIndexP;

	//--	Now read the HFS Plus VolumeHeader located at the first block within the embeded volume

	ioRefNum = lowMemParamBlock->ioRefNum;
	
	if ( pureHFSPlusVolume == false )
	{
		mdb = (HFSMasterDirectoryBlock *) lowMemParamBlock->ioBuffer;

		hfsPlusIOPosOffset				=	(mdb->drEmbedExtent.startBlock * mdb->drAlBlkSiz) + ( mdb->drAlBlSt * 512 );
		
		lowMemParamBlock->ioPosMode		=	fsFromStart;				
		lowMemParamBlock->ioPosOffset	=	hfsPlusIOPosOffset + (kIDSectorOffset * 512);		//	VolumeHeader is 2 sectors in
		lowMemParamBlock->ioBuffer		=	(char *)blockBuffer;

		err = PBReadSync( (ParmBlkPtr) lowMemParamBlock );						//	Read the VolumeHeader
	}
	else
	{
		hfsPlusIOPosOffset	= 0;		//	From beginning of volume.
	}
	
	volumeHeader = (HFSPlusVolumeHeader *) lowMemParamBlock->ioBuffer;
	
	err = ValidVolumeHeader( volumeHeader );

	if (err == badMDBErr || err == noMacDskErr)
		return err;															// it's not valid, so don't try to mount it

	if ( err == noErr )
	{
		err		= notARemountErr;											//	cuz we're looking for remounts only here
		vcbQHdr	= LMGetVCBQHdr();											//	search the queue of VCBs

		for ( vcb = (ExtendedVCB*)vcbQHdr->qHead ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
		{
			if (   (vcb->vcbDrvNum	== 0)									//	matching volume better be off-line (0 drive num)
				&& (vcb->vcbSigWord	== volumeHeader->signature)				//	Same signature?
				&& (vcb->vcbCrDate	== volumeHeader->createDate)			//	Same create date?
				&& (vcb->vcbWrCnt	== volumeHeader->writeCount)			//	Same write count?
			   )
			{
				//	At this point, we're fairly certain that the two volumes are, in fact, the same,
				//	but just to make sure (and avoid problems with stuck clocks), we'll compare the
				//	volume names as well:
				
				//₯₯	How do I compare unicode names?
				//₯₯	do we just skip this check?
		//		if ( EqualString( vcb->volumeName, volumeHeader->volumeName, true, true) )	//	caseSensitive, diacSensitive
					break;													//	Remount
			}
		}
		if ( vcb == nil ) goto RemountExit;


		((CntrlParam*)lowMemParamBlock)->csCode = drvStsCode;				//	Drive status!
		((CntrlParam*)lowMemParamBlock)->csParam[1] &= 0x00ff;				//	Clear status byte for return
		
		err = PBStatusSync( (ParmBlkPtr)lowMemParamBlock );					//	refnum and drivenum set up by read call


		//--	Now propagate the write protect status byte back to the vcb
		flagsP = (Byte *)((Ptr)(&(vcb->vcbAtrb))) + 1;
		pbFlagsP = (Byte *)((Ptr)(((CntrlParam*)lowMemParamBlock)->csParam)+2);
		*flagsP = *pbFlagsP & kVolumeHardwareLockMask;

		vcb->vcbDrvNum	= driveNumber;
		vcb->vcbDRefNum	= ioRefNum;											//	Driver RefNum
		
		#if ( hasHFSManualEject )
			SetVCBManEject(vcb);
		#endif


		if ( !(*flagsP & kVolumeHardwareLockMask) )							//	if bit is NOT set
		{
			vcb->vcbAtrb &= ~kVolumeUnmountedMask;							//	clear the kVolumeUnmounted attribute
			MarkVCBDirty( vcb );
			err = FlushVolumeControlBlock( vcb );
		}
		
		err = noErr;

	}


RemountExit:
	if ( err == noErr ) goto CmdDone;										//	noErr implies a successful remount			
		
	//--	if we are allowing new mounts, let the ROM handle the details.  Otherwise, let the error stand
	fsVars = (FSVarsRec*)LMGetFSMVars();
	if ( fsVars->fsFlags & fsNoAllocateMask )
		goto CmdDone;
		
	LMSetNewMount( -1 );													//	assume now that it's a new volume
	
	
	
	//--	First see if the disk is already mounted.
mv_Start:
	err = paramErr;
	driveNumber = lowMemParamBlock->ioVRefNum;
	if ( driveNumber <= 0 )	goto MtVolDone;
	err = GetVCBDriveNum( &vcb, driveNumber );
	if ( err == noErr )
	{
		if ( vcb->vcbDrvNum != 0 )
		{
			err = volOnLinErr;
			goto MtVolDone;
		}
	}
	
	//--	Find the appropriate drive queue entry and allocate a VCB.
	err = FindDrive( &ioRefNum, &dqe, driveNumber );
	if ( err != noErr ) goto MtVolDone;
	
	vcb = (ExtendedVCB *) NewPtrSysClear( sizeof( ExtendedVCB ) );		//	Zero out the Extended VCB

	if ( vcb == nil ) goto MtVolDone;									//	exit if we got no memory
	
	AddVCB( vcb, driveNumber, ioRefNum );								//	prep the VCB for cache calls
	
	//--	Now try reading in the Volume Header.
	//	We use "hfsPlusIOPosOffset / 512" cuz the VCB is not set up yet, so it's an absolute read
	err = GetBlock_glue( gbDefault, (hfsPlusIOPosOffset / 512) + kIDSectorOffset , (Ptr*)&volumeHeader, vcb->vcbVRefNum, vcb );	//	no Flags, VH is always block #0, vcb->vcbBufAdr == CacheQueueHeader

	if ( err != noErr )	goto MtVolEr2;									//	Quit & Release VCB on errors
	
	//--	Now check what manner of animal we have before us:
	err = RelBlock_glue( (Ptr)volumeHeader, rbTrashMask );
	
	err = ValidVolumeHeader( volumeHeader );
	if ( err != noErr )
	{
		if ( err == noMacDskErr )
			goto MtVolEr2;
		else
			goto MtVolEr1;
	}
	
	//	The VolumeHeader seems OK: transfer info from buffer into VCB.

	//	Initialize our dirID/nodePtr cache associated with this volume.
	err = InitMRUCache( sizeof(UInt32), kDefaultNumMRUCacheBlocks, &(vcb->hintCachePtr) );
	ReturnIfError( err );

	//--	Copy fields between [vcbSigWord, vcbVN]

	vcb->vcbCrDate					=	volumeHeader->createDate;					// NOTE: local time, not GMT!
	vcb->vcbLsMod					=	UTCToLocal(volumeHeader->modifyDate);
	vcb->vcbAtrb					=	(UInt16)volumeHeader->attributes;			//	VCB uses 16 bits, next 16 are for future API changes
	vcb->vcbClpSiz					=	volumeHeader->rsrcClumpSize;
	vcb->vcbNxtCNID					=	volumeHeader->nextCatalogID;
	vcb->vcbVolBkUp					=	UTCToLocal(volumeHeader->backupDate);
	vcb->vcbWrCnt					=	volumeHeader->writeCount;
	vcb->vcbXTClpSiz				=	volumeHeader->extentsFile.clumpSize;
	vcb->vcbCTClpSiz				=	volumeHeader->catalogFile.clumpSize;
	vcb->attributesClumpSize		=	volumeHeader->attributesFile.clumpSize;
	vcb->vcbFilCnt					=	volumeHeader->fileCount;
	vcb->vcbDirCnt					=	volumeHeader->folderCount;
	BlockMoveData( volumeHeader->finderInfo, vcb->vcbFndrInfo, sizeof( SInt32 ) * 8 );
	
	if ( pureHFSPlusVolume == true )
	{
		UInt32	totalSectors		=	(volumeHeader->blockSize / 512) * volumeHeader->totalBlocks;
		HFSBlocksFromTotalSectors( totalSectors, &hfsBlockSize, &hfsBlockCount );
	}
	else
	{
		hfsBlockSize				=	mdb->drAlBlkSiz;
		hfsBlockCount				=	mdb->drEmbedExtent.blockCount;
	}
	
	//	vcbFreeBks is the number of free bytes divided by the (HFS compatible) allocation block size.  Since the
	//	number of free bytes could overflow a UInt32, we actually divide both free bytes and allocation block size by
	//	512 before doing the division.  This prevents intermediate values from overflowing until the volume size is
	//	2 TB.  It's OK to divide by 512 since both HFS and HFS Plus require allocation blocks to be a multiple of 512.
	vcb->vcbFreeBks = (volumeHeader->freeBlocks * (volumeHeader->blockSize / 512)) / (hfsBlockSize / 512);
	

	//--	Fields taken from the MDB
	vcb->vcbSigWord					=	kHFSPlusSigWord;						//	new field in VCB space
	vcb->vcbNmAlBlks				=	hfsBlockCount;							//	number of HFS allocation blocks HFS+ volume
	vcb->vcbAlBlkSiz				=	hfsBlockSize;							//	add to extended vcb

	//--	Now fill in the Extended VCB info
	vcb->vcbSigWord					=	volumeHeader->signature;
	vcb->nextAllocation				=	volumeHeader->nextAllocation;
	vcb->totalBlocks				=	volumeHeader->totalBlocks;
	vcb->freeBlocks					=	volumeHeader->freeBlocks;
	vcb->blockSize					=	volumeHeader->blockSize;
	vcb->allocationsClumpSize		=	volumeHeader->allocationFile.clumpSize;
	vcb->checkedDate				=	volumeHeader->checkedDate;
	vcb->encodingsBitmap			=	volumeHeader->encodingsBitmap;
	
	//	Set the allocation block start for this HFS Plus wrapped volume
	vcb->vcbAlBlSt					=	hfsPlusIOPosOffset / 512;
	vcb->hfsPlusIOPosOffset			=	hfsPlusIOPosOffset;
	
	//₯₯	Questionables
	vcb->vcbVBMSt					= 0;										//₯₯ (We should set this) BitMap start NOT used in HFS Plus
	vcb->vcbEmbedSigWord			= 0;
	vcb->vcbEmbedExtent.startBlock	= 0;
	vcb->vcbEmbedExtent.blockCount	= 0;


	//--	Mark all the unused VCB fields with 0
	vcb->vcbAllocPtr				=	0;										//	use extended VCB value				
	
	//	Remember which implementation last mounted this volume
	lastMountedVersion				=	volumeHeader->lastMountedVersion;
	
	currentIORequestPB = (IOParam *) ((QHdr *)LMGetFSQHdr())->qHead;			//	current request
	currentIORequestPB->ioVRefNum = vcb->vcbVRefNum;							//	return volume refnum to user
	
	lowMemParamBlock = (IOParam *) LMParamBlock;
	((CntrlParam*)lowMemParamBlock)->csCode = drvStsCode;						//	drive status!
	((CntrlParam*)lowMemParamBlock)->csParam[1] &= 0x00ff;						//	Clear status byte for return
	
	err = PBStatusSync( (ParmBlkPtr)lowMemParamBlock );
	
	flagsP = (Byte *)((Ptr)(&(vcb->vcbAtrb))) + 1;
	pbFlagsP = (Byte *)((Ptr)(((CntrlParam*)lowMemParamBlock)->csParam)+2);
	*flagsP = *pbFlagsP & kVolumeHardwareLockMask;
	
	if ( IsARamDiskDriver() )
	{
		vcb->vcbAtrb |= kVolumeNoCacheRequiredMask;								//	yes:Κmark VCB as a RAM Disk
	}
	
	vcb->vcbXTAlBlks = 0;	//	Unused, use fcb.fcbPLen instead
	vcb->vcbCTAlBlks = 0;	//	Unused, use fcb.fcbPLen instead
	
	vcb->vcbWrCnt++;															//	Compensate for write of MDB on last flush
	
	
	//--	Try to find three free FCBs for use by the volume control B*-Trees:
	
	//
	//--	Set up the Extent FCB
	//
	err = FindFileControlBlock( &fcbIndexP, &fcbsP );									//	Get new FCB
	if ( err != noErr )	goto MtVolErr;
	
	fcb = GetFileControlBlock( fcbIndexP );
	fcb->fcbEOF = volumeHeader->extentsFile.logicalSize.lo;								//	Set extent tree's LEOF
	fcb->fcbPLen = volumeHeader->extentsFile.totalBlocks * volumeHeader->blockSize;		//	Set extent tree's PEOF

	vcb->extentsRefNum		= fcbIndexP;												//	Set Extents B*-Tree file RefNum

	ClearMemory( (Ptr)fcb->fcbExtRec, sizeof(HFSExtentRecord) );

	extendedFCB = ParallelFCBFromRefnum( fcbIndexP );
	BlockMoveData( volumeHeader->extentsFile.extents, extendedFCB->extents, sizeof(HFSPlusExtentRecord) );
	
	//
	//--	Set up the Catalog FCB
	//
	err = FindFileControlBlock( &fcbIndexP, &fcbsP );
	if ( err != noErr )	goto MtVolErr;
	
	fcb = GetFileControlBlock( fcbIndexP );
	fcb->fcbEOF = volumeHeader->catalogFile.logicalSize.lo;								//	Set catalog tree's LEOF
	fcb->fcbPLen = volumeHeader->catalogFile.totalBlocks * volumeHeader->blockSize;		//	Set catalog tree's PEOF

	vcb->catalogRefNum		= fcbIndexP;												//	Set Catalog B*-Tree file RefNum

	ClearMemory( (Ptr)fcb->fcbExtRec, sizeof(HFSExtentRecord) );
	
	extendedFCB = ParallelFCBFromRefnum( fcbIndexP );
	BlockMoveData( volumeHeader->catalogFile.extents, extendedFCB->extents, sizeof(HFSPlusExtentRecord) );

	
	//
	//--	Set up the Allocations FCB
	//
	err = FindFileControlBlock( &fcbIndexP, &fcbsP );
	if ( err != noErr )	goto MtVolErr;
	
	fcb = GetFileControlBlock( fcbIndexP );
	fcb->fcbEOF = volumeHeader->allocationFile.logicalSize.lo;							//	Set catalog tree's LEOF
	fcb->fcbPLen = volumeHeader->allocationFile.totalBlocks * volumeHeader->blockSize;	//	Set catalog tree's PEOF

	vcb->allocationsRefNum	= fcbIndexP;												//	Set Catalog B*-Tree file RefNum

	ClearMemory( (Ptr)fcb->fcbExtRec, sizeof(HFSExtentRecord) );

	extendedFCB = ParallelFCBFromRefnum( fcbIndexP );
	BlockMoveData( volumeHeader->allocationFile.extents, extendedFCB->extents, sizeof(HFSPlusExtentRecord) );

	
	//
	//--	Set up the Attributes FCB
	//
	vcb->attributesRefNum = 0;		//	In case there isn't an attributes BTree
	
	if (volumeHeader->attributesFile.extents[0].blockCount != 0)
	{
		err = FindFileControlBlock( &fcbIndexP, &fcbsP );
		if ( err != noErr )	goto MtVolErr;
		
		fcb = GetFileControlBlock( fcbIndexP );
		fcb->fcbEOF = volumeHeader->attributesFile.logicalSize.lo;							//	Set attribute tree's LEOF
		fcb->fcbPLen = volumeHeader->attributesFile.totalBlocks * volumeHeader->blockSize;	//	Set attribute tree's PEOF

		vcb->attributesRefNum	= fcbIndexP;												//	Set Catalog B*-Tree file RefNum

		ClearMemory( (Ptr)fcb->fcbExtRec, sizeof(HFSExtentRecord) );

		extendedFCB = ParallelFCBFromRefnum( fcbIndexP );
		BlockMoveData( volumeHeader->attributesFile.extents, extendedFCB->extents, sizeof(HFSPlusExtentRecord) );
	}
	
	
	vcb->vcbMAdr	= (Ptr)*(long *)SysBMCPtr;					//	Set up the volume bitmap cache
	vcb->vcbCtlBuf	= (Ptr)*(long *)SysCtlCPtr;					//	Set up the volume control cache
	
	//
	//--	Finish setting up the FCBs for the extent B*-Tree and the catalog B*-Tree:
	//

	err = AccessBTree( vcb, vcb->extentsRefNum, kHFSExtentsFileID, vcb->vcbXTClpSiz, (void *) CompareExtentKeysPlus );
	if ( err != noErr )	goto MtChkErr;
	
	err = AccessBTree( vcb, vcb->catalogRefNum, kHFSCatalogFileID, vcb->vcbCTClpSiz, (void *) CompareExtendedCatalogKeys );
	if ( err != noErr )	goto MtChkErr;
	
	if (vcb->attributesRefNum)
	{
		err = AccessBTree( vcb, vcb->attributesRefNum, kHFSAttributesFileID, vcb->attributesClumpSize, (void *) CompareAttributeKeys );
		if ( err != noErr )	goto MtChkErr;
	}

	(void) SetupFCB( vcb, vcb->allocationsRefNum, kHFSAllocationFileID, volumeHeader->allocationFile.clumpSize );
	

	err = CreateVolumeCatalogCache(vcb);
	if ( err )
		goto MtChkErr;

	// set up root file count...
	
	vcb->vcbNmFls = CountRootFiles(vcb);
	
	
	//	Get the volume name from the catalog file
	(void) GetVolumeNameFromCatalog(vcb);
	
	//	If the volume was not unmounted properly, then run MountCheck.
	//	If the volume was last mounted by Bride 1.0b2 ('8.01'), then the create date may be wrong, so
	//		we should run MountCheck to fix it.
	if ( !(vcb->vcbAtrb & (short)kVolumeUnmountedMask) ||
			lastMountedVersion == '8.01')
	{
		UInt32		consistencyStatus;
		
		vcb->vcbAtrb &=	~kVolumeUnmountedMask;					//	From now until _Unmount	(clear the bit)
		*(Ptr)FSCallAsync = 0;
		err = MountCheck( vcb, &consistencyStatus );
		if ( err )
			goto MtChkErr;
	}
	else
	{
		vcb->vcbAtrb &=	~kVolumeUnmountedMask;					//	From now until _Unmount	(clear the bit)
	}

CheckRemount:

	//	₯₯This comment was in TFSVol.a, should look into it
	//	Deleted stuff patched out by KillCheckRemountNiceWay in FileMgrPatches.a <SM1>


MtVolOK:
	err = noErr;

MtVolDone:
	if ( err == noErr )
	{
		if ( !(vcb->vcbAtrb & kVolumeHardwareLockMask) )		//	if the disk is not write protected
		{
			MarkVCBDirty( vcb );								//	mark VCB dirty so it will be written
			err = FlushVolumeControlBlock( vcb );				//	Go flush the VCB info BEFORE close
		}
	}
	goto	CmdDone;



MtChkErr:
	(void) DisposeVolumeCatalogCache(vcb);
	InvalidateCatalogCache( vcb );

	*((OSErr *)HFSDSErr) = err;
	err = badMDBErr;
	
	//--	Release any resources allocated so far before exiting with an error:
MtVolErr:
	DisposeFileControlBlocks( vcb );								//	deallocate control file FCBs
	
MtVolEr1:
	DisposeVolumeCacheBlocks( vcb );								//	invalidate any cache blocks for this volume
	
MtVolEr2:
	if ( (Byte)LMGetNewMount() == 0 )								//	only deallocate VCB for new mounts
	{
		//		Remount case -- Must zero buffer and map ptrs in old VCB.
		//		Restore VCB to its original off-line state (ie clear VCBDrvNum,
		//		and move drive number to VCBDRefNum field)
		vcb->vcbDRefNum	= vcb->vcbDrvNum;							//	write drv number to VCBDRefNum field
		vcb->vcbDRefNum	= 0;										//	clear drive number field of VCB
		vcb->vcbMAdr	= nil;										//	clear mapptr in VCB
		vcb->vcbBufAdr	= nil;										//	clear buffptr in VCB
	}
	else
	{
		DisposeVolumeControlBlock( vcb );
	}

	if ( err <= 0 )	goto CmdDone;
	
	currentIORequestPB = (IOParam *) ((QHdr *)LMGetFSQHdr())->qHead;
	goto mv_Start;	

CmdDone:
return( err );

}
#endif /* TARGET_OS_MAC */



#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	MountHFSVolume
//
// Function: 	The VCB pointers are checked to see if a volume in the
//				specified drive is already on-line (error if so).  Reads in the
//				directory master block and allocates memory for VCB and additional
//				data structures, depending on the volume type:
//
//				- For MFS volumes (handled in MFSVol), space is allocated for
//				  a volume buffer, as well as the block map, which is read in
//				  from disk).
//
//				- For TFS volumes, space is allocated for a bitmap cache, a volume
//				  control cache (for B*-Tree use), and a volume cache.	In addition,
//				  two FCBs are used by the B*-Trees.
//
//				No new VCB storage is allocated for remounts.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹

OSErr	MountHFSVolume( short driveNumber, IOParam* lowMemParamBlock, OSErr err )
{
	HFSMasterDirectoryBlock	*mdb;
	QHdr					*vcbQHdr;
	Byte					*flagsP;
	Byte					*mdbFlagsP;
	FSVarsRec				*fsVars;
	IOParam					*currentIORequestPB;
	Ptr 					fcbsP;
	FCB						*fcb;
	ExtendedVCB				*vcb;
	DrvQEl					*dqe;
	short					ioRefNum;
	UInt16					fcbIndexP;
	
	if ( err != noErr )
		goto RemountExit;
	
	ioRefNum = lowMemParamBlock->ioRefNum;
	mdb = (HFSMasterDirectoryBlock *) lowMemParamBlock->ioBuffer;			//	₯₯ figure out how we use VolumeHeader & MasterDirectoryBlock, size of structures, etc.	
	
	err = ValidMasterDirectoryBlock( mdb );								//	is this an HFS/HFS PlusDisk
	if ( err == noErr )
	{
		err = notARemountErr;											//	cuz we're looking for remounts only here
		vcbQHdr = LMGetVCBQHdr();										//	search the queue of VCBs
		
		//	CheckRemountLoop:
		for ( vcb = (ExtendedVCB*)vcbQHdr->qHead ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
		{
			if (   (vcb->vcbDrvNum	== 0)								//	matching volume better be off-line (0 drive num)
				&& (vcb->vcbSigWord	== mdb->drSigWord)					//	Same signature?
				&& (vcb->vcbCrDate	== mdb->drCrDate)					//	Same create date?
				&& (vcb->vcbLsMod	== mdb->drLsMod)					//	Same mod date?
			   )
			{
				//	At this point, we're fairly certain that the two volumes are, in fact, the same,
				//	but just to make sure (and avoid problems with stuck clocks), we'll compare the
				//	volume names as well:
				//₯₯	Modify this call to use the extended VCB ascii name, not the Unicode name
				if ( EqualString( vcb->vcbVN, mdb->drVN, true, true) )	//	caseSensitive, diacSensitive
					break;												//	Remount
			}
		}
		if ( vcb == nil ) goto RemountExit;
	
		//	We are now convinced that we have a remount.  We will rebuild the attributes
		//	byte, which is the only one that could have changed.  We'll also store the
		//	current drive number and driver refNum, and mark the volume dirty.
		//	Remount:
	
		((CntrlParam*)lowMemParamBlock)->csCode = drvStsCode;			//	Drive status!
		((CntrlParam*)lowMemParamBlock)->csParam[1] &= 0x00ff;			//	Clear status byte for return
		
		err = PBStatusSync( (ParmBlkPtr)lowMemParamBlock );				//	refnum and drivenum set up by read call
		
		//--	Now propagate the write protect status byte back to the vcb
		flagsP = ((Byte *)(&(vcb->vcbAtrb))) + 1;
		mdbFlagsP = ((Byte *)(((CntrlParam*)lowMemParamBlock)->csParam)+2);
		*flagsP = *mdbFlagsP & kVolumeHardwareLockMask;

		vcb->vcbDrvNum	= driveNumber;
		vcb->vcbDRefNum	= ioRefNum;										//	Driver RefNum
		
		#if ( hasHFSManualEject )
			SetVCBManEject(vcb);
		#endif
		
		if ( !(*flagsP & kVolumeHardwareLockMask) )						//	if bit is NOT set
		{
			vcb->vcbAtrb &= ~kVolumeUnmountedMask;						//	clear the kVolumeUnmounted attribute
			MarkVCBDirty( vcb );
			err = FlushVolumeControlBlock( vcb );						//	Go flush the VCB info BEFORE close
		}
		
		err = noErr;
	}


RemountExit:
	if ( err == noErr )
		goto CmdDone;													//	noErr implies a successful remount			
		
	//--	if we are allowing new mounts, let the ROM handle the details.  Otherwise, let the error stand
	fsVars = (FSVarsRec*)LMGetFSMVars();								//	$BB8
	if ( fsVars->fsFlags & fsNoAllocateMask )
		goto CmdDone;
		
	//₯₯ This sets the word to -1, Asm only set the byte to -1
	LMSetNewMount( -1 );												//	assume now that it's a new volume
	
	
	
	//--	First see if the disk is already mounted.
mv_Start:
	err = paramErr;
	driveNumber = lowMemParamBlock->ioVRefNum;
	if ( driveNumber <= 0 )
		goto MtVolDone;
	
	err = GetVCBDriveNum( &vcb, driveNumber );
	if ( err == noErr )
	{
		if ( vcb->vcbDrvNum != 0 )
		{
			err = volOnLinErr;
			goto MtVolDone;
		}
	}
	
	//--	Find the appropriate drive queue entry and allocate a VCB.
	err = FindDrive( &ioRefNum, &dqe, driveNumber );
	if ( err != noErr ) goto MtVolDone;
	
	vcb = (ExtendedVCB *) NewPtrSysClear( sizeof( ExtendedVCB ) );

	if ( vcb == nil ) goto MtVolDone;									//	exit if we got no memory
	
	AddVCB( vcb, driveNumber, ioRefNum );								//	prep the VCB for cache calls

	//--	Now try reading in the master directory block.
	err = GetBlock_glue( gbDefault, kIDSectorOffset, (Ptr*)&mdb, vcb->vcbVRefNum, vcb );	//	no Flags, MDB is always block #2, vcb->vcbBufAdr == CacheQueueHeader
	if ( err != noErr )	goto MtVolEr2;									//	Quit & Release VCB on errors
	
	//--	Now check what manner of animal we have before us:
	err = RelBlock_glue( (Ptr)mdb, rbTrashMask );
	
	err = ValidMasterDirectoryBlock( mdb );
	if ( err != noErr )
	{
		if ( err == noMacDskErr )
			goto MtVolEr2;
		else
			goto MtVolEr1;
	}
	
	//	The MDB seems OK: transfer all master directory info from buffer into VCB.
	
	//	vcbSigWord is the start of duplicate mdb/vcb info. Asm VCBDInfoSt Equ 8
	//	copy all fields in mdb till the drVolBkUp field
	
	BlockMoveData( mdb, &(vcb->vcbSigWord),  (Size)(&(mdb->drVolBkUp)) - (Size)mdb );

	//	Initialize our dirID/nodePtr cache associated with this volume.
	err = InitMRUCache( sizeof(UInt32), kDefaultNumMRUCacheBlocks, &(vcb->hintCachePtr) );
	ReturnIfError( err );

	//-- Some fields, vcbNmAlBlks, vcbAlBlkSiz, and vcbFreeBks are just around for API compatability.
	//-- The HFS/HFS Plus code we will use the corresponding ExtendedVCB fields. vcbNmAlBlks * vcbAlBlkSiz
	//-- still gives correct volume size.
	vcb->totalBlocks	=	vcb->vcbNmAlBlks;
	vcb->freeBlocks		=	vcb->vcbFreeBks;
	vcb->blockSize		=	vcb->vcbAlBlkSiz;

	
	currentIORequestPB = (IOParam *) ((QHdr *)LMGetFSQHdr())->qHead;			//	current request
	currentIORequestPB->ioVRefNum = vcb->vcbVRefNum;							//	return volume refnum to user
	
	lowMemParamBlock = (IOParam *) LMParamBlock;
	((CntrlParam*)lowMemParamBlock)->csCode = drvStsCode;						//	drive status!
	((CntrlParam*)lowMemParamBlock)->csParam[1] &= 0x00ff;						//	Clear status byte for return
	
	err = PBStatusSync( (ParmBlkPtr)lowMemParamBlock );
	
	flagsP = ((Byte *)(&(vcb->vcbAtrb))) + 1;
	mdbFlagsP = ((Byte *)(((CntrlParam*)lowMemParamBlock)->csParam)+2);
	*flagsP = *mdbFlagsP & kVolumeHardwareLockMask;
	
	if ( IsARamDiskDriver() )
	{
		vcb->vcbAtrb |= kVolumeNoCacheRequiredMask;								//	yes:Κmark VCB as a RAM Disk
	}
	
	//--	copy all fields in mdb between drXTFlSize & drVolBkUp
	BlockMoveData( &(mdb->drVolBkUp), &(vcb->vcbVolBkUp), (Size)&(mdb->drXTFlSize) - (Size)&(mdb->drVolBkUp) ); //	66 bytes
	
	vcb->vcbXTAlBlks = DivUp( mdb->drXTFlSize, vcb->blockSize );				//	Pick up PEOF of extent B*-Tree
	vcb->vcbCTAlBlks = DivUp( mdb->drCTFlSize, vcb->blockSize );				//	Pick up PEOF of catalog B*-Tree
	
																				//	₯₯₯ WHY ARE WE RESTARTING THE ROAVING PTR
	vcb->nextAllocation				=	mdb->drAllocPtr;
	vcb->vcbAllocPtr = 0;														//	Restart the roving allocation ptr
	vcb->vcbWrCnt++;															//	Compensate for write of MDB on last flush
	
	
	//--	Try to find two free FCBs for use by the volume control B*-Trees:
	
	//--	Set up the Extent FCB
	err = FindFileControlBlock( &fcbIndexP, &fcbsP );
	if ( err != noErr )	goto MtVolErr;
	
	fcb = GetFileControlBlock( fcbIndexP );
	fcb->fcbEOF = mdb->drXTFlSize;												//	Set catalog tree's LEOF
	fcb->fcbPLen = mdb->drXTFlSize;												//	Set catalog tree's PEOF

	vcb->extentsRefNum		= fcbIndexP;										//	Set Extents B*-Tree file RefNum
	BlockMoveData( mdb->drXTExtRec, fcb->fcbExtRec, sizeof(HFSExtentRecord) );
	
	
	//--	Set up the Catalog FCB
	err = FindFileControlBlock( &fcbIndexP, &fcbsP );
	if ( err != noErr )	goto MtVolErr;
	
	fcb = GetFileControlBlock( fcbIndexP );
	fcb->fcbEOF = mdb->drCTFlSize;								//	Set catalog tree's LEOF
	fcb->fcbPLen = mdb->drCTFlSize;								//	Set catalog tree's PEOF

	vcb->catalogRefNum		= fcbIndexP;										//	Set Catalog B*-Tree file RefNum
	BlockMoveData( mdb->drCTExtRec, fcb->fcbExtRec, sizeof(HFSExtentRecord) );
	
	
	vcb->vcbMAdr	= (Ptr)*(long *)SysBMCPtr;					//	Set up the volume bitmap cache
	vcb->vcbCtlBuf	= (Ptr)*(long *)SysCtlCPtr;					//	Set up the volume control cache
	

	//--	Finish setting up the FCBs for the extent B*-Tree and the catalog B*-Tree:

	err = AccessBTree( vcb, vcb->extentsRefNum, kHFSExtentsFileID, vcb->vcbXTClpSiz, (void *) CompareExtentKeys );
	if ( err != noErr )	goto MtChkErr;
	
	err = AccessBTree( vcb, vcb->catalogRefNum, kHFSCatalogFileID, vcb->vcbCTClpSiz, (void *) CompareCatalogKeys );
	if ( err != noErr )	goto MtChkErr;

	err = CreateVolumeCatalogCache(vcb);
	if ( err )	goto MtChkErr;

	if ( !(vcb->vcbAtrb & (short)kVolumeUnmountedMask) )
	{
		UInt32		consistencyStatus;

		vcb->vcbAtrb &=	~kVolumeUnmountedMask;					//	From now until _Unmount	(clear the bit)
		*(Ptr)FSCallAsync = 0;
		err = MountCheck( vcb, &consistencyStatus );
		if ( err )	goto MtChkErr;
	}
	else
	{
		vcb->vcbAtrb &=	~kVolumeUnmountedMask;					//	From now until _Unmount	(clear the bit)
	}

CheckRemount:

	//	₯₯This comment was in TFSVol.a, should look into it
	//	Deleted stuff patched out by KillCheckRemountNiceWay in FileMgrPatches.a <SM1>


MtVolOK:
	err = noErr;

MtVolDone:
	if ( err == noErr )
	{
		if ( !(vcb->vcbAtrb & kVolumeHardwareLockMask) )		//	if the disk is not write protected
		{
			MarkVCBDirty( vcb );								//	mark VCB dirty so it will be written
			err = FlushVolumeControlBlock( vcb );				//	Go flush the VCB info BEFORE close
		}
	}
	goto	CmdDone;



MtChkErr:
	(void) DisposeVolumeCatalogCache(vcb);
	InvalidateCatalogCache( vcb );


	*((OSErr *)HFSDSErr) = err;
	err = badMDBErr;
	
	//--	Release any resources allocated so far before exiting with an error:
MtVolErr:
	DisposeFileControlBlocks( vcb );							//	deallocate control file FCBs
	
MtVolEr1:
	DisposeVolumeCacheBlocks( vcb );							//	invalidate any cache blocks for this volume
	
MtVolEr2:
	if ( (Byte)LMGetNewMount() == 0 )							//	only deallocate VCB for new mounts
	{
		//		Remount case -- Must zero buffer and map ptrs in old VCB.
		//		Restore VCB to its original off-line state (ie clear VCBDrvNum,
		//		and move drive number to VCBDRefNum field)
		vcb->vcbDRefNum	= vcb->vcbDrvNum;						//	write drv number to VCBDRefNum field
		vcb->vcbDRefNum	= 0;									//	clear drive number field of VCB
		vcb->vcbMAdr	= nil;									//	clear mapptr in VCB
		vcb->vcbBufAdr	= nil;									//	clear buffptr in VCB
	}
	else
	{
		DisposeVolumeControlBlock( vcb );
	}

	if ( err <= 0 )
		goto CmdDone;
	currentIORequestPB = (IOParam *) ((QHdr *)LMGetFSQHdr())->qHead;
	goto mv_Start;	

CmdDone:
	return( err );

}
#endif	/* TARGET_OS_MAC */


#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	UnMountVolume
//
//	Function: 	All files on the volume in the drive are closed and any changed
//				directory information is written out to the diskette.  Memory
//				for the VCB, volume buffer, and block map is deallocated.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹

OSErr	UnMountVolume( VolumeParam *volume, WDCBRecPtr *wdcb )
{
	FCB						*fcb;
	OSErr					err;
	SInt16					nameLength;
	Boolean					nameSpecified;
	ExtendedVCB				*vcb;
	unsigned char			*pathName;
	UInt16					index;
	Ptr						fcbs;
	Boolean					doneSearching;
	Boolean					isHFSPlus;
	FSSpec					spec;
	UInt16					driveNumber;
	HFSMasterDirectoryBlock	*mdb;
	Boolean					mustRenameWrapperVolume = false;

	LMSetFlushOnly( 0 );										//	Setup same as UnmountVol
	err = DeturmineVolume3( volume, &nameLength, &nameSpecified, &vcb, wdcb, &pathName );
	if ( err != noErr )
		goto CmdDone;
		
	isHFSPlus	= ( vcb->vcbSigWord == kHFSPlusSigWord );
	
	//--	Get the volume name from the catalog file to set in the HFS wrapper later
	if ( (isHFSPlus) && (vcb->vcbDrvNum != 0) )					//	HFS+ volume and on-line
	{
		UInt32					hint;
		CatalogNodeData			nodeData;
		
		err = GetCatalogNode( vcb, kHFSRootFolderID, NULL, kNoHint, &spec, &nodeData, &hint );
		if ( err == noErr )
		{
			err = GetBlock_glue( gbDefault, kIDSectorOffset, (Ptr*)&mdb, vcb->vcbVRefNum, vcb );	//	Read the MDB
			if ( err == noErr )
			{
				if ( mdb->drSigWord == kHFSSigWord )			//	it's a wrapped volume
				{
					if ( memcmp( spec.name, mdb->drVN, spec.name[0]+1 ) != 0 )	//	Has the name changed?
					{
						driveNumber = vcb->vcbDrvNum;
						mustRenameWrapperVolume = true;
					}
				}
				err = RelBlock_glue( (Ptr)mdb, rbTrashMask );
			}
		}
	}
	
	//	Initialize fcbs pointer
	Get1stFileControlBlock( &index, &fcbs );

	if ( !(volume->ioTrap & kHFSMask) )							//	if NOT Unconditional unmount?
	{
		//--	Search the FCB array for open files that reference the volume...
		do
		{
			fcb = GetFileControlBlock( index );
			if ( (fcb->fcbFlNm != 0) && (fcb->fcbVPtr == vcb) )	//	The file is currently open and on this volume
			{
				if ( fcb->fcbFlNm >= fsUsrCNID )				//	is it a user file?
				{
					//--	Found an open user file on the volume, so return a busy condition...
					err = fBsyErr;
					break;
				}
			}
			
			doneSearching = GetNextFileControlBlock( &index, fcbs );
			
		} while ( !doneSearching );
	}
	
	if ( err == noErr )
	{
		err = CheckExternalFileSystem( vcb );
		if ( err == noErr )
		{
			FlushVolume( vcb );
			//	Unmounting a TFS volume: close the volume control B*-Tree and use the
			//	volume buffer to write out the MDB

			//	From UnmountForTheNineties in FileMgrPatches.a
			//	Here's the change:  we no longer check whether the volume is offline.  Instead we always
			//	go ahead and close the control files, since we no longer close them on _Offline or _Eject.
	
			if ( vcb->vcbDrvNum != 0 )						//	Check drive number: vol. offline?
			{
				err = FlushVolumeBuffers( vcb );
				if ( err != noErr )
					goto CmdDone;							//	Punt on errors
			}
			else
			{
				//	here begins CloseControlFiles, which is derived from FlushBuffers
				//	Now we have to close the control files because we no longer close them 
				//	in Eject and Offline
				
				if ( LMGetFlushOnly() == 0 )				//	volume needs flushing
				{
					err = CloseFile( vcb, vcb->catalogRefNum, fcbs );
					if ( err != noErr )
						goto CmdDone;						//	Punt on errors
					
					err = CloseFile( vcb, vcb->extentsRefNum, fcbs );
					if ( err != noErr )
						goto CmdDone;						//	Punt on errors
						
					if ( isHFSPlus )
					{
						err = AttributesCloseVolume( vcb );
						if ( err != noErr )
							goto CmdDone;

						err = CloseFile( vcb, vcb->allocationsRefNum, fcbs );
						if ( err != noErr )
							goto CmdDone;
					}

					(void) DisposeMRUCache(vcb->hintCachePtr);
					(void) DisposeVolumeCatalogCache(vcb);
					InvalidateCatalogCache( vcb );
					DisposeVolumeCacheBlocks( vcb );
				}
			}

			if ( LMGetFlushOnly() == 0 )					//	volume needs flushing
			{
				//	For UnMountVol, dequeue the VCB and trash any WDCBs for this volume ...
				DisposeVolumeControlBlock( vcb );
			}
			
			//	The volume is now unmounted, if it's an HFS+ volume, and the name does not match what's
			//	in the MDB, mount the HFS wrapper rename the wrapper volume and unmount it.
			if ( mustRenameWrapperVolume )
			{
				RenameWrapperVolume( spec.name, driveNumber );
			}
			
			err = noErr;
		}
	}

CmdDone:
	return( err );
}
#endif /* TARGET_OS_MAC */


#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	RenameWrapperVolume	
//
//	Function: 	Called during UnmountVolume if the HFS+ volume name does not
//				match the name stored in the wrapper MDB.  This function
//				renames the HFS wrapper volume.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
void	RenameWrapperVolume( Str27 newVolumeName, UInt16 driveNumber )
{
	VolumeParam		volume;
	ExtendedVCB		*vcb;
	OSErr			err;
	UInt32			hint;
	FSSpec			spec;
	CatalogNodeData	nodeData;
	Ptr				pmsp;
	UInt32			modDate;
	WDCBRecPtr		wdcbPtr;
	
	ClearMemory( &volume, sizeof(VolumeParam) );
	volume.ioVRefNum	= driveNumber;
	
	err = MountVolume( &volume, true );
	if ( err != noErr )
		return;		// punt on errors
	
	err = GetVCBDriveNum( &vcb, driveNumber );
	if ( err != noErr )
		goto ErrorExit;		// punt on errors

	modDate	= vcb->vcbLsMod;
	
	err = GetCatalogNode( vcb, kHFSRootFolderID, NULL, kNoHint, &spec, &nodeData, &hint );
	if ( err != noErr )
	{
		#if DEBUG_BUILD
			DebugStr("\p GetCatalogNode failed");
		#endif
		goto ErrorExit;		// punt on errors
	}
	
	err = RenameCatalogNode( vcb, kHFSRootParentID, spec.name, newVolumeName, hint, &hint );
	if ( err != noErr )
	{
		#if DEBUG_BUILD
			DebugStr("\p RenameCatalogNode failed");
		#endif
		goto ErrorExit;		// punt on errors
	}

	BlockMoveData( newVolumeName, vcb->vcbVN, sizeof(Str27) );
	vcb->vcbLsMod	= modDate;
	MarkVCBDirty( vcb );

	pmsp	= LMGetPMSPPtr();
	*((short*)(pmsp + kPoorMansSearchIndex))	= 0;			//	Invalidate Poor Mans search path

ErrorExit:

	(void) UnMountVolume( &volume, &wdcbPtr );
}
#endif /*TARGET_OS_MAC */


#if TARGET_OS_MAC
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	CheckExternalFileSystem		Asm: CkExtFS
//
//	Function: 	Simple routine to separate out calls to be handled by an
//				external file system.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	CheckExternalFileSystem( ExtendedVCB *vcb )
{
	if ( vcb->vcbFSID == 0 )
	{
		return( noErr );
	}
	else
	{
		return( extFSErr );
	}
}
#endif /* TARGET_OS_MAC */




//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	CheckVolumeConsistancy
//
//	Function: 	Check kBootVolumeInconsistentMask, and run MountCheck if necessary
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	CheckVolumeConsistency( ExtendedVCB *vcb )
{
	UInt32		consistencyStatus;
	OSErr		err	= noErr;
		
#if TARGET_OS_RHAPSODY
	if ( vcb->vcbSigWord == kHFSSigWord)					//	and is an HFS disk
#else 	/* TARGET_OS_RHAPSODY */
	if ( ( *((UInt16*)&(((ROMHeaderRec *) LMGetROMBase())->MachineNumber)) == 0x077D ) &&	//	is supermario based
		 ( vcb->vcbSigWord == kHFSSigWord ) )					//	and is an HFS disk
#endif 	/* TARGET_OS_RHAPSODY */
	{
		if ( vcb->vcbAtrb & kHFSBootVolumeInconsistentMask )		//	was marked inconsistant
		{
			err = MountCheck( vcb, &consistencyStatus );
			
			if ( err == noErr )
				vcb->vcbAtrb &= ~kHFSVolumeSoftwareLockMask;		//	clear the bit
		}
#if TARGET_OS_MAC
		if ( LMGetBootDrive() == vcb->vcbDrvNum )				//	this is the boot volume
			vcb->vcbAtrb |= kHFSBootVolumeInconsistentMask;
#endif 	/* TARGET_OS_MAC */
	}
	
	return( err );
}




//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	VolumeWritable		Asm: CVFlgs
//
//	Function: 	Check the volume's flags to see if modify requests are allowed.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	VolumeWritable( ExtendedVCB *vcb )
{
	if ( !(vcb->vcbAtrb & 0x8000) )		//	if the volume is not locked
	{
		if ( ! (*((Ptr)&(vcb->vcbAtrb) + 1) & kHFSVolumeHardwareLockMask) )	//	if it's not write protected
			return( noErr );
		else
			return( wPrErr );
	}
	else
	{
		return( vLckdErr );
	}
}





#if TARGET_OS_MAC

//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	FlushVolume		Asm: FlushVFiles
//
//	Function: 	All file buffers on the volume are flushed and any changed
//				directory information is written out to the disk.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	FlushVolume( ExtendedVCB *vcb )
{
	UInt16			index;
	Ptr				fcbs;
	FCB				*fcb;
	OSErr			err;
	Boolean			doneSearching;
	
	Get1stFileControlBlock( &index, &fcbs );

//ckNxtFCB
	err = noErr;
	do
	{
		fcb = GetFileControlBlock( index );
		if ( (fcb->fcbFlNm != 0) && (fcb->fcbVPtr == vcb) )	//	The file is currently open and on this volume
		{
			if ( fcb->fcbFlNm >= fsUsrCNID )				//	is it a user file?
			{
				//--	Found an open user file on the volume, so return a busy condition...
				err = CloseFile( vcb, index, fcbs );
				if ( err != noErr )
					break;
			}
		}
		
		doneSearching = GetNextFileControlBlock( &index, fcbs );
		
	} while ( !doneSearching );
	
	return( err );
}



//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	DisposeFileControlBlocks from Asm: DsposFCBs
//
//	Function: 	Clears the extent B*-Tree refNum & catalog refnum
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
void	DisposeFileControlBlocks( ExtendedVCB *vcb )
{
	OSErr	err;
	
	err = AttributesCloseVolume( vcb );
#if DEBUG_BUILD
	if ( err != noErr ) DebugStr("\pHFS+: Can't close Property Manager");
#endif

	if ( vcb->extentsRefNum != 0 )
	{
		//--	Clear extent B*-Tree refNum file number in FCB as unused
		( (FCB *) ( LMGetFCBSPtr() + vcb->extentsRefNum ) )->fcbFlNm = 0L;
	}
	if ( vcb->catalogRefNum != 0 )
	{
		//--	Clear catalog refnum file number in FCB as unused
		( (FCB *) ( LMGetFCBSPtr() + vcb->catalogRefNum ) )->fcbFlNm = 0L;
	}
	
	if ( vcb->vcbSigWord == kHFSPlusSigWord && vcb->allocationsRefNum != 0)
	{
		//--	Clear allocation (bitmap) file number in FCB as unused
		( (FCB *) ( LMGetFCBSPtr() + vcb->allocationsRefNum ) )->fcbFlNm = 0L;
	}
}

#endif /* TARGET_OS_MAC */


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	AccessBTree from Asm: AccessBT
//
//	Function:	Create an access path for a B*-Tree file:
//				1. Initialize the FCB
//				2. Attach a B*-Tree control block
//
//	Note: 		For now the compare proc is the entry to the assembly glue calling a C compare function.
//				Once BTOpen is ported to C, we can pass in C comparison routines.
//
// 	Result:		I/O result from BTOpen
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
#if TARGET_OS_MAC
OSErr	AccessBTree( ExtendedVCB *vcb, FileReference refNum, UInt32 fileID, UInt32 fileClumpSize, void *CompareRoutine )
{
	FCB				*fcb;
	OSErr			err;

	fcb = SetupFCB( vcb, refNum, fileID, fileClumpSize );
	
	err = OpenBTree( refNum, CompareRoutine );
	
	return( err );
}
#endif /* TARGET_OS_MAC */

//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	SetupFCB fills in the FCB info
//
//	Returns:	The filled up FCB
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
#if TARGET_OS_MAC
FCB	*SetupFCB( ExtendedVCB *vcb, FileReference refNum, UInt32 fileID, UInt32 fileClumpSize )
{
	FCB				*fcb;

	fcb = (FCB *) GetFileControlBlock( refNum );
	
	fcb->fcbFlags		= 0;
	fcb->fcbSBlk		= 0;
	fcb->fcbCrPs		= 0;
	fcb->fcbFlNm		= fileID;
	fcb->fcbVPtr		= vcb;
	fcb->fcbClmpSize	= fileClumpSize;
	
	// for debugging...	
	switch (fileID)
	{
		case kHFSExtentsFileID:
			BlockMoveData( "\p**** EXTENTS B-TREE", fcb->fcbCName, sizeof("\p**** EXTENTS B-TREE") );
			break;
		case kHFSCatalogFileID:
			BlockMoveData( "\p**** CATALOG B-TREE", fcb->fcbCName, sizeof("\p**** CATALOG B-TREE") );
			break;
		case kHFSAllocationFileID:
			BlockMoveData( "\p**** VOLUME BITMAP", fcb->fcbCName,  sizeof("\p**** VOLUME BITMAP") );
			break;
		case kHFSAttributesFileID:
			BlockMoveData( "\p**** ATTRIBUTES B-TREE", fcb->fcbCName,  sizeof("\p**** ATTRIBUTES B-TREE") );
			break;
	}
	
	return( fcb );
}
#endif /* TARGET_OS_MAC */


#if TARGET_OS_MAC

//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	FindFileControlBlock from Asm: FindFCB
//
//	Function:	Find an unused FCB
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	FindFileControlBlock( UInt16 *index, Ptr *fcbsH )
{
	Boolean			doneSearching;
	OSErr			err;
	FCB				*fcb;
	
	Get1stFileControlBlock( index, fcbsH );
	
	do
	{
		doneSearching = GetNextFileControlBlock( index, *fcbsH );
		if ( doneSearching == false )
		{
			fcb = GetFileControlBlock( *index );
			if ( fcb->fcbFlNm == 0 )										//	FCB unused?
			{
				ClearMemory( fcb, sizeof( FCB ) );
				fcb->fcbFlNm = -1;											//	Mark it in use
				err = noErr;
				doneSearching = true;
			}
		}
		else
		{
			err = tmfoErr;
		}
	}	while ( doneSearching == false );
	
	return( err );
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	Get1stFileControlBlock from Asm: Gt1stFCB
//
//	Function:	Point to the first FCB
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
void	Get1stFileControlBlock( UInt16 *index, Ptr *fcbsH )
{
	//		struct FCBSRec {
	//			UInt16					length;								//	first word is FCB part length
	//			FCB						fcb[1];								//	fcb array
	//		};
	*fcbsH = LMGetFCBSPtr();
	*index = 2;
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	GetNextFileControlBlock from Asm: GtNxtFCB
//
//	Function:	Increment to the next FCB
//					Check if we've hit the end fcb yet
//
// 	Result:		Return true if end was reached
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
Boolean	GetNextFileControlBlock( UInt16 *index, Ptr fcbsP )
{

	*index += LMGetFSFCBLen();
	 
	return ( *index == *((UInt16 *)fcbsP) );									//	first word is length
}

#endif /* TARGET_OS_MAC */


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	DivUp from Asm: DivUp
//
//	Function:	Given a number of bytes and block size, calculate the number of
//				blocks needd to hold all the bytes.
//
// 	Result:		Number of physical blocks needed
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
UInt16	DivUp( UInt32 byteRun, UInt32 blockSize )
{
	UInt32	blocks;
	
	blocks = (byteRun + blockSize - 1) / blockSize;							//	Divide up, remember this is integer math.
	
	if ( blocks > 0xffff )													//	maximum 16 bit value
		blocks = 0xffff;
		
	return( (UInt16) blocks );
}




//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	HFSBlocksFromTotalSectors
//
//	Function:	Given the total number of sectors on the volume, calculate
//				the 16Bit number of allocation blocks, and allocation block size.
//
// 	Result:		none
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
void	HFSBlocksFromTotalSectors( UInt32 totalSectors, UInt32 *blockSize, UInt16 *blockCount )
{
	UInt16	newBlockSizeInSectors	= 1;
	UInt32	newBlockCount			= totalSectors;
	
	while ( newBlockCount > 0XFFFF )
	{
		newBlockSizeInSectors++;
		newBlockCount	=  totalSectors / newBlockSizeInSectors;
	}
	
	*blockSize	= newBlockSizeInSectors * 512;
	*blockCount	= newBlockCount;
}




//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	ValidMasterDirectoryBlock
//
//	Function:	Run some sanity checks to make sure the MDB is valid
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	ValidMasterDirectoryBlock( HFSMasterDirectoryBlock *mdb )
{
	OSErr	err;
	
	if ( (mdb->drSigWord == kHFSPlusSigWord) || (mdb->drSigWord == kHFSSigWord) )	//	if HFS or HFS Plus volume
	{
		if ( (mdb->drAlBlkSiz != 0) && ((mdb->drAlBlkSiz & 0x01FF) == 0) )			//	non zero multiple of 512
			err = noErr;
		else
			err = badMDBErr;
	}
	else
	{
		err = noMacDskErr;
	}
	
	return( err );
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	ValidVolumeHeader
//
//	Function:	Run some sanity checks to make sure the VolumeHeader is valid
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	ValidVolumeHeader( HFSPlusVolumeHeader *volumeHeader )
{
	OSErr	err;
	
	if ( volumeHeader->signature == kHFSPlusSigWord && volumeHeader->version == kHFSPlusVersion )
	{
		if ( (volumeHeader->blockSize != 0) && ((volumeHeader->blockSize & 0x01FF) == 0) )			//	non zero multiple of 512
			err = noErr;
		else
			err = badMDBErr;									//€€	I want badVolumeHeaderErr in Errors.i
	}
	else
	{
		err = noMacDskErr;
	}
	
	return( err );
}


#if TARGET_OS_MAC

//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	FindDrive from Asm: FindDrive
//
//	Function:	Given a drive number, this routine returns the RefNum
//				for the driver by searching the system drive queue.
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	FindDrive( short *driverRefNum, DrvQEl **dqe, short driveNumber )
{
	OSErr	err		=	nsDrvErr;						//	initialize as "drive not found"
	
	*dqe = ((DrvQEl*)LMGetDrvQHdr()->qHead);			//	get first DQE
	while ( *dqe != 0 )									//	end of list?
	{
		if ( (**dqe).dQDrive != driveNumber )			//	find the drive we're looking for?
		{
			*dqe = (DrvQEl*)(**dqe).qLink;				//	get ptr to next
		}
		else
		{
			*driverRefNum = (**dqe).dQRefNum;			//	return driver refnum
			if ( (**dqe).dQFSID == 0 )					//	is this a Mac Filesystem?
				err = noErr;
			else
				err = extFSErr;							//	no, return error
			break;
		}
	}
	
	return( err );
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	GetVCBDriveNum from Asm: GetVCBDrv
//
//	Function:	Determine VCB from DriveNum/VRefNum field
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	GetVCBDriveNum( ExtendedVCB **vcbResult, short driveNumber )
{
	ExtendedVCB	*vcb;
	
	for ( vcb = (ExtendedVCB*)((LMGetVCBQHdr())->qHead) ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
	{
		if ( vcb->vcbDrvNum == driveNumber )					//	success!
			break;
		if ( vcb->vcbDrvNum == 0 )								//	offline
		{
			if ( vcb->vcbDRefNum == driveNumber * (-1) )		//	offline match
				break;
		}
	}
	
	*vcbResult = vcb;

	if ( vcb == nil )
		return( nsvErr );
	else
		return( noErr );
 }


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	GetVCBRefNum from Asm: GetVCBRfn
//
//	Function:	Determine VCB from DriveNum/VRefNum field
//
// 	Result:		error
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
OSErr	GetVCBRefNum( ExtendedVCB **vcbResult, short vRefNum )
{
	ExtendedVCB	*vcb;
	
	for ( vcb = (ExtendedVCB*)((LMGetVCBQHdr())->qHead) ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
	{
		if ( vcb->vcbVRefNum == vRefNum )				//	success!
		{
			*vcbResult = vcb;
			return( noErr );
		}
	}
	
	return( nsvErr );
}

#endif /* TARGET_OS_MAC */


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	IsARamDiskDriver from Asm: IsRAMDiskDriver
//
//	Function:	Test if disk driver is a RAM disk
//
// 	Result:		true if RAM disk
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
Boolean	IsARamDiskDriver( void )
{
#if TARGET_OS_MAC
	OSErr			err;
	ParamBlockRec	*pb;
	Byte			diskType;
	
	pb = (ParamBlockRec *) LMParamBlock;					//	general purpose I/O param block
	pb->ioParam.ioCompletion	= 0;						//	no completion routine
	pb->cntrlParam.csCode		= infoCC;					//	pass control code
	pb->cntrlParam.csParam[0]	= 0;						//	start out clean
	pb->cntrlParam.csParam[1]	= 0;
	
	err = PBControlSync( pb );								//	get driver infoŠ
	
	if ( err == noErr )										//	this control call supported
	{
		diskType = ((Byte *)(pb->cntrlParam.csParam))[3];
		if ( (diskType == ramDiskType) || (diskType == romDiskType) )
			return( true );
	}
	
	return( false );
#else
	return( false );
#endif 	/* TARGET_OS_MAC */
}


#if TARGET_OS_MAC

OSErr	MakeVCBsExtendedVCBs( void )
{
	VCB						*oVCB;
	VCB						*nextVCB;
	ExtendedVCB				*exVCB;
	ExtendedVCB				*firstNewVCBInQueue	= nil;
	QHdr					*vcbQHdr;
	UInt16					fcbIndex;
	UInt16					i;
	Ptr						fcbs;
	FCB						*fcb;
	WDCBArray				*wdcbsAry;
	OSErr					err;
		
	vcbQHdr	= LMGetVCBQHdr();											//	search the queue of VCBs

	for ( oVCB = (VCB *)vcbQHdr->qHead ; (oVCB != nil) && (oVCB != (VCB *)firstNewVCBInQueue) ; )
	{
		nextVCB = (VCB *) oVCB->qLink;
		
		if ( (oVCB->vcbSigWord != kHFSPlusSigWord) && (oVCB->vcbFSID == 0) )	//	if it's an HFS Plus VCB, assume it is already of ExtendedVCB type
		{
			exVCB = (ExtendedVCB *) NewPtrSysClear( sizeof(ExtendedVCB) );	//	allocate space for the new ExtendedVCB
			ReturnIfError( MemError() );
			BlockMoveData( oVCB, exVCB, sizeof(VCB) );						//	copy the original VCB over
			
			if ( firstNewVCBInQueue == nil )
				firstNewVCBInQueue = exVCB;
			
			//--	Set up the Extended fields, used internally in the HFS source
			exVCB->totalBlocks		=	exVCB->vcbNmAlBlks;
			exVCB->freeBlocks		=	exVCB->vcbFreeBks;
			exVCB->blockSize		=	exVCB->vcbAlBlkSiz;
			exVCB->nextAllocation	=	(UInt16)exVCB->vcbAllocPtr;			//	Almost certain to be zero
			// Note: SuperMario ROMs clear vcbAllocPtr and then flush the MDB.
			// So, when an HFS disk gets mounted, the allocation pointer is cleared both in
			// memory and on disk.  The only way to fix this is to put HFS Plus in ROM.

			//--	Set up the caches for this volume
 			err = InitMRUCache( sizeof(UInt32), kDefaultNumMRUCacheBlocks, &(exVCB->hintCachePtr) );
			ReturnIfError( err );
			
			err = CreateVolumeCatalogCache(exVCB);
			ReturnIfError( err );
			
			//--	If we are dealing with the default VCB, set the corresponding ExtendedVCB as the default
			if ( oVCB == (VCB*) LMGetDefVCBPtr() )
				LMSetDefVCBPtr( (Ptr) exVCB );
			
			//--	Remove and deallocate the original VCB
			err = Dequeue( (QElemPtr) oVCB, (QHdrPtr) vcbQHdr );			//	Dequeue the VCB from the VCB Queue
			ReturnIfError( err );
			
			//--	Search the FCB list for refs to the old VCB pointer and update them
			Get1stFileControlBlock( &fcbIndex, &fcbs );
			do
			{
				fcb = GetFileControlBlock( fcbIndex );
				if ( fcb->fcbVPtr == (ExtendedVCB *) oVCB )	
					fcb->fcbVPtr = exVCB;
			} while ( GetNextFileControlBlock( &fcbIndex, fcbs ) == false );//	while there's still some left
			
			//--	Search the WDCB list for refs to the old VCB pointer and update them
			wdcbsAry = (WDCBArray*)LMGetWDCBsPtr();							//	Point to WDCB array
			for ( i=0 ; i< (wdcbsAry->count / sizeof(WDCBRec)) ; i++ )
			{
				if ( wdcbsAry->wdcb[i].wdVCBPtr == oVCB )
					wdcbsAry->wdcb[i].wdVCBPtr = (VCB *) exVCB;
			}
			
			//--	Deallocate memory used by the old VCB
			DisposePtr( (Ptr) oVCB );

			//--	Insert the new extended vcb in the queue
			Enqueue( (QElemPtr) exVCB, (QHdrPtr) vcbQHdr );
		}
		oVCB = nextVCB;
	}
	
	return( noErr );
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	RemountWrappedVolumes
//
//	Function:	Called by InitHFSPlus, Attempt to re-mount all HFS+ volumes
//				previously mounted as HFS wrappers.
//
//	Note:		This routine assumes vcbEmbedSigWord is cleared during mount.
//
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
extern	void	RemountWrappedVolumes( void )
{
	ExtendedVCB		*vcb;
	ExtendedVCB		*nextVCB;
	OSErr			err;

	for ( vcb = (ExtendedVCB *) ((QHdr*)LMGetVCBQHdr())->qHead ; vcb != nil ; )
	{
		//	PBMountVol and UnmountVol modify the vcb queue, so save the link
		nextVCB = (ExtendedVCB *) vcb->qLink;

		if ( (vcb->vcbEmbedSigWord == kHFSPlusSigWord) && ( vcb->vcbFSID == 0) )
		{	
			err = UnmountVol( nil, vcb->vcbVRefNum );
			if ( err == noErr )
			{
				ParamBlockRec theParam;
				
				theParam.ioParam.ioCompletion	= nil;
				theParam.ioParam.ioNamePtr		= nil;
				theParam.ioParam.ioVRefNum		= vcb->vcbDrvNum;
				(void) PBMountVol( &theParam );
			}
		}
		
		vcb = nextVCB;
	}
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	FillHFSStack
//
//	Function:	Called by HFSInit.  Fills the newly created HFS stack so we 
//				can identify how much of the stack is actually being used.
//
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
extern	void	FillHFSStack( void )
{
	Ptr		p;
	SInt16	len		=	0;
	char	*s		=	"HFS+ By Don Brady, Mark Day, and Deric Horn.   ";
	
	while ( s[len] != '\0' )
		++len;
		
	for ( p = LMGetHFSStkTop() - kFileSystemStackSize ; p + len < LMGetHFSStkTop() ; p += len )
	{
		BlockMoveData( s, p, len );
	}
}


//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//	Routine:	CheckVolumeOffLine from Asm: ExtOffLinCk
//
//	Function:	Simple routine to separate out calls to be handled by an
//				external file system.
//
// 	Result:		noErr if we handle it
//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹
//€€ Same function appears in CatSearch.c as ExtOffLineCheck(), but the code is slightly different
//€€ we should delete one copy.
OSErr	CheckVolumeOffLine( ExtendedVCB *vcb )
{
	OSErr		err;
	
	err = CheckExternalFileSystem( vcb );
	if ( err == noErr && vcb->vcbDrvNum == 0 )
		err = volOffLinErr;
	
	LMSetReqstVol( (Ptr)vcb );
	
	return( err );
}

#endif /* TARGET_OS_MAC */




//_______________________________________________________________________
//
//	Routine:	CountRootFiles
//
//	Input:		pointer to VCB
//
//	Function: 	Return a count of the number of files and folders in
//				the root directory of a volume.  For HFS volumes, this
//				is maintained in the VCB (and MDB).  For HFS Plus volumes,
//				we get the valence of the root directory from its catalog
//				record.
//_______________________________________________________________________
UInt16 CountRootFiles(ExtendedVCB *vcb)
{
	OSErr			err;
	FSSpec			spec;
	CatalogNodeData	catNodeData;
	UInt32			hint;
	UInt16			rootCount;
	
//	if (vcb->vcbSigWord == kHFSSigWord || vcb->vcbFSID != 0) {
//		return vcb->vcbNmFls;
//	}
	
	//	Here, it's an HFS Plus volume, so get the valence from the root
	//	directory's catalog record.
	
	rootCount = 0;
	
	err = GetCatalogNode( vcb, kHFSRootFolderID, nil, kNoHint, &spec, &catNodeData, &hint );
	if ( err == noErr ) {
		if (catNodeData.valence < 65536)
			rootCount = catNodeData.valence;
		else
			rootCount = 65535;			//	if the value is too large, pin it
	}

	return rootCount;
}

#if TARGET_OS_MAC

//_______________________________________________________________________
//
//	Routine:	GetVolumeInformation	:	GetVolInfo
//	Arguments:	HVolumeParam	*volume
//	Output:		WDCBRec			**wdcb		(Needed for ExtFsHook in CmdDone)
//				OSErr			err
//
//	Function: 	Return information about the volume in a mounted drive.
//				If the IOVolIndex field is 0, the name of the default
//				volume is returned; if non-zero, the name of the nth
//				mounted volume is returned. The maximum length of a
//				volume name is 27 bytes.  The drive number for the
//				volume is also returned.
//_______________________________________________________________________

OSErr	GetVolumeInformation( HVolumeParam *volume, WDCBRecPtr *wdcb )
{
	FSSpec			spec;
	UInt32			maxBlocks;
	SInt16			nameLength;
	SInt16			volumeIndex;
	FileReference			vRefNum;
	OSErr			err;
	Ptr				fcbs;
	FCB				*fcb;
	ExtendedVCB		*vcb;
	unsigned char	*pathName;
	CatalogNodeData	catNodeData;
	Boolean			doneSearching;
	Boolean			nameSpecified;
	WDCBArray		*wdcbsAry;
	UInt16			fcbIndex;

	volumeIndex = volume->ioVolIndex;
	if ( volumeIndex == 0 )
	{
		err = DeturmineVolume1( (VolumeParam*)volume, &nameLength, &nameSpecified, (ExtendedVCB **)&vcb, wdcb, &pathName );
		if ( err != noErr )	goto CmdDone;
	}
	else if ( volume->ioVolIndex < 0 )
	{
		err = DeturmineVolume3( (VolumeParam*)volume, &nameLength, &nameSpecified, (ExtendedVCB **)&vcb, wdcb, &pathName );
		if ( err != noErr )	goto CmdDone;
	}
	else
	{
		nameSpecified = false;						//	Never any name specified (ioNamePtr is output arg)
		volume->ioVRefNum = 0;

		for ( vcb = (ExtendedVCB*)((LMGetVCBQHdr())->qHead) ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
		{
			if ( --volumeIndex == 0 )
				break;
		}
		if ( vcb == nil )
		{
			err = nsvErr;
			goto CmdDone;
		}
	}

	//	first copy the heart of the VCB into the parameter block
	volume->ioVCrDate	=	vcb->vcbCrDate;
	volume->ioVLsMod	=	vcb->vcbLsMod;
	volume->ioVAtrb		=	vcb->vcbAtrb;
	volume->ioVNmFls	=	vcb->vcbNmFls;				// don't call CountRootFiles(vcb) anymore
	volume->ioVBitMap	=	vcb->vcbVBMSt;
	volume->ioVNmAlBlks	=	vcb->vcbNmAlBlks;			//	we return the HFS volume format equivalents cuz ioVNmAlBlks is a short
	volume->ioVAlBlkSiz	=	vcb->vcbAlBlkSiz;
	volume->ioVClpSiz	=	vcb->vcbClpSiz;
	volume->ioAlBlSt	=	vcb->vcbAlBlSt;
	volume->ioVNxtCNID	=	vcb->vcbNxtCNID;
	volume->ioVFrBlk	=	vcb->vcbFreeBks;
	
	if ( vcb->vcbFSID != 0 )
		volume->ioAllocPtr	=	vcb->vcbAllocPtr;		// external file system
	else if ( vcb->nextAllocation < 0xFFFF )
		volume->ioAllocPtr	=	vcb->nextAllocation;
	else
		volume->ioAllocPtr	=	0xFFFF;
	

	Get1stFileControlBlock( &fcbIndex, &fcbs );
	doneSearching		=	false;

	do
	{
		fcb = GetFileControlBlock( fcbIndex );
		if ( fcb->fcbVPtr == (ExtendedVCB *) vcb )		//	The file is currently open and on this volume
		{
			if ( fcb->fcbFlNm >= fsUsrCNID )
				break;
		}
		doneSearching = GetNextFileControlBlock( &fcbIndex, fcbs );
	} while ( doneSearching == false );
	
	if ( doneSearching == true )
		volume->ioVAtrb		&=	~kFilesOpenMask;		//	Clear if no open files match
	else
		volume->ioVAtrb		|=	kFilesOpenMask;			//	Set if any files are opened

	if ( vcb == (ExtendedVCB *) LMGetDefVCBPtr() )		//	Are we supposedly the default?
		volume->ioVAtrb		|=	kDefaultVolumeMask;		//	Set volume as the default
	else
		volume->ioVAtrb		&=	~kDefaultVolumeMask;	//	Clear volume as the default
	
	*wdcb = nil;
	
	if ( (volume->ioVolIndex <= 0) && (nameSpecified == 0) )
	{
		vRefNum = volume->ioVRefNum;
		if ( vRefNum > 0 )								//	If driveNum, ignore it
		{
			goto	retVRef;
		}
		else if ( vRefNum == 0 )
		{
			vRefNum = 2;
		}
		else if ( vRefNum <= WDRfnMax )
		{
			//	a WDRefNum was supplied as VRefNum: change the number of files to be the		<21Sep85>
			//	valence of the directory in question: 										<21Sep85>
			vRefNum -= WDRfnMin;					//	Change WDRefNum to index
		}
		else
		{
			goto retVRef;
		}
		
		wdcbsAry = (WDCBArray*)LMGetWDCBsPtr();							//	Point to WDCB array
		*wdcb = (WDCBRec*)((Ptr)wdcbsAry + vRefNum);

		err = CheckVolumeOffLine( vcb );			//	check external FS err
		if ( err == noErr )
		{
			if ( (**wdcb).wdDirID != fsRtDirID )
			{
				//€€	This will change to handle Don's meta struct
				err = GetCatalogNode( (ExtendedVCB*)vcb, (**wdcb).wdDirID, nil, (**wdcb).wdCatHint, &spec, &catNodeData, (UInt32*)&((**wdcb).wdCatHint) );
				if ( err == noErr )
				{
					volume->ioVNmFls = (UInt16) catNodeData.valence;
				}
			}
		}
	}
	
	//	Do some hairy checking: if a working directory was specified, it shouldn't
	//	be overwritten by the volRefNum.	However, if a volume NAME was used, the
	//	volRefNum field is ignored, and SHOULD be filled in with the volume's VRefNum.
retVRef:
	if( (nameSpecified != 0) || (volume->ioVRefNum >= 0) )
	{
		volume->ioVRefNum = vcb->vcbVRefNum;
	}
	//	Copy the volume name field if specified.
	if ( volume->ioNamePtr != 0 )
		BlockMoveData( vcb->vcbVN, volume->ioNamePtr, vcb->vcbVN[0]+1 );


	//	Many applications call GetVolInfo for preflighting when saving files. However,
	//	they tend to use signed integers when calculating the volume's free space
	//	(vcbFreeBlks * vcbAlBlkSize).  If the free space is greater than 2 gigabytes they
	//	get a negative result and think there is not enough space left on the volume.
	//	
	//	To fix this problem we high water mark vcbFreeBlks and vcbNmAlBlks so that when
	//	multiplied by vcbAlBlkSiz the result is always less than or equal to 2GB.

	if ( volume->ioVAlBlkSiz > 0x8000 )			//	Do the mock up
	{
		//	Calculate the maximum number of allocation blocks that can fit within 2 gigabytesŠ											<LgVol>
		//	Divide 2 gig by block allocation size
		maxBlocks = 0x7FFFFFFF / volume->ioVAlBlkSiz;
		
		//	Clip total and free to the maximumŠ	
		if ( volume->ioVNmAlBlks > maxBlocks )
		{
			volume->ioVNmAlBlks = maxBlocks;		//	pin the value limit
			
			if ( volume->ioVFrBlk > maxBlocks )
			{
				volume->ioVFrBlk = maxBlocks;
			}
		}
	}
	
	//	Subtract the physical sizes of the extent B*-Tree and the catalog B*-Tree <21Sep85>
	//	from the volume size, so that the difference between the volume size and	<21Sep85>
	//	the free space reflects only the user's allocations:                      <21Sep85>
	volume->ioVNmAlBlks	-= vcb->vcbXTAlBlks;		//	Take out XT allocation
	volume->ioVNmAlBlks	-= vcb->vcbCTAlBlks;		//	Take out CT allocation
	
	if ( !(volume->ioTrap & kHFSMask) )		//	TFGetVolInfo vs. plain GetVolInfo?
	{										//	GetVolInfo
		if ( volume->ioVNmAlBlks > kMaxHFSAllocationBlocks )
			volume->ioVNmAlBlks = kMaxHFSAllocationBlocks;
		
		if ( volume->ioVFrBlk > kMaxHFSAllocationBlocks )
			volume->ioVFrBlk = kMaxHFSAllocationBlocks;
	}
	else		//	Return some additional information for TGetVolInfo requests:
	{
		volume->ioVRefNum	=	vcb->vcbVRefNum;		//	ALWAYS return VRefNum
		volume->ioVDrvInfo	=	vcb->vcbDrvNum;			//	Driver number
		volume->ioVDRefNum	=	vcb->vcbDRefNum;		//	Driver RefNum
		volume->ioVFSID		=	vcb->vcbFSID;			//	File System ID
		
		//	We return  kHFSSigWord for HFS+ volumes for copatability with
		//	existing applications.  All StuffIt products, AppleShare, FileSharing,
		//	and others incorrectly assume the volume is unsupported or MFS if the
		//	sigWord is not 'BD'. GetXVolInfo() returns the "real" value.
		volume->ioVSigWord	= vcb->vcbSigWord == kHFSPlusSigWord ? kHFSSigWord : vcb->vcbSigWord;
		
		
//volume->ioVFilCnt	=	0;					//	Clear
//volume->ioVFilCnt	=	vcb->vcbNmFls;		//	Stuff low word as if MFS
		
		volume->ioVBkUp		=	vcb->vcbVolBkUp;	//	Last backup date
		volume->ioVSeqNum	=	vcb->vcbVSeqNum;	//	Volume sequence number
		volume->ioVWrCnt	=	vcb->vcbWrCnt;		//	Volume write count
		volume->ioVFilCnt	=	vcb->vcbFilCnt;		//	Files count for volume
		volume->ioVDirCnt	=	vcb->vcbDirCnt;		//	Directory count for volume

		BlockMoveData( vcb->vcbFndrInfo, volume->ioVFndrInfo, sizeof( SInt32 ) * 8 );
	}

GVIDone:
	LMSetReqstVol( (Ptr)vcb );

	err = CheckExternalFileSystem( vcb );
	
CmdDone:
	return( err );

}




//_______________________________________________________________________
//
//	Routine:	GetXVolumeInformation	:	GetXVolInfo
//	Arguments:	XVolumeParam	*volume
//	Output:		WDCBRecPtr		*wdcb		(Needed for ExtFsHook in CmdDone)
//				OSErr			err
//
//	Function: 	Return information about the volume in a mounted drive.
//				If the IOVolIndex field is 0, the name of the default
//				volume is returned; if non-zero, the name of the nth
//				mounted volume is returned. The maximum length of a
//				volume name is 27 bytes.  The drive number for the
//				volume is also returned.
//_______________________________________________________________________

OSErr	GetXVolumeInformation( XVolumeParam *volume, WDCBRecPtr *wdcb )
{
	FSSpec			spec;
	SInt16			nameLength;
	SInt16			volumeIndex;
	FileReference			vRefNum;
	OSErr			err;
	Ptr				fcbs;
	FCB				*fcb;
	ExtendedVCB		*vcb;
	unsigned char	*pathName;
	CatalogNodeData	catNodeData;
	Boolean			doneSearching;
	Boolean			nameSpecified;
	WDCBArray		*wdcbsAry;
	UInt16			fcbIndex;

// The Installer doesn't setup ioXVersion (radar #1666217)
#if 0
	if (volume->ioXVersion != 0)
		return paramErr;
#endif

	err = noErr;		//	Assume things will work
	
	volumeIndex = volume->ioVolIndex;
	if ( volumeIndex == 0 )
	{
		err = DeturmineVolume1( (VolumeParam*)volume, &nameLength, &nameSpecified, (ExtendedVCB **)&vcb, wdcb, &pathName );
		if ( err != noErr )	goto CmdDone;
	}
	else if ( volume->ioVolIndex < 0 )
	{
		err = DeturmineVolume3( (VolumeParam*)volume, &nameLength, &nameSpecified, (ExtendedVCB **)&vcb, wdcb, &pathName );
		if ( err != noErr )	goto CmdDone;
	}
	else
	{
		nameSpecified = false;						//	Never any name specified (ioNamePtr is output arg)
		volume->ioVRefNum = 0;

		for ( vcb = (ExtendedVCB*)((LMGetVCBQHdr())->qHead) ; vcb != nil ; vcb = (ExtendedVCB*)vcb->qLink )	//	search the queue of VCBs
		{
			if ( --volumeIndex == 0 )
				break;
		}
		if ( vcb == nil )
		{
			err = nsvErr;
			goto CmdDone;
		}
	}

	//	first copy the heart of the VCB into the parameter block
	volume->ioVCrDate	=	vcb->vcbCrDate;
	volume->ioVLsMod	=	vcb->vcbLsMod;
	volume->ioVAtrb		=	vcb->vcbAtrb;
	volume->ioVBitMap	=	vcb->vcbVBMSt;
	volume->ioVNmAlBlks	=	vcb->vcbNmAlBlks;			//	16 bit value
	volume->ioVClpSiz	=	vcb->vcbClpSiz;
	volume->ioAlBlSt	=	vcb->vcbAlBlSt;
	volume->ioVNxtCNID	=	vcb->vcbNxtCNID;
	volume->ioVFrBlk	=	vcb->vcbFreeBks;
	volume->ioVNmFls	=	vcb->vcbNmFls;				// don't call CountRootFiles(vcb) anymore

	if ( vcb->vcbFSID == 0 )	// an HFS or HFS Plus disk
	{
		volume->ioVAlBlkSiz	= vcb->blockSize;			//	return true allocation blocks

		if ( vcb->nextAllocation < 0xFFFF )
			volume->ioAllocPtr	=	vcb->nextAllocation;
		else
			volume->ioAllocPtr	=	0xFFFF;
	}
	else	//	an external file system
	{
		volume->ioVAlBlkSiz	=	vcb->vcbAlBlkSiz;			
		volume->ioAllocPtr	=	vcb->vcbAllocPtr;
	}


	Get1stFileControlBlock( &fcbIndex, &fcbs );
	doneSearching		=	false;

	do
	{
		fcb = GetFileControlBlock( fcbIndex );
		if ( fcb->fcbVPtr == vcb )						//	The file is currently open and on this volume
		{
			if ( fcb->fcbFlNm >= fsUsrCNID )
				break;
		}
		doneSearching = GetNextFileControlBlock( &fcbIndex, fcbs );
	} while ( doneSearching == false );
	
	if ( doneSearching == true )
		volume->ioVAtrb		&=	~kFilesOpenMask;		//	Clear if no open files match
	else
		volume->ioVAtrb		|=	kFilesOpenMask;			//	Set if any files are opened

	if ( vcb == (ExtendedVCB *) LMGetDefVCBPtr() )		//	Are we supposedly the default?
		volume->ioVAtrb		|=	kDefaultVolumeMask;		//	Set volume as the default
	else
		volume->ioVAtrb		&=	~kDefaultVolumeMask;	//	Clear volume as the default
	
	
	*wdcb = nil;
	
	if ( (volume->ioVolIndex <= 0) && (nameSpecified == 0) )
	{
		vRefNum = volume->ioVRefNum;
		if ( vRefNum > 0 )								//	If driveNum, ignore it
		{
			goto	retVRef;
		}
		else if ( vRefNum == 0 )
		{
			vRefNum = 2;
		}
		else if ( vRefNum <= WDRfnMax )
		{
			//	a WDRefNum was supplied as VRefNum: change the number of files to be the		<21Sep85>
			//	valence of the directory in question: 										<21Sep85>
			vRefNum -= WDRfnMin;						//	Change WDRefNum to index
		}
		else
		{
			goto retVRef;
		}
		
		wdcbsAry = (WDCBArray*)LMGetWDCBsPtr();			//	Point to WDCB array
		*wdcb = (WDCBRec*)((Ptr)wdcbsAry + vRefNum);

		err = CheckVolumeOffLine( (ExtendedVCB*)vcb );			//	check external FS err
		if ( err == noErr )
		{
			if ( (**wdcb).wdDirID != fsRtDirID )
			{
				//€€	This will change to handle Don's meta struct
				err = GetCatalogNode( (ExtendedVCB*)vcb, (**wdcb).wdDirID, nil, (**wdcb).wdCatHint, &spec, &catNodeData, (UInt32*)&((**wdcb).wdCatHint) );
				if ( err == noErr )
				{
					volume->ioVNmFls = (UInt16) catNodeData.valence;
				}
			}
		}
	}
	
	//	If a working directory was specified, it shouldn't be overwritten by
	//	the volRefNum.  However, if a volume NAME was used, the volRefNum
	//	field is ignored, and SHOULD be filled in with the volume's VRefNum.
retVRef:
	if( (nameSpecified != 0) || (volume->ioVRefNum >= 0) )
	{
		volume->ioVRefNum = vcb->vcbVRefNum;
	}
	//	Copy the volume name field if specified.
	if ( volume->ioNamePtr != 0 )
		BlockMoveData( vcb->vcbVN, volume->ioNamePtr, vcb->vcbVN[0]+1 );


	//	Calculate the total and free bytes available on the volume as a 64 bit integer:

	if ( vcb->vcbSigWord == kHFSPlusSigWord )
	{
		//--	Change to use ExtendedVCB fields for HFS Plus
		MultiplyUInt32IntoUInt64( &(volume->ioVTotalBytes), vcb->totalBlocks, vcb->blockSize );
		MultiplyUInt32IntoUInt64( &(volume->ioVFreeBytes), vcb->freeBlocks, vcb->blockSize );
	}
	else
	{
		//--	It's just an HFS volume or some other external file system
		MultiplyUInt32IntoUInt64( &(volume->ioVTotalBytes), volume->ioVNmAlBlks, volume->ioVAlBlkSiz );
		MultiplyUInt32IntoUInt64( &(volume->ioVFreeBytes), volume->ioVFrBlk, volume->ioVAlBlkSiz );
	}
	
	
	volume->ioVRefNum	=	vcb->vcbVRefNum;			//	VRefNum
	volume->ioVSigWord	=	vcb->vcbSigWord;			//	Volume signature
	volume->ioVDrvInfo	=	vcb->vcbDrvNum;				//	Driver number
	volume->ioVDRefNum	=	vcb->vcbDRefNum;			//	Driver RefNum
	volume->ioVFSID		=	vcb->vcbFSID;				//	File System ID
	volume->ioVBkUp		=	vcb->vcbVolBkUp;			//	Last backup date
	volume->ioVSeqNum	=	vcb->vcbVSeqNum;			//	Volume sequence number
	volume->ioVWrCnt	=	vcb->vcbWrCnt;				//	Volume write count
	volume->ioVFilCnt	=	vcb->vcbFilCnt;				//	Files count for volume
	volume->ioVDirCnt	=	vcb->vcbDirCnt;				//	Directory count for volume

	BlockMoveData( vcb->vcbFndrInfo, volume->ioVFndrInfo, sizeof( SInt32 ) * 8 );

CmdDone:
	if ( err == noErr && volume->ioVFSID != 0)
	{
		LMSetReqstVol((Ptr) vcb);
		err = extFSErr;		// not an HFS or HFS Plus volume, let others have a crack at this call...
	}

	return( err );
}

#endif /* TARGET_OS_MAC */


//_______________________________________________________________________
//
//	Routine:	FlushVolumeControlBlock
//	Arguments:	ExtendedVCB		*vcb
//	Output:		OSErr			err
//
//	Function: 	Flush volume information to either the VolumeHeader of the Master Directory Block
//_______________________________________________________________________

OSErr	FlushVolumeControlBlock( ExtendedVCB *vcb )
{
	OSErr			err;
#if TARGET_OS_MAC
	HFSPlusVolumeHeader	*volumeHeader;
	FileReference	index;
	FCB				*fcb;
	ExtendedFCB		*extendedFCB;
#endif	
	if ( ! IsVCBDirty( vcb ) )			//	if it's not dirty
		return( noErr );

	if ( vcb->vcbSigWord == kHFSPlusSigWord )
	{
#if TARGET_OS_MAC
		err = GetBlock_glue( gbDefault, (vcb->hfsPlusIOPosOffset / 512) + kIDSectorOffset, (Ptr*)&volumeHeader, vcb->vcbVRefNum, vcb );	//	no Flags, VH is always block #0, vcb->vcbBufAdr == CacheQueueHeader
		#if( DEBUG_BUILD )
			if ( volumeHeader->signature !=	vcb->vcbSigWord )
				DebugStr("\p Bad VolumeHeader, signature does not match vcb" );
		#endif
		ReturnIfError( err );
		
		//	2005507, Keep the MDB creation date and VolumeHeader creation date in sync.
		if ( vcb->hfsPlusIOPosOffset != 0 )								//	It's a wrapped HFS+ volume
		{
			HFSMasterDirectoryBlock	*mdb;

			err = GetBlock_glue(	gbDefault,
						kIDSectorOffset,
						(Ptr*)&mdb,
						vcb->vcbVRefNum,
						vcb );
			if ( err == noErr )
			{
				if ( mdb->drCrDate	!= vcb->vcbCrDate )					//	The creation date changed
				{
					mdb->drCrDate	= vcb->vcbCrDate;
					err = RelBlock_glue( (Ptr)mdb, rbWriteMask );		//	Force it to be written
				}
				else
				{
					err = RelBlock_glue( (Ptr)mdb, rbFreeMask );		//	Just release it
				}
			}
		}

	//	volumeHeader->signature			=	vcb->signature;
	//	volumeHeader->version			=
		volumeHeader->attributes		=	vcb->vcbAtrb;				//	VCB uses 16 bits, next 16 are for future API changes
		volumeHeader->lastMountedVersion=	kHFSPlusMountVersion;		//	Let others know we've mounted volume for writing
	//₯₯	volumeName
		volumeHeader->createDate		=	vcb->vcbCrDate;				//	NOTE: local time, not GMT!
		volumeHeader->modifyDate		=	LocalToUTC(vcb->vcbLsMod);
		volumeHeader->backupDate		=	LocalToUTC(vcb->vcbVolBkUp);
		volumeHeader->checkedDate		=	vcb->checkedDate;
		volumeHeader->fileCount			=	vcb->vcbFilCnt;
		volumeHeader->folderCount		=	vcb->vcbDirCnt;
		volumeHeader->blockSize			=	vcb->blockSize;
		volumeHeader->totalBlocks		=	vcb->totalBlocks;
		volumeHeader->freeBlocks		=	vcb->freeBlocks;
		volumeHeader->nextAllocation	=	vcb->nextAllocation;
		volumeHeader->rsrcClumpSize		=	vcb->vcbClpSiz;
		volumeHeader->dataClumpSize		=	vcb->vcbClpSiz;
		volumeHeader->nextCatalogID		=	vcb->vcbNxtCNID;
		volumeHeader->writeCount		=	vcb->vcbWrCnt;
		volumeHeader->encodingsBitmap	=	vcb->encodingsBitmap;

		//€€ should we use the vcb or fcb clumpSize values ????? -djb
		volumeHeader->allocationFile.clumpSize	= vcb->allocationsClumpSize;
		volumeHeader->extentsFile.clumpSize	= vcb->vcbXTClpSiz;
		volumeHeader->catalogFile.clumpSize	= vcb->vcbCTClpSiz;
		
		BlockMoveData( vcb->vcbFndrInfo, volumeHeader->finderInfo, sizeof(volumeHeader->finderInfo) );
	
		index = vcb->extentsRefNum;
		extendedFCB = GetParallelFCB( index );
		BlockMoveData( extendedFCB->extents, volumeHeader->extentsFile.extents, sizeof(HFSPlusExtentRecord) );
		fcb = GetFileControlBlock( index );
		volumeHeader->extentsFile.logicalSize.lo = fcb->fcbEOF;
		volumeHeader->extentsFile.logicalSize.hi = 0;
		volumeHeader->extentsFile.totalBlocks = fcb->fcbPLen / vcb->blockSize;
	
		index = vcb->catalogRefNum;
		extendedFCB = GetParallelFCB( index );
		BlockMoveData( extendedFCB->extents, volumeHeader->catalogFile.extents, sizeof(HFSPlusExtentRecord) );
		fcb = GetFileControlBlock( index );
		volumeHeader->catalogFile.logicalSize.lo = fcb->fcbPLen;
		volumeHeader->catalogFile.logicalSize.hi = 0;
		volumeHeader->catalogFile.totalBlocks = fcb->fcbPLen / vcb->blockSize;

		index = vcb->allocationsRefNum;
		extendedFCB = GetParallelFCB( index );
		BlockMoveData( extendedFCB->extents, volumeHeader->allocationFile.extents, sizeof(HFSPlusExtentRecord) );
		fcb = GetFileControlBlock( index );
		volumeHeader->allocationFile.logicalSize.lo = fcb->fcbPLen;
		volumeHeader->allocationFile.logicalSize.hi = 0;
		volumeHeader->allocationFile.totalBlocks = fcb->fcbPLen / vcb->blockSize;
	
		index = vcb->attributesRefNum;
		if (index != 0)		//	Only update fields if an attributes file existed and was open
		{
			extendedFCB = GetParallelFCB( index );
			BlockMoveData( extendedFCB->extents, volumeHeader->attributesFile.extents, sizeof(HFSPlusExtentRecord) );
			fcb = GetFileControlBlock( index );
			volumeHeader->attributesFile.logicalSize.lo = fcb->fcbPLen;
			volumeHeader->attributesFile.logicalSize.hi = 0;
			volumeHeader->attributesFile.clumpSize = fcb->fcbClmpSize;
			volumeHeader->attributesFile.totalBlocks = fcb->fcbPLen / vcb->blockSize;
		}
		
		//--	Write the MDB out by releasing the block dirty
		
		err = RelBlock_glue( (Ptr)volumeHeader, rbWriteMask );		//	force it to be written
	
		#if( hasHFSManualEject )
			// The critical volume info that needs to be maintained
			// for volumes in manual-eject drives has just been flushed.
			if (err == noErr)
				vcb->vcbFlags &= ~vcbFlushCriticalInfoMask;
		#endif
		
		MarkVCBClean( vcb );
#else
		// This does now what above does also, so lets keep it all together in a single place
        err = C_FlushMDB( vcb );		//	Go flush the VCB info BEFORE close
#endif
	}
	else
	{
		// This routine doesn't really return an error!!!
		// So for now, we will just return noErr
		err = C_FlushMDB( vcb );		//	Go flush the VCB info BEFORE close
		return( noErr );
	}

	return( err );
}


ExtendedFCB* GetParallelFCB(FileReference fref)
{
#if TARGET_OS_MAC
	ParallelFCB*	pfcbArray;
	FSVarsRec*		fsVars;
	short			index;

	index = fref / sizeof(FCB);
	
	fsVars = (FSVarsRec*) LMGetFSMVars();

	pfcbArray = (ParallelFCB*) fsVars->fcbPBuf;

	return &pfcbArray->extendedFCB[index];
#else
	return &VTOH(fref)->h_xfcb->fcb_extFCB;
#endif /* TARGET_OS_MAC */
}


//_______________________________________________________________________
//
//	Routine:	FlushAlternateVolumeControlBlock
//	Arguments:	ExtendedVCB		*vcb
//				Boolean			ifHFSPlus
//	Output:		OSErr			err
//
//	Function: 	Flush volume information to either the Alternate VolumeHeader of the 
//				Alternate Master Directory Block.  Called by the BTree when the catalog
//				or extent files grow.  Simply BlockMoves the original to the alternate
//				location.
//_______________________________________________________________________

OSErr	FlushAlternateVolumeControlBlock( ExtendedVCB *vcb, Boolean isHFSPlus )
{
	OSErr			err;
	UInt32			alternateBlockLocation;

	err = FlushVolumeControlBlock( vcb );
	
	if ( isHFSPlus )															//	Flush the VolumeHeader
	{
		HFSPlusVolumeHeader	*volumeHeader;
		HFSPlusVolumeHeader	*alternateVolumeHeader;

		//--	Get the VolumeHeader Block, 1st block in the HFS Plus partition
#if TARGET_OS_MAC
		err = GetBlock_glue( gbDefault, (vcb->hfsPlusIOPosOffset / 512) + kIDSectorOffset, (Ptr*)&volumeHeader, vcb->vcbVRefNum, vcb );	//	VH is always 1st sector of HFS Plus partition
#else
                err = GetBlock_glue( gbDefault, (vcb->hfsPlusIOPosOffset / 512) + kIDSectorOffset, (Ptr*)&volumeHeader, kNoFileReference, vcb );	//	VH is always 1st sector of HFS Plus partition
#endif
		ReturnIfError( err );
		
		//--	Get the Alternate VolumeHeader Block, last sector in the HFS Plus partition
		alternateBlockLocation = (vcb->hfsPlusIOPosOffset / 512) + vcb->totalBlocks * (vcb->blockSize / 512) - 2;
#if TARGET_OS_MAC
		err = GetBlock_glue( gbDefault, alternateBlockLocation, (Ptr*)&alternateVolumeHeader, vcb->vcbVRefNum, vcb );	//	Alt VH is always 2nd to last sector of HFS Plus partition
#else
                err = GetBlock_glue( gbDefault, alternateBlockLocation, (Ptr*)&alternateVolumeHeader, kNoFileReference, vcb );	//	Alt VH is always 2nd to last sector of HFS Plus partition
#endif
		if ( err == noErr )
		{
			//--	Copy the VolumeHeader to the alternate location
			BlockMoveData( volumeHeader, alternateVolumeHeader, sizeof(HFSPlusVolumeHeader) );
	
			//--	And write it back to the disk, NOW!
			err = RelBlock_glue( (Ptr)volumeHeader, rbFreeMask );				//	free the MDB
			err = RelBlock_glue( (Ptr)alternateVolumeHeader, rbWriteMask );		//	force it to be written
		}
		else
		{			
			err = RelBlock_glue( (Ptr)volumeHeader, rbFreeMask );				//	free the VH
		}
	}
	else																		//	Flush the MasterDirectoryBlock
	{
		HFSMasterDirectoryBlock	*mdb;
		HFSMasterDirectoryBlock	*alternateMDB;
		UInt32					numBlocks;

		
		err = GetDiskBlocks( vcb, &numBlocks );
		ReturnIfError( err );

		//--	Get the MDB, 2nd block on HFS disk
#if TARGET_OS_MAC
		err = GetBlock_glue( gbDefault, kIDSectorOffset, (Ptr*)&mdb, vcb->vcbVRefNum, vcb );	//	no Flags, MDB is always block
#else
                err = GetBlock_glue( gbDefault, kIDSectorOffset, (Ptr*)&mdb, kNoFileReference, vcb );	//	no Flags, MDB is always block
#endif
		ReturnIfError( err );

		//--	Get the Alternate MDB, 2nd to last block on disk
		alternateBlockLocation = numBlocks - 2;
#if TARGET_OS_MAC
		err = GetBlock_glue( gbDefault, alternateBlockLocation, (Ptr*)&alternateMDB, vcb->vcbVRefNum, vcb );	//	no Flags, VH is always block #0, vcb->vcbBufAdr == CacheQueueHeader
#else
                err = GetBlock_glue( gbDefault, alternateBlockLocation, (Ptr*)&alternateMDB, kNoFileReference, vcb );	//	no Flags, VH is always block #0, vcb->vcbBufAdr == CacheQueueHeader
#endif
		if ( err == noErr )
		{
			//--	Copy the MDB to the alternate location
			BlockMoveData( mdb, alternateMDB, sizeof(HFSMasterDirectoryBlock) );
			
			//--	And write it back to the disk, NOW!
			err = RelBlock_glue( (Ptr)mdb, rbFreeMask );						//	free the MDB
			err = RelBlock_glue( (Ptr)alternateMDB, rbWriteMask );				//	force it to be written
		}
		else
		{
			err = RelBlock_glue( (Ptr)mdb, rbFreeMask );						//	free the MDB
		}
	}

	return( err );
}



//_______________________________________________________________________
//
//	Routine:	TrashAllFSCaches
//	Arguments:	ExtendedVCB		*vcb
//	Output:		OSErr			err
//
//	Function: 	Trash all file system related caches as well as the disk cache.
//				This allows applications such as Disk First Aid or Norton Utilities
//				to be able to repair disks without first unmounting the volume.
//				
//				All file system caches should also have a call to the cache
//				trashing routine here!
//_______________________________________________________________________
#if TARGET_OS_MAC
OSErr	TrashAllFSCaches( ExtendedVCB *vcb )
{
	OSErr			err;
	FSVarsRec*		fsVars	= (FSVarsRec*) LMGetFSMVars();
	
	InvalidateCatalogCache( vcb );				//	Calls TrashCatalogIterator 
	TrashCatalogNodeCache( );					//	Trash Catalog node cache, gCatalogFSSpec
	err = TrashCacheBlocks( 0 );				//	Trash BTree node cache
	TrashMRUCache( vcb->hintCachePtr );			//	Trash the heuristic hint cache
	ClearMemory( fsVars->gAttributesBuffer, fsVars->gAttributesBufferSize );	//	Clear the buffer used by MountCheck and CatSearch
	TrashVolumeDiskCache( vcb );				//	Trash the disk cache, calls TRASHVBLKSA7
	
	return( err );
}
#endif /* TARGET_OS_MAC */


#if TARGET_OS_MAC
OSErr GetDiskBlocks( ExtendedVCB *vcb, unsigned long *numBlocks)
{
	/* Various constants for GetDiskBlocks() */
	enum
	{
		/* return format list status code */
		kFmtLstCode = 6,
		
		/* reference number of .SONY driver */
		kSonyRefNum = 0xfffb,
		
		/* values returned by DriveStatus in DrvSts.twoSideFmt */
		kSingleSided = 0,
		kDoubleSided = -1,
		kSingleSidedSize = 800,		/* 400K */
		kDoubleSidedSize = 1600,	/* 800K */
		
		/* values in DrvQEl.qType */
		kWordDrvSiz = 0,
		kLongDrvSiz = 1,
		
		/* more than enough formatListRecords */
		kMaxFormatListRecs = 16
	};
	
	DrvQEl			*driveQElementPtr;
	unsigned long	blocks;
	ParamBlockRec	pb;
	DrvSts			status;
	FormatListRec	formatListRecords[kMaxFormatListRecs];
	short			formatListRecIndex;
	OSErr			err;
	short 			driverRefNum;

	blocks = 0;
	
	//	Find the drive queue element for this volume
	err = FindDrive( &driverRefNum, &driveQElementPtr, vcb->vcbDrvNum );
	ReturnIfError( err );
	
	//	Make sure this is a real driver (dQRefNum < 0).
	//	AOCE's Mail Enclosures volume uses 0 for dQRefNum which will cause
	//	problems if you try to use it as a driver refNum.

	if ( driveQElementPtr->dQRefNum >= 0 )
	{
		err = paramErr;
	}
	else
	{
		//	Attempt to get the drive's format list.
		//	(see the Technical Note "What Your Sony Drives For You")
		
		pb.cntrlParam.ioVRefNum = driveQElementPtr->dQDrive;
		pb.cntrlParam.ioCRefNum = driveQElementPtr->dQRefNum;
		pb.cntrlParam.csCode = kFmtLstCode;
		pb.cntrlParam.csParam[0] = kMaxFormatListRecs;
		*(long *)&pb.cntrlParam.csParam[1] = (long)&formatListRecords[0];
		
		err = PBStatusSync(&pb);
		
		if ( err == noErr )
		{
			//	The drive supports ReturnFormatList status call.
			
			//	Get the current disk's size.
			for( formatListRecIndex = 0;
				 formatListRecIndex < pb.cntrlParam.csParam[0];
	    		 ++formatListRecIndex )
	    	{
	    		if ( (formatListRecords[formatListRecIndex].formatFlags &
	    			  diCIFmtFlagsCurrentMask) != 0 )
	    		{
	    			blocks = formatListRecords[formatListRecIndex].volSize;
	    		}
			}
    		if ( blocks == 0 )
    		{
    			//	This should never happen
    			err = paramErr;
    		}
		}
		else if ( driveQElementPtr->dQRefNum == (short)kSonyRefNum )
		{
			//	The drive is a non-SuperDrive floppy which only supports 400K and 800K disks
			
			err = DriveStatus(driveQElementPtr->dQDrive, &status);
			if ( err == noErr )
			{
				switch ( status.twoSideFmt )
				{
				case kSingleSided:
					blocks = kSingleSidedSize;
					break;
				case kDoubleSided:
					blocks = kDoubleSidedSize;
					break;
				default:
					//	This should never happen
					err = paramErr;
					break;
				}
			}
		}
		else
		{
			//	The drive is not a floppy and it doesn't support ReturnFormatList
			//	so use the dQDrvSz field(s)
			
			err = noErr;			//	reset err
			switch ( driveQElementPtr->qType )
			{
			case kWordDrvSiz:
				blocks = driveQElementPtr->dQDrvSz;
				break;
			case kLongDrvSiz:
				blocks = ((unsigned long)driveQElementPtr->dQDrvSz2 << 16) +
						 driveQElementPtr->dQDrvSz;
				break;
			default:
				//	This should never happen
				err = paramErr;
				break;
			}
		}
	}
	
	*numBlocks = blocks;
	
	return ( err );
}
#endif 	/* TARGET_OS_MAC */


//
//	Sets up a VCB for calls to GetBlock()
//
void	AddVCB( ExtendedVCB	*vcb, short driveNumber, short ioRefNum )
{
#if TARGET_OS_MAC
	OSErr			err			= noErr;
	WDCBArray		*wdcbsAry;
	QHdr			*vcbQHdr;
	WDCBRecPtr		wdcb;
			
	vcbQHdr = LMGetVCBQHdr();
	if ( vcbQHdr->qHead == nil )
	{																	//	 Queue is empty, new vcb is the default now
		LMSetDefVCBPtr( (Ptr) vcb );
		wdcbsAry = (WDCBArray*)LMGetWDCBsPtr();							//	Point to WDCB array
		
		wdcb = &(wdcbsAry->wdcb[0]);
		wdcb->wdVCBPtr	= (VCB *) vcb;									//	Set default VCB pointer in default WDCB
		wdcb->wdDirID	= fsRtDirID;									//	Default to root directory
		wdcb->wdCatHint	= 0;											//	Clear catalog hint
		wdcb->wdProcID	= 0;											//	And procID of WDCB 'owner'
		vcbQHdr = LMGetVCBQHdr();										//	Reset for VCB insertion
	}
	

	Enqueue( (QElemPtr)vcb, (QHdrPtr)vcbQHdr );							//	Insert the new VCB in the queue

	vcb->vcbDrvNum	= driveNumber;
	vcb->vcbDRefNum	= ioRefNum;
	
	#if ( hasHFSManualEject )
		SetVCBManEject(vcb);											//	set vcb maneject flag appropriately	<SM7> <BH 03Aug93>
	#endif


	vcb->vcbBufAdr = LMGetSysVolCPtr();									//	always use system-wide cache Š

	vcb->vcbVRefNum	= GetNewVRefNum();									//	Assign a refNum to this new volume.

	if ( vcb == (ExtendedVCB *) LMGetDefVCBPtr() )						//	Are we supposedly the default?
	{
		LMSetDefVRefNum( vcb->vcbVRefNum );
	}
#else
#pragma unused(vcb,driveNumber,ioRefNum,wdcbH)
#endif 	/* TARGET_OS_MAC */
}


//--------------------------------------------------------------------------------------
//	Routine:	SetVCBManEject
//	Input:		VCB ptr
//	Called by:	MountVol
//	Function:	Sets or clears the manual-eject bit in the flags word of the input VCB
//				based on the type of the drive indicated by vcbDrvNum.  All registers
//				are preserved.  The manual-eject flag should only be changed at MountVol
//				time.
//--------------------------------------------------------------------------------------

#if ( hasHFSManualEject )

	static void SetVCBManEject(ExtendedVCB *vcb)
	{
		DrvQEl	*driveQElementPtr;
		DrvSts	*driveStatsPtr;
		short	driverRefNum;
		OSErr	err;
	
		//	Find the drive queue element for this volume
		err = FindDrive( &driverRefNum, &driveQElementPtr, vcb->vcbDrvNum );
		
		if (err) return;
		
		driveStatsPtr = (DrvSts*) ((Ptr) driveQElementPtr - offsetof(DrvSts, qLink));
	
		if ( driveStatsPtr->installed & (1 << kdqManualEjectBit) )		// manual eject drive asserted?
			vcb->vcbFlags |= vcbManualEjectMask;
		else
			vcb->vcbFlags &= ~vcbManualEjectMask;
	}

#endif


//
//	Simple utility function to deturmine if a keyboard key is pressed.
//
short IsPressed( UInt16 k )		// k =  any keyboard scan code, 0-127
{
#if TARGET_OS_MAC
	unsigned char	km[16];

	GetKeys( *( (KeyMap*) &km ) );										//	Ugly casting to compile with MPWC & SC
	return ( ( km[k>>3] >> (k & 7) ) & 1);
#else
#pragma unused(k)
	return( 0 );
#endif 	/* TARGET_OS_MAC */
}


#if TARGET_OS_MAC
//
//	Utility function to allocate a new vRefNum. 
//	This function cycles through all vRefNums, rather than reuse them.
//
FileReference	GetNewVRefNum()
{
	FileReference		refNum;
	ExtendedVCB	*tempVCB;
	OSErr		err			= noErr;

	#if (0)		//	See bug 1664445
	{
		FSVarsRec	*fsVars;
	
	fsVars = (FSVarsRec*)LMGetFSMVars();
	for ( refNum = fsVars->nextVRefNum ; err == noErr ; refNum-- )
	{
		if ( refNum >= 0x8001 )											//	if we've cycled through all the possible vRef nums, start over
			refNum = -1;
			
		err = GetVCBRefNum( &tempVCB, refNum );							//	Check if already assigned
	}
	
	fsVars->nextVRefNum	= refNum;
	return( ++refNum );													//	for loop post decrements
	}
	#else
	{
		for ( refNum = -1 ; err == noErr ; refNum-- )
		{
			err = GetVCBRefNum( &tempVCB, refNum );							//	Check if already assigned
		}
		return( ++refNum );													//	for loop post decrements
	}
	#endif
}
#endif /* TARGET_OS_MAC */

//‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹