The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
**	Apple Macintosh Developer Technical Support
**
**	A collection of useful high-level File Manager routines.
**
**	by Jim Luther, Apple Developer Technical Support Emeritus
**
**	File:		MoreFilesExtras.c
**
**	Copyright © 1992-1999 Apple Computer, Inc.
**	All rights reserved.
**
**	You may incorporate this sample code into your applications without
**	restriction, though the sample code has been provided "AS IS" and the
**	responsibility for its operation is 100% yours.  However, what you are
**	not permitted to do is to redistribute the source as "DSC Sample Code"
**	after having made changes. If you're going to re-distribute the source,
**	we require that you make it clear in the source that the code was
**	descended from Apple Sample Code, but that you've made changes.
*/

#include <Types.h>
//#include <Traps.h>
#include <OSUtils.h>
#include <Errors.h>
#include <Files.h>
#include <Devices.h>
#include <Finder.h>
#include <Folders.h>
//#include <FSM.h>
//#include <Disks.h>
#include <Gestalt.h>
#include <TextUtils.h>
#include <Script.h>
#include <Math64.h>
#include <CodeFragments.h>
#include <stddef.h>

#define	__COMPILINGMOREFILES

#include "MoreFiles.h"
#include "MoreFilesExtras.h"
#include "MoreDesktopMgr.h"
#include "FSpCompat.h"

/*****************************************************************************/

/* local data structures */

/* The DeleteEnumGlobals structure is used to minimize the amount of
** stack space used when recursively calling DeleteLevel and to hold
** global information that might be needed at any time. */

#if PRAGMA_STRUCT_ALIGN
	#pragma options align=mac68k
#endif	//	PRAGMA_STRUCT_ALIGN
struct DeleteEnumGlobals
{
	OSErr			error;				/* temporary holder of results - saves 2 bytes of stack each level */
	Str63			itemName;			/* the name of the current item */
	UniversalFMPB	myPB;				/* the parameter block used for PBGetCatInfo calls */
};
#if PRAGMA_STRUCT_ALIGN
	#pragma options align=reset
#endif //	PRAGMA_STRUCT_ALIGN

typedef struct DeleteEnumGlobals DeleteEnumGlobals;
typedef DeleteEnumGlobals *DeleteEnumGlobalsPtr;

/*****************************************************************************/

/*
**	CallPBXGetVolInfoSync is the glue code needed to make PBXGetVolInfoSync
**	File Manager requests from CFM-based programs. Apple added PBXGetVolInfoSync
**	to InterfaceLib in Mac OS 8.5, so if __MACOSEIGHTFIVEORLATER is defined,
**	CallPBXGetVolInfoSync is defined back to PBXGetVolInfoSync.
**
**	Non-CFM 68K programs don't needs this glue (and won't get it) because
**	they instead use the inline assembly glue found in the Files.h interface
**	file.
*/

#if TARGET_API_MAC_CARBON || !TARGET_RT_MAC_CFM

	// Carbon builds and 68K builds don't need this glue
	#define CallPBXGetVolInfoSync PBXGetVolInfoSync

#else	//	TARGET_API_MAC_CARBON || !TARGET_RT_MAC_CFM

	#if	__WANTPASCALELIMINATION
		#undef	pascal
	#endif	//	__WANTPASCALELIMINATION
	
	/* This is exactly like the simple mixed mode glue in InterfaceLib in Mac OS 8.5 and 8.6 */
	static pascal OSErr PBXGetVolInfoSyncGlue(XVolumeParamPtr paramBlock)
	{
		enum
		{
			uppFSDispatchProcInfo = kRegisterBased
				 | REGISTER_RESULT_LOCATION(kRegisterD0)
				 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr)))
				 | REGISTER_ROUTINE_PARAMETER(1, kRegisterD0, SIZE_CODE(sizeof(long)))	/* selector */
				 | REGISTER_ROUTINE_PARAMETER(2, kRegisterD1, SIZE_CODE(sizeof(long)))	/* trap word */
				 | REGISTER_ROUTINE_PARAMETER(3, kRegisterA0, SIZE_CODE(sizeof(XVolumeParamPtr)))
		};
		
		static UniversalProcPtr	fsDispatchTrapAddress = NULL;
		
		/* Is this the first time we've been called? */
		if ( fsDispatchTrapAddress == NULL )
		{
			/* Yes - Get the trap address of _FSDispatch */
			fsDispatchTrapAddress = NGetTrapAddress(_FSDispatch, OSTrap);
		}
		return ( CallOSTrapUniversalProc(fsDispatchTrapAddress,
											uppFSDispatchProcInfo,
											kFSMXGetVolInfo,
											_FSDispatch,
											paramBlock) );
	}
	
	/*
	** PBXGetVolInfoSync was added to the File Manager in System software 7.5.2.
	** However, PBXGetVolInfoSync wasn't added to InterfaceLib until Mac OS 8.5.
	** This wrapper calls PBXGetVolInfoSync if it is found in InterfaceLib;
	** otherwise, it calls PBXGetVolInfoSyncGlue. This ensures that your program
	** is calling the latest implementation of PBXGetVolInfoSync.
	*/
	static pascal OSErr CallPBXGetVolInfoSync(XVolumeParamPtr paramBlock)
	{
		typedef pascal OSErr (*PBXGetVolInfoProcPtr) (XVolumeParamPtr paramBlock);
		
		OSErr						result;
		CFragConnectionID			connID;
		static PBXGetVolInfoProcPtr	PBXGetVolInfoSyncPtr = NULL;
		
		//* Is this the first time we've been called? */
		if ( PBXGetVolInfoSyncPtr == NULL )
		{
			/* Yes - Get our connection ID to InterfaceLib */
			result = GetSharedLibrary("\pInterfaceLib", kPowerPCCFragArch, kLoadCFrag, &connID, NULL, NULL);
			if ( result == noErr )
			{
				/* See if PBXGetVolInfoSync is in InterfaceLib */
				if ( FindSymbol(connID, "\pPBXGetVolInfoSync", &(Ptr)PBXGetVolInfoSyncPtr, NULL) != noErr )
				{
					/* Use glue code if symbol isn't found */
					PBXGetVolInfoSyncPtr = PBXGetVolInfoSyncGlue;
				}
			}
		}
		/* Call PBXGetVolInfoSync if present; otherwise, call PBXGetVolInfoSyncGlue */
		return ( (*PBXGetVolInfoSyncPtr)(paramBlock) );
	}

	#if	__WANTPASCALELIMINATION
		#define	pascal	
	#endif	//	__WANTPASCALELIMINATION

#endif	//	TARGET_API_MAC_CARBON || !TARGET_RT_MAC_CFM

/*****************************************************************************/

pascal	void	TruncPString(StringPtr destination,
							 ConstStr255Param source,
							 short maxLength)
{
	short	charType;
	
	if ( source != NULL && destination != NULL )	/* don't do anything stupid */
	{
		if ( source[0] > maxLength )
		{
			/* Make sure the string isn't truncated in the middle of */
			/* a multi-byte character. */
			while (maxLength != 0)
			{
				// Note: CharacterByteType's textOffset parameter is zero-based from the textPtr parameter
				charType = CharacterByteType((Ptr)&source[1], maxLength - 1, smSystemScript);
				if ( (charType == smSingleByte) || (charType == smLastByte) )
					break;	/* source[maxLength] is now a valid last character */ 
				--maxLength;
			}
		}
		else
		{
			maxLength = source[0];
		}
		/* Set the destination string length */
		destination[0] = maxLength;
		/* and copy maxLength characters (if needed) */
		if ( source != destination )
		{
			while ( maxLength != 0 )
			{
				destination[maxLength] = source[maxLength];
				--maxLength;
			}
		}
	}
}

/*****************************************************************************/

pascal	Ptr	GetTempBuffer(long buffReqSize,
						  long *buffActSize)
{
	enum
	{
		kSlopMemory = 0x00008000	/* 32K - Amount of free memory to leave when allocating buffers */
	};
	Ptr	tempPtr;
	
	/* Make request a multiple of 1024 bytes */
	buffReqSize = buffReqSize & 0xfffffc00;
	
	if ( buffReqSize < 0x00000400 )
	{
		/* Request was smaller than 1024 bytes - make it 1024 */
		buffReqSize = 0x00000400;
	}
	
	/* Attempt to allocate the memory */
	tempPtr = NewPtr(buffReqSize);
	
	/* If request failed, go to backup plan */
	if ( (tempPtr == NULL) && (buffReqSize > 0x00000400) )
	{
		/*
		**	Try to get largest 1024-byte block available
		**	leaving some slop for the toolbox if possible
		*/
		long freeMemory = (FreeMem() - kSlopMemory) & 0xfffffc00;
		
		buffReqSize = MaxBlock() & 0xfffffc00;
		
		if ( buffReqSize > freeMemory )
		{
			buffReqSize = freeMemory;
		}
		
		if ( buffReqSize == 0 )
		{
			buffReqSize = 0x00000400;
		}
		
		tempPtr = NewPtr(buffReqSize);
	}
	
	/* Return bytes allocated */
	if ( tempPtr != NULL )
	{
		*buffActSize = buffReqSize;
	}
	else
	{
		*buffActSize = 0;
	}
	
	return ( tempPtr );
}

/*****************************************************************************/

/*
**	GetVolumeInfoNoName uses pathname and vRefNum to call PBHGetVInfoSync
**	in cases where the returned volume name is not needed by the caller.
**	The pathname and vRefNum parameters are not touched, and the pb
**	parameter is initialized by PBHGetVInfoSync except that ioNamePtr in
**	the parameter block is always returned as NULL (since it might point
**	to the local tempPathname).
**
**	I noticed using this code in several places, so here it is once.
**	This reduces the code size of MoreFiles.
*/
pascal	OSErr	GetVolumeInfoNoName(ConstStr255Param pathname,
									short vRefNum,
									HParmBlkPtr pb)
{
	Str255 tempPathname;
	OSErr error;
	
	/* Make sure pb parameter is not NULL */ 
	if ( pb != NULL )
	{
		pb->volumeParam.ioVRefNum = vRefNum;
		if ( pathname == NULL )
		{
			pb->volumeParam.ioNamePtr = NULL;
			pb->volumeParam.ioVolIndex = 0;		/* use ioVRefNum only */
		}
		else
		{
			BlockMoveData(pathname, tempPathname, pathname[0] + 1);	/* make a copy of the string and */
			pb->volumeParam.ioNamePtr = (StringPtr)tempPathname;	/* use the copy so original isn't trashed */
			pb->volumeParam.ioVolIndex = -1;	/* use ioNamePtr/ioVRefNum combination */
		}
		error = PBHGetVInfoSync(pb);
		pb->volumeParam.ioNamePtr = NULL;	/* ioNamePtr may point to local	tempPathname, so don't return it */
	}
	else
	{
		error = paramErr;
	}
	return ( error );
}

/*****************************************************************************/

/*
**	XGetVolumeInfoNoName uses pathname and vRefNum to call PBXGetVolInfoSync
**	in cases where the returned volume name is not needed by the caller.
**	The pathname and vRefNum parameters are not touched, and the pb
**	parameter is initialized by PBXGetVolInfoSync except that ioNamePtr in
**	the parameter block is always returned as NULL (since it might point
**	to the local tempPathname).
*/
pascal	OSErr	XGetVolumeInfoNoName(ConstStr255Param pathname,
									short vRefNum,
									XVolumeParamPtr pb)
{
	Str255 tempPathname;
	OSErr error;
	
	/* Make sure pb parameter is not NULL */ 
	if ( pb != NULL )
	{
		pb->ioVRefNum = vRefNum;
		pb->ioXVersion = 0;			/* this XVolumeParam version (0) */
		if ( pathname == NULL )
		{
			pb->ioNamePtr = NULL;
			pb->ioVolIndex = 0;		/* use ioVRefNum only */
		}
		else
		{
			BlockMoveData(pathname, tempPathname, pathname[0] + 1);	/* make a copy of the string and */
			pb->ioNamePtr = (StringPtr)tempPathname;	/* use the copy so original isn't trashed */
			pb->ioVolIndex = -1;	/* use ioNamePtr/ioVRefNum combination */
		}
		
		{
#if !TARGET_API_MAC_CARBON
			long response;
			
			/* Is PBXGetVolInfo available? */
			if ( ( Gestalt(gestaltFSAttr, &response) != noErr ) || ((response & (1L << gestaltFSSupports2TBVols)) == 0) )
			{
				/* No, fall back on PBHGetVInfo */
				error = PBHGetVInfoSync((HParmBlkPtr)pb);
				if ( error == noErr )
				{
					/* calculate the ioVTotalBytes and ioVFreeBytes fields */
					pb->ioVTotalBytes = U64Multiply(U64SetU(pb->ioVNmAlBlks), U64SetU(pb->ioVAlBlkSiz));
					pb->ioVFreeBytes = U64Multiply(U64SetU(pb->ioVFrBlk), U64SetU(pb->ioVAlBlkSiz));
				}
			}
			else
#endif
			{
				/* Yes, so use it */
				error = CallPBXGetVolInfoSync(pb);
			}
		}
		pb->ioNamePtr = NULL;		/* ioNamePtr may point to local	tempPathname, so don't return it */
	}
	else
	{
		error = paramErr;
	}
	return ( error );
}

/*****************************************************************************/

pascal	OSErr GetCatInfoNoName(short vRefNum,
							   long dirID,
							   ConstStr255Param name,
							   CInfoPBPtr pb)
{
	Str31 tempName;
	OSErr error;
	
	/* Protection against File Sharing problem */
	if ( (name == NULL) || (name[0] == 0) )
	{
		tempName[0] = 0;
		pb->dirInfo.ioNamePtr = tempName;
		pb->dirInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb->dirInfo.ioNamePtr = (StringPtr)name;
		pb->dirInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	pb->dirInfo.ioVRefNum = vRefNum;
	pb->dirInfo.ioDrDirID = dirID;
	error = PBGetCatInfoSync(pb);
	pb->dirInfo.ioNamePtr = NULL;
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DetermineVRefNum(ConstStr255Param pathname,
								 short vRefNum,
								 short *realVRefNum)
{
	HParamBlockRec pb;
	OSErr error;

	error = GetVolumeInfoNoName(pathname,vRefNum, &pb);
	if ( error == noErr )
	{
		*realVRefNum = pb.volumeParam.ioVRefNum;
	}
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	HGetVInfo(short volReference,
						  StringPtr volName,
						  short *vRefNum,
						  unsigned long *freeBytes,
						  unsigned long *totalBytes)
{
	OSErr	result;
	UInt64	freeBytes64;
	UInt64	totalBytes64;
	
	// get the best values possible from XGetVInfo
	result = XGetVInfo(volReference, volName, vRefNum, &freeBytes64, &totalBytes64);
	if ( result == noErr )
	{
		// and pin those values if needed
		if ( UInt64ToUnsignedWide(freeBytes64).hi != 0 )
		{
			// pin to maximum 512-byte block aligned value
			*freeBytes = 0xfffffe00;
		}
		else
		{
			*freeBytes = U32SetU(freeBytes64);
		}
		
		if ( UInt64ToUnsignedWide(totalBytes64).hi != 0 )
		{
			// pin to maximum 512-byte block aligned value
			*totalBytes = 0xfffffe00;
		}
		else
		{
			*totalBytes = U32SetU(totalBytes64);
		}
	}
	
	return ( result );
}

/*****************************************************************************/

pascal	OSErr	XGetVInfo(short volReference,
						  StringPtr volName,
						  short *vRefNum,
						  UInt64 *freeBytes,
						  UInt64 *totalBytes)
{
	OSErr			result;
	XVolumeParam	pb;
	
#if !TARGET_API_MAC_CARBON
	
	long			response;
	
#endif	//	!TARGET_API_MAC_CARBON
	
	pb.ioVRefNum = volReference;
	pb.ioNamePtr = volName;
	pb.ioXVersion = 0;	/* this XVolumeParam version (0) */
	pb.ioVolIndex = 0;	/* use ioVRefNum only, return volume name */
	
#if !TARGET_API_MAC_CARBON

	/* See if large volume support is available */
	if ( ( Gestalt(gestaltFSAttr, &response) == noErr ) && ((response & (1L << gestaltFSSupports2TBVols)) != 0) )
	
#endif	//	!TARGET_API_MAC_CARBON
	
	{
		/* Large volume support is available */
		result = CallPBXGetVolInfoSync(&pb);
		if ( result == noErr )
		{
			/* The volume name was returned in volName (if not NULL) and */
			/* we have the volume's vRefNum and allocation block size */
			*vRefNum = pb.ioVRefNum;
			
			/* return the freeBytes and totalBytes */
			*totalBytes = pb.ioVTotalBytes;
			*freeBytes = pb.ioVFreeBytes;
		}
	}
	
#if !TARGET_API_MAC_CARBON
	
	else
	{
		/* No large volume support */
		/* Use PBHGetVInfoSync to get the results */
		result = PBHGetVInfoSync((HParmBlkPtr)&pb);
		if ( result == noErr )
		{
			VCB				*theVCB;
		
			/* The volume name was returned in volName (if not NULL) and */
			/* we have the volume's vRefNum */
			*vRefNum = pb.ioVRefNum;
			
			/* System 7.5 (and beyond) pins the number of allocation blocks and */
			/* the number of free allocation blocks returned by PBHGetVInfo to */
			/* a value so that when multiplied by the allocation block size, */
			/* the volume will look like it has $7fffffff bytes or less. This */
			/* was done so older applications that use signed math or that use */
			/* the GetVInfo function (which uses signed math) will continue to work. */
			/* However, the unpinned numbers (which we want) are always available */
			/* in the volume's VCB so we'll get those values from the VCB. */
			/* Note: Carbon doesn't support the VCB queue, so this code cannot be */
			/* used (and is conditionalized out) by Carbon applications. */
			
			/* Find the volume's VCB */
			theVCB = (VCB *)(GetVCBQHdr()->qHead);
			while ( theVCB != NULL )
			{
				if ( theVCB->vcbVRefNum == *vRefNum )
				{
					break;
				}
				
				theVCB = (VCB *)(theVCB->qLink);	/* next VCB */
			}
			
			if ( theVCB != NULL )
			{
				/* Found a VCB we can use. Get the un-pinned number of allocation blocks */
				/* and the number of free blocks from the VCB. */
				*freeBytes = U64Multiply(U64SetU((unsigned short)theVCB->vcbFreeBks), U64SetU((unsigned long)pb.ioVAlBlkSiz));
				*totalBytes = U64Multiply(U64SetU((unsigned short)theVCB->vcbNmAlBlks), U64SetU((unsigned long)pb.ioVAlBlkSiz));
			}
			else
			{
				/* Didn't find a VCB we can use. Return the number of allocation blocks */
				/* and the number of free blocks returned by PBHGetVInfoSync. */
				*freeBytes = U64Multiply(U64SetU((unsigned short)pb.ioVFrBlk), U64SetU((unsigned long)pb.ioVAlBlkSiz));
				*totalBytes = U64Multiply(U64SetU((unsigned short)pb.ioVNmAlBlks), U64SetU((unsigned long)pb.ioVAlBlkSiz));
			}
			
		}
	}
	
#endif	//	!TARGET_API_MAC_CARBON
	
	return ( result );
}

/*****************************************************************************/

pascal	OSErr	CheckVolLock(ConstStr255Param pathname,
							 short vRefNum)
{
	HParamBlockRec pb;
	OSErr error;

	error = GetVolumeInfoNoName(pathname,vRefNum, &pb);
	if ( error == noErr )
	{
		if ( (pb.volumeParam.ioVAtrb & kHFSVolumeHardwareLockMask) != 0 )
		{
			error = wPrErr;		/* volume locked by hardware */
		}
		else if ( (pb.volumeParam.ioVAtrb & kHFSVolumeSoftwareLockMask) != 0 )
		{
			error = vLckdErr;	/* volume locked by software */
		}
	}
	
	return ( error );
}

/*****************************************************************************/
//
//	The following routines call Mac OS routines that are not supported by
//	Carbon:
//	
//		GetDriverName
//		FindDrive
//		GetDiskBlocks
//		GetVolState

#if !TARGET_API_MAC_CARBON	//	{

	/*****************************************************************************/

	pascal	OSErr GetDriverName(short driverRefNum,
								Str255 driverName)
	{
		OSErr result;
		DCtlHandle theDctl;
		DRVRHeaderPtr dHeaderPtr;
		
		theDctl = GetDCtlEntry(driverRefNum);
		if ( theDctl != NULL )
		{
		    if ( (**theDctl).dCtlFlags & dRAMBasedMask )
		    {
		    	/* dctlDriver is handle - dereference */
				dHeaderPtr = *((DRVRHeaderHandle)(**theDctl).dCtlDriver);
		    }
		    else
		    {
				/* dctlDriver is pointer */
		      dHeaderPtr = (DRVRHeaderPtr)(**theDctl).dCtlDriver;
		    }
			BlockMoveData((*dHeaderPtr).drvrName, driverName, (*dHeaderPtr).drvrName[0] + 1);
			result = noErr;
		}
		else
		{
			driverName[0] = 0;
			result = badUnitErr;	/* bad reference number */
		}
		
		return ( result );
	}

	/*****************************************************************************/

	pascal	OSErr	FindDrive(ConstStr255Param pathname,
							  short vRefNum,
							  DrvQElPtr *driveQElementPtr)
	{
		OSErr			result;
		HParamBlockRec	hPB;
		short			driveNumber;
		
		*driveQElementPtr = NULL;
		
		/* First, use GetVolumeInfoNoName to determine the volume */
		result = GetVolumeInfoNoName(pathname, vRefNum, &hPB);
		if ( result == noErr )
		{
			/*
			**	The volume can be either online, offline, or ejected. What we find in
			**	ioVDrvInfo and ioVDRefNum will tell us which it is.
			**	See Inside Macintosh: Files page 2-80 and the Technical Note
			**	"FL 34 - VCBs and Drive Numbers : The Real Story"
			**	Where we get the drive number depends on the state of the volume.
			*/
			if ( hPB.volumeParam.ioVDrvInfo != 0 )
			{
				/* The volume is online and not ejected */
				/* Get the drive number */
				driveNumber = hPB.volumeParam.ioVDrvInfo;
			}
			else
			{
				/* The volume's is either offline or ejected */
				/* in either case, the volume is NOT online */

				/* Is it ejected or just offline? */
				if ( hPB.volumeParam.ioVDRefNum > 0 )
				{
					/* It's ejected, the drive number is ioVDRefNum */
					driveNumber = hPB.volumeParam.ioVDRefNum;
				}
				else
				{
					/* It's offline, the drive number is the negative of ioVDRefNum */
					driveNumber = (short)-hPB.volumeParam.ioVDRefNum;
				}
			}
			
			/* Get pointer to first element in drive queue */
			*driveQElementPtr = (DrvQElPtr)(GetDrvQHdr()->qHead);
			
			/* Search for a matching drive number */
			while ( (*driveQElementPtr != NULL) && ((*driveQElementPtr)->dQDrive != driveNumber) )
			{
				*driveQElementPtr = (DrvQElPtr)(*driveQElementPtr)->qLink;
			}
			
			if ( *driveQElementPtr == NULL )
			{
				/* This should never happen since every volume must have a drive, but... */
				result = nsDrvErr;
			}
		}
		
		return ( result );
	}

	/*****************************************************************************/

	pascal	OSErr	GetDiskBlocks(ConstStr255Param pathname,
								  short vRefNum,
								  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
		};
		
		DrvQElPtr		driveQElementPtr;
		unsigned long	blocks;
		ParamBlockRec	pb;
		FormatListRec	formatListRecords[kMaxFormatListRecs];
		DrvSts			status;
		short			formatListRecIndex;
		OSErr			result;

		blocks = 0;
		
		/* Find the drive queue element for this volume */
		result = FindDrive(pathname, vRefNum, &driveQElementPtr);
		
		/* 
		**	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 ( (result == noErr) && (driveQElementPtr->dQRefNum >= 0) )
		{
			result = 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];
			
			result = PBStatusSync(&pb);
			
			if ( result == 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 */
	    			result = paramErr;
	    		}
			}
			else if ( driveQElementPtr->dQRefNum == (short)kSonyRefNum )
			{
				/* The drive is a non-SuperDrive floppy which only supports 400K and 800K disks */
				
				result = DriveStatus(driveQElementPtr->dQDrive, &status);
				if ( result == noErr )
				{
					switch ( status.twoSideFmt )
					{
					case kSingleSided:
						blocks = kSingleSidedSize;
						break;
					case kDoubleSided:
						blocks = kDoubleSidedSize;
						break;
					default:
						/* This should never happen */
						result = paramErr;
						break;
					}
				}
			}
			else
			{
				/* The drive is not a floppy and it doesn't support ReturnFormatList */
				/* so use the dQDrvSz field(s) */
				
				result = noErr;	/* reset result */
				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 */
					result = paramErr;
					break;
				}
			}
		}
		
		if ( result == noErr )
		{
			*numBlocks = blocks;
		}
		
		return ( result );
	}

	/*****************************************************************************/

	pascal	OSErr	GetVolState(ConstStr255Param pathname,
								short vRefNum,
								Boolean *volumeOnline,
								Boolean *volumeEjected,
								Boolean *driveEjectable,
								Boolean *driverWantsEject)
	{
		HParamBlockRec pb;
		short driveNumber;
		OSErr error;

		error = GetVolumeInfoNoName(pathname,vRefNum, &pb);
		if ( error == noErr )
		{
			if ( pb.volumeParam.ioVDrvInfo != 0 )
			{
				/* the volume is online and not ejected */
				*volumeOnline = true;
				*volumeEjected = false;
				
				/* Get the drive number */
				driveNumber = pb.volumeParam.ioVDrvInfo;
			}
			else
			{
				/* the volume's is either offline or ejected */
				/* in either case, the volume is NOT online */
				*volumeOnline = false;

				/* Is it ejected? */
				*volumeEjected = pb.volumeParam.ioVDRefNum > 0;
				
				if ( *volumeEjected )
				{
					/* If ejected, the drive number is ioVDRefNum */
					driveNumber = pb.volumeParam.ioVDRefNum;
				}
				else
				{
					/* If offline, the drive number is the negative of ioVDRefNum */
					driveNumber = (short)-pb.volumeParam.ioVDRefNum;
				}
			}
			
			{
				DrvQElPtr drvQElem;
				
				/* Find the drive queue element by searching the drive queue */
				drvQElem = (DrvQElPtr)(GetDrvQHdr()->qHead);
				while ( (drvQElem != NULL) && (drvQElem->dQDrive != driveNumber) )
				{
					drvQElem = (DrvQElPtr)drvQElem->qLink;
				}
				
				if ( drvQElem != NULL )
				{
					/*
					**	Each drive queue element is preceded by 4 flag bytes.
					**	Byte 1 (the second flag byte) has bits that tell us if a
					**	drive is ejectable and if its driver wants an eject call.
					**	See Inside Macintosh: Files, page 2-85.
					*/
					{
						Ptr		flagBytePtr;
						
						/* point to byte 1 of the flag bytes */
						flagBytePtr = (Ptr)drvQElem;
						flagBytePtr -= 3;
						
						/*
						**	The drive is ejectable if flag byte 1 does not contain
						**	0x08 (nonejectable) or 0x48 (nonejectable, but wants eject call).
						*/
						
						*driveEjectable = (*flagBytePtr != 0x08) && (*flagBytePtr != 0x48);
						
						/*
						**	The driver wants an eject call if flag byte 1 does not contain
						**	0x08 (nonejectable). This may seem like a minor point, but some
						**	disk drivers use the Eject request to flush their caches to disk
						**	and you wouldn't want to skip that step after unmounting a volume.
						*/
						
						*driverWantsEject = (*flagBytePtr != 0x08);
					}
				}
				else
				{
					/* Didn't find the drive (this should never happen) */
					*driveEjectable = false;
					*driverWantsEject = false;
				}
			}
		}
		
		return ( error );
	}

	/*****************************************************************************/

#endif	//	}	!TARGET_API_MAC_CARBON

/*****************************************************************************/

pascal	OSErr	GetVolFileSystemID(ConstStr255Param pathname,
								   short vRefNum,
								   short *fileSystemID)
{
	HParamBlockRec pb;
	OSErr error;

	error = GetVolumeInfoNoName(pathname,vRefNum, &pb);
	if ( error == noErr )
	{
		*fileSystemID = pb.volumeParam.ioVFSID;
	}
	
	return ( error );
}

/*****************************************************************************/

//
//	Note:	Under Carbon there are no drive numbers, so you cannot call
//			Eject with a drive number after unmounting a volume.
//			When a Carbon application calls UnmountVol, CarbonLib will make
//			sure ejectable media is ejected (leaving ejectable media in the
//			disk drive makes no sense to Carbon applications).
//
pascal	OSErr	UnmountAndEject(ConstStr255Param pathname,
								short vRefNum)
{
	HParamBlockRec pb;
	OSErr error;

	error = GetVolumeInfoNoName(pathname, vRefNum, &pb);
	if ( error == noErr )
	{
	
#if	!TARGET_API_MAC_CARBON

		short driveNum;
		Boolean ejected, wantsEject;
		DrvQElPtr drvQElem;
		
		if ( pb.volumeParam.ioVDrvInfo != 0 )
		{
			/* the volume is online and not ejected */
			ejected = false;
			
			/* Get the drive number */
			driveNum = pb.volumeParam.ioVDrvInfo;
		}
		else
		{
			/* the volume is ejected or offline */
			
			/* Is it ejected? */
			ejected = pb.volumeParam.ioVDRefNum > 0;
			
			if ( ejected )
			{
				/* If ejected, the drive number is ioVDRefNum */
				driveNum = pb.volumeParam.ioVDRefNum;
			}
			else
			{
				/* If offline, the drive number is the negative of ioVDRefNum */
				driveNum = (short)-pb.volumeParam.ioVDRefNum;
			}
		}
		
		/* find the drive queue element */
		drvQElem = (DrvQElPtr)(GetDrvQHdr()->qHead);
		while ( (drvQElem != NULL) && (drvQElem->dQDrive != driveNum) )
		{
			drvQElem = (DrvQElPtr)drvQElem->qLink;
		}
		
		if ( drvQElem != NULL )
		{
			/* does the drive want an eject call */
			wantsEject = (*((Ptr)((Ptr)drvQElem - 3)) != 8);
		}
		else
		{
			/* didn't find the drive!! */
			wantsEject = false;
		}
		
#endif	//	!TARGET_API_MAC_CARBON

		/* unmount the volume */
		pb.volumeParam.ioNamePtr = NULL;
		/* ioVRefNum is already filled in from PBHGetVInfo */
		error = PBUnmountVol((ParmBlkPtr)&pb);

#if	!TARGET_API_MAC_CARBON

		if ( error == noErr )
		{
			if ( wantsEject && !ejected )
			{
				/* eject the media from the drive if needed */
				pb.volumeParam.ioVRefNum = driveNum;
				error = PBEject((ParmBlkPtr)&pb);
			}
		}
		
#endif	//	!TARGET_API_MAC_CARBON

	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	OnLine(FSSpecPtr volumes,
					   short reqVolCount,
					   short *actVolCount,
					   short *volIndex)
{
	HParamBlockRec pb;
	OSErr error = noErr;
	FSSpec *endVolArray;

	if ( *volIndex > 0 )
	{
		*actVolCount = 0;
		for ( endVolArray = volumes + reqVolCount; (volumes < endVolArray) && (error == noErr); ++volumes )
		{
			pb.volumeParam.ioNamePtr = (StringPtr) & volumes->name;
			pb.volumeParam.ioVolIndex = *volIndex;
			error = PBHGetVInfoSync(&pb);
			if ( error == noErr )
			{
				volumes->parID = fsRtParID;		/* the root directory's parent is 1 */
				volumes->vRefNum = pb.volumeParam.ioVRefNum;
				++*volIndex;
				++*actVolCount;
			}
		}
	}
	else
	{
		error = paramErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr SetDefault(short newVRefNum,
						 long newDirID,
						 short *oldVRefNum,
						 long *oldDirID)
{
	OSErr	error;
	
	/* Get the current default volume/directory. */
	error = HGetVol(NULL, oldVRefNum, oldDirID);
	if ( error == noErr )
	{
		/* Set the new default volume/directory */
		error = HSetVol(NULL, newVRefNum, newDirID);
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr RestoreDefault(short oldVRefNum,
							 long oldDirID)
{
	OSErr	error;
	
#if	!TARGET_API_MAC_CARBON

	short	defaultVRefNum;
	long	defaultDirID;
	long	defaultProcID;
	
	/* Determine if the default volume was a wdRefNum. */
	error = GetWDInfo(oldVRefNum, &defaultVRefNum, &defaultDirID, &defaultProcID);
	if ( error == noErr )
	{
		/* Restore the old default volume/directory, one way or the other. */
		if ( defaultDirID != fsRtDirID )
		{
			/* oldVRefNum was a wdRefNum - use SetVol */
			error = SetVol(NULL, oldVRefNum);
		}
		else
		{
		
#endif	//	!TARGET_API_MAC_CARBON

			/* oldVRefNum was a real vRefNum - use HSetVol */
			error = HSetVol(NULL, oldVRefNum, oldDirID);

#if	!TARGET_API_MAC_CARBON

		}
	}
#endif	//	!TARGET_API_MAC_CARBON
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr GetDInfo(short vRefNum,
					   long dirID,
					   ConstStr255Param name,
					   DInfo *fndrInfo)
{
	CInfoPBRec pb;
	OSErr error;
	
	error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
	if ( error == noErr )
	{
		if ( (pb.dirInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
		{
			/* it's a directory, return the DInfo */
			*fndrInfo = pb.dirInfo.ioDrUsrWds;
		}
		else
		{
			/* oops, a file was passed */
			error = dirNFErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr FSpGetDInfo(const FSSpec *spec,
						  DInfo *fndrInfo)
{
	return ( GetDInfo(spec->vRefNum, spec->parID, spec->name, fndrInfo) );
}

/*****************************************************************************/

pascal	OSErr SetDInfo(short vRefNum,
					   long dirID,
					   ConstStr255Param name,
					   const DInfo *fndrInfo)
{
	CInfoPBRec pb;
	Str31 tempName;
	OSErr error;

	/* Protection against File Sharing problem */
	if ( (name == NULL) || (name[0] == 0) )
	{
		tempName[0] = 0;
		pb.dirInfo.ioNamePtr = tempName;
		pb.dirInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb.dirInfo.ioNamePtr = (StringPtr)name;
		pb.dirInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	pb.dirInfo.ioVRefNum = vRefNum;
	pb.dirInfo.ioDrDirID = dirID;
	error = PBGetCatInfoSync(&pb);
	if ( error == noErr )
	{
		if ( (pb.dirInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
		{
			/* it's a directory, set the DInfo */
			if ( pb.dirInfo.ioNamePtr == tempName )
			{
				pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID;
			}
			else
			{
				pb.dirInfo.ioDrDirID = dirID;
			}
			pb.dirInfo.ioDrUsrWds = *fndrInfo;
			error = PBSetCatInfoSync(&pb);
		}
		else
		{
			/* oops, a file was passed */
			error = dirNFErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr FSpSetDInfo(const FSSpec *spec,
						  const DInfo *fndrInfo)
{
	return ( SetDInfo(spec->vRefNum, spec->parID, spec->name, fndrInfo) );
}

/*****************************************************************************/

pascal	OSErr	GetDirectoryID(short vRefNum,
							   long dirID,
							   ConstStr255Param name,
							   long *theDirID,
							   Boolean *isDirectory)
{
	CInfoPBRec pb;
	OSErr error;

	error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
	if ( error == noErr )
	{
		*isDirectory = (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0;
		if ( *isDirectory )
		{
			*theDirID = pb.dirInfo.ioDrDirID;
		}
		else
		{
			*theDirID = pb.hFileInfo.ioFlParID;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpGetDirectoryID(const FSSpec *spec,
								  long *theDirID,
								  Boolean *isDirectory)
{
	return ( GetDirectoryID(spec->vRefNum, spec->parID, spec->name,
			 theDirID, isDirectory) );
}

/*****************************************************************************/

pascal	OSErr	GetDirName(short vRefNum,
						   long dirID,
						   Str31 name)
{
	CInfoPBRec pb;
	OSErr error;

	if ( name != NULL )
	{
		pb.dirInfo.ioNamePtr = name;
		pb.dirInfo.ioVRefNum = vRefNum;
		pb.dirInfo.ioDrDirID = dirID;
		pb.dirInfo.ioFDirIndex = -1;	/* get information about ioDirID */
		error = PBGetCatInfoSync(&pb);
	}
	else
	{
		error = paramErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	GetIOACUser(short vRefNum,
							long dirID,
							ConstStr255Param name,
							SInt8 *ioACUser)
{
	CInfoPBRec pb;
	OSErr error;
	
	/* Clear ioACUser before calling PBGetCatInfo since some file systems
	** don't bother to set or clear this field. If ioACUser isn't set by the
	** file system, then you'll get the zero value back (full access) which
	** is the access you have on volumes that don't support ioACUser.
	*/
	pb.dirInfo.ioACUser = 0;	/* ioACUser used to be filler2 */
	error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
	if ( error == noErr )
	{
		if ( (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) == 0 )
		{
			/* oops, a file was passed */
			error = dirNFErr;
		}
		else
		{
			*ioACUser = pb.dirInfo.ioACUser;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpGetIOACUser(const FSSpec *spec,
							   SInt8 *ioACUser)
{
	return ( GetIOACUser(spec->vRefNum, spec->parID, spec->name, ioACUser) );
}

/*****************************************************************************/

pascal	OSErr	GetParentID(short vRefNum,
							long dirID,
							ConstStr255Param name,
							long *parID)
{
	CInfoPBRec pb;
	Str31 tempName;
	OSErr error;
	short realVRefNum;
	
	/* Protection against File Sharing problem */
	if ( (name == NULL) || (name[0] == 0) )
	{
		tempName[0] = 0;
		pb.hFileInfo.ioNamePtr = tempName;
		pb.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb.hFileInfo.ioNamePtr = (StringPtr)name;
		pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	pb.hFileInfo.ioVRefNum = vRefNum;
	pb.hFileInfo.ioDirID = dirID;
	error = PBGetCatInfoSync(&pb);
	if ( error == noErr )
	{
		/*
		**	There's a bug in HFS where the wrong parent dir ID can be
		**	returned if multiple separators are used at the end of a
		**	pathname. For example, if the pathname:
		**		'volumeName:System Folder:Extensions::'
		**	is passed, the directory ID of the Extensions folder is
		**	returned in the ioFlParID field instead of fsRtDirID. Since
		**	multiple separators at the end of a pathname always specifies
		**	a directory, we only need to work-around cases where the
		**	object is a directory and there are multiple separators at
		**	the end of the name parameter.
		*/
		if ( (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
		{
			/* Its a directory */
			
			/* is there a pathname? */
			if ( pb.hFileInfo.ioNamePtr == name )	
			{
				/* could it contain multiple separators? */
				if ( name[0] >= 2 )
				{
					/* does it contain multiple separators at the end? */
					if ( (name[name[0]] == ':') && (name[name[0] - 1] == ':') )
					{
						/* OK, then do the extra stuff to get the correct parID */
						
						/* Get the real vRefNum (this should not fail) */
						error = DetermineVRefNum(name, vRefNum, &realVRefNum);
						if ( error == noErr )
						{
							/* we don't need the parent's name, but add protect against File Sharing problem */
							tempName[0] = 0;
							pb.dirInfo.ioNamePtr = tempName;
							pb.dirInfo.ioVRefNum = realVRefNum;
							/* pb.dirInfo.ioDrDirID already contains the */
							/* dirID of the directory object */
							pb.dirInfo.ioFDirIndex = -1;	/* get information about ioDirID */
							error = PBGetCatInfoSync(&pb);
							/* now, pb.dirInfo.ioDrParID contains the correct parID */
						}
					}
				}
			}
		}
		
		if ( error == noErr )
		{
			/* if no errors, then pb.hFileInfo.ioFlParID (pb.dirInfo.ioDrParID) */
			/* contains the parent ID */
			*parID = pb.hFileInfo.ioFlParID;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	GetFilenameFromPathname(ConstStr255Param pathname,
										Str255 filename)
{
	short	index;
	short	nameEnd;
	OSErr	error;

	/* default to no filename */
	filename[0] = 0;

	/* check for no pathname */
	if ( pathname != NULL )
	{
		/* get string length */
		index = pathname[0];
		
		/* check for empty string */
		if ( index != 0 )
		{
			/* skip over last trailing colon (if any) */
			if ( pathname[index] == ':' )
			{
				--index;
			}

			/* save the end of the string */
			nameEnd = index;

			/* if pathname ends with multiple colons, then this pathname refers */
			/* to a directory, not a file */
			if ( pathname[index] != ':' )
			{
				/* parse backwards until we find a colon or hit the beginning of the pathname */
				while ( (index != 0) && (pathname[index] != ':') )
				{
					--index;
				}
				
				/* if we parsed to the beginning of the pathname and the pathname ended */
				/* with a colon, then pathname is a full pathname to a volume, not a file */
				if ( (index != 0) || (pathname[pathname[0]] != ':') )
				{
					/* get the filename and return noErr */
					filename[0] = (char)(nameEnd - index);
					BlockMoveData(&pathname[index+1], &filename[1], nameEnd - index);
					error = noErr;
				}
				else
				{
					/* pathname to a volume, not a file */
					error = notAFileErr;
				}
			}
			else
			{
				/* directory, not a file */
				error = notAFileErr;
			}
		}
		else
		{
			/* empty string isn't a file */
			error = notAFileErr;
		}
	}
	else
	{
		/* NULL pathname isn't a file */
		error = notAFileErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	GetObjectLocation(short vRefNum,
								  long dirID,
								  ConstStr255Param pathname,
								  short *realVRefNum,
								  long *realParID,
								  Str255 realName,
								  Boolean *isDirectory)
{
	OSErr error;
	CInfoPBRec pb;
	Str255 tempPathname;
	
	/* clear results */
	*realVRefNum = 0;
	*realParID = 0;
	realName[0] = 0;
	
	/*
	**	Get the real vRefNum
	*/
	error = DetermineVRefNum(pathname, vRefNum, realVRefNum);
	if ( error == noErr )
	{
		/*
		**	Determine if the object already exists and if so,
		**	get the real parent directory ID if it's a file
		*/
		
		/* Protection against File Sharing problem */
		if ( (pathname == NULL) || (pathname[0] == 0) )
		{
			tempPathname[0] = 0;
			pb.hFileInfo.ioNamePtr = tempPathname;
			pb.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
		}
		else
		{
			pb.hFileInfo.ioNamePtr = (StringPtr)pathname;
			pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
		}
		pb.hFileInfo.ioVRefNum = vRefNum;
		pb.hFileInfo.ioDirID = dirID;
		error = PBGetCatInfoSync(&pb);
		if ( error == noErr )
		{
			/*
			**	The file system object is present and we have the file's real parID
			*/
			
			/*	Is it a directory or a file? */
			*isDirectory = (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0;
			if ( *isDirectory )
			{
				/*
				**	It's a directory, get its name and parent dirID, and then we're done
				*/
				
				pb.dirInfo.ioNamePtr = realName;
				pb.dirInfo.ioVRefNum = *realVRefNum;
				/* pb.dirInfo.ioDrDirID already contains the dirID of the directory object */
				pb.dirInfo.ioFDirIndex = -1;	/* get information about ioDirID */
				error = PBGetCatInfoSync(&pb);
				
				/* get the parent ID here, because the file system can return the */
				/* wrong parent ID from the last call. */
				*realParID = pb.dirInfo.ioDrParID;
			}
			else
			{
				/*
				**	It's a file - use the parent directory ID from the last call
				**	to GetCatInfoparse, get the file name, and then we're done
				*/
				*realParID = pb.hFileInfo.ioFlParID;	
				error = GetFilenameFromPathname(pathname, realName);
			}
		}
		else if ( error == fnfErr )
		{
			/*
			**	The file system object is not present - see if its parent is present
			*/
			
			/*
			**	Parse to get the object name from end of pathname
			*/
			error = GetFilenameFromPathname(pathname, realName);
			
			/* if we can't get the object name from the end, we can't continue */
			if ( error == noErr )
			{
				/*
				**	What we want now is the pathname minus the object name
				**	for example:
				**	if pathname is 'vol:dir:file' tempPathname becomes 'vol:dir:'
				**	if pathname is 'vol:dir:file:' tempPathname becomes 'vol:dir:'
				**	if pathname is ':dir:file' tempPathname becomes ':dir:'
				**	if pathname is ':dir:file:' tempPathname becomes ':dir:'
				**	if pathname is ':file' tempPathname becomes ':'
				**	if pathname is 'file or file:' tempPathname becomes ''
				*/
				
				/* get a copy of the pathname */
				BlockMoveData(pathname, tempPathname, pathname[0] + 1);
				
				/* remove the object name */
				tempPathname[0] -= realName[0];
				/* and the trailing colon (if any) */
				if ( pathname[pathname[0]] == ':' )
				{
					--tempPathname[0];
				}
				
				/* OK, now get the parent's directory ID */
				
				/* Protection against File Sharing problem */
				pb.hFileInfo.ioNamePtr = (StringPtr)tempPathname;
				if ( tempPathname[0] != 0 )
				{
					pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
				}
				else
				{
					pb.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
				}
				pb.hFileInfo.ioVRefNum = vRefNum;
				pb.hFileInfo.ioDirID = dirID;
				error = PBGetCatInfoSync(&pb);
				*realParID = pb.dirInfo.ioDrDirID;

				*isDirectory = false;	/* we don't know what the object is really going to be */
			}
			
			if ( error != noErr )
			{
				error = dirNFErr;	/* couldn't find parent directory */
			}
			else
			{
				error = fnfErr;	/* we found the parent, but not the file */
			}
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	GetDirItems(short vRefNum,
							long dirID,
							ConstStr255Param name,
							Boolean getFiles,
							Boolean getDirectories,
							FSSpecPtr items,
							short reqItemCount,
							short *actItemCount,
							short *itemIndex) /* start with 1, then use what's returned */
{
	CInfoPBRec pb;
	OSErr error;
	long theDirID;
	Boolean isDirectory;
	FSSpec *endItemsArray;
	
	if ( *itemIndex > 0 )
	{
		/* NOTE: If I could be sure that the caller passed a real vRefNum and real directory */
		/* to this routine, I could rip out calls to DetermineVRefNum and GetDirectoryID and this */
		/* routine would be much faster because of the overhead of DetermineVRefNum and */
		/* GetDirectoryID and because GetDirectoryID blows away the directory index hint the Macintosh */
		/* file system keeps for indexed calls. I can't be sure, so for maximum throughput, */
		/* pass a big array of FSSpecs so you can get the directory's contents with few calls */
		/* to this routine. */
		
		/* get the real volume reference number */
		error = DetermineVRefNum(name, vRefNum, &pb.hFileInfo.ioVRefNum);
		if ( error == noErr )
		{
			/* and the real directory ID of this directory (and make sure it IS a directory) */
			error = GetDirectoryID(vRefNum, dirID, name, &theDirID, &isDirectory);
			if ( error == noErr )
			{
				if ( isDirectory )
				{
					*actItemCount = 0;
					endItemsArray = items + reqItemCount;
					while ( (items < endItemsArray) && (error == noErr) )
					{
						pb.hFileInfo.ioNamePtr = (StringPtr) &items->name;
						pb.hFileInfo.ioDirID = theDirID;
						pb.hFileInfo.ioFDirIndex = *itemIndex;
						error = PBGetCatInfoSync(&pb);
						if ( error == noErr )
						{
							items->parID = pb.hFileInfo.ioFlParID;	/* return item's parID */
							items->vRefNum = pb.hFileInfo.ioVRefNum;	/* return item's vRefNum */
							++*itemIndex;	/* prepare to get next item in directory */
							
							if ( (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
							{
								if ( getDirectories )
								{
									++*actItemCount; /* keep this item */
									++items; /* point to next item */
								}
							}
							else
							{
								if ( getFiles )
								{
									++*actItemCount; /* keep this item */
									++items; /* point to next item */
								}
							}
						}
					}
				}
				else
				{
					/* it wasn't a directory */
					error = dirNFErr;
				}
			}
		}
	}
	else
	{
		/* bad itemIndex */
		error = paramErr;
	}
	
	return ( error );
}

/*****************************************************************************/

static	void	DeleteLevel(long dirToDelete,
							DeleteEnumGlobalsPtr theGlobals)
{
	long savedDir;
	
	do
	{
		/* prepare to delete directory */
		theGlobals->myPB.ciPB.dirInfo.ioNamePtr = (StringPtr)&theGlobals->itemName;
		theGlobals->myPB.ciPB.dirInfo.ioFDirIndex = 1;	/* get first item */
		theGlobals->myPB.ciPB.dirInfo.ioDrDirID = dirToDelete;	/* in this directory */
		theGlobals->error = PBGetCatInfoSync(&(theGlobals->myPB.ciPB));
		if ( theGlobals->error == noErr )
		{
			savedDir = dirToDelete;
			/* We have an item.  Is it a file or directory? */
			if ( (theGlobals->myPB.ciPB.dirInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
			{
				/* it's a directory */
				savedDir = theGlobals->myPB.ciPB.dirInfo.ioDrDirID;	/* save dirID of directory instead */
				DeleteLevel(theGlobals->myPB.ciPB.dirInfo.ioDrDirID, theGlobals);	/* Delete its contents */
				theGlobals->myPB.ciPB.dirInfo.ioNamePtr = NULL;	/* prepare to delete directory */
			}
			if ( theGlobals->error == noErr )
			{
				theGlobals->myPB.ciPB.dirInfo.ioDrDirID = savedDir;	/* restore dirID */
				theGlobals->myPB.hPB.fileParam.ioFVersNum = 0;	/* just in case it's used on an MFS volume... */
				theGlobals->error = PBHDeleteSync(&(theGlobals->myPB.hPB));	/* delete this item */
				if ( theGlobals->error == fLckdErr )
				{
					(void) PBHRstFLockSync(&(theGlobals->myPB.hPB));	/* unlock it */
					theGlobals->error = PBHDeleteSync(&(theGlobals->myPB.hPB));	/* and try again */
				}
			}
		}
	} while ( theGlobals->error == noErr );
	
	if ( theGlobals->error == fnfErr )
	{
		theGlobals->error = noErr;
	}
}

/*****************************************************************************/

pascal	OSErr	DeleteDirectoryContents(short vRefNum,
								 		long dirID,
										ConstStr255Param name)
{
	DeleteEnumGlobals theGlobals;
	Boolean	isDirectory;
	OSErr error;

	/*  Get the real dirID and make sure it is a directory. */
	error = GetDirectoryID(vRefNum, dirID, name, &dirID, &isDirectory);
	if ( error == noErr )
	{
		if ( isDirectory )
		{
			/* Get the real vRefNum */
			error = DetermineVRefNum(name, vRefNum, &vRefNum);
			if ( error == noErr )
			{
				/* Set up the globals we need to access from the recursive routine. */
				theGlobals.myPB.ciPB.dirInfo.ioVRefNum = vRefNum;
					
				/* Here we go into recursion land... */
				DeleteLevel(dirID, &theGlobals);
				error = theGlobals.error;
			}
		}
		else
		{
			error = dirNFErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DeleteDirectory(short vRefNum,
								long dirID,
								ConstStr255Param name)
{
	OSErr error;
	
	/* Make sure a directory was specified and then delete its contents */
	error = DeleteDirectoryContents(vRefNum, dirID, name);
	if ( error == noErr )
	{
		error = HDelete(vRefNum, dirID, name);
		if ( error == fLckdErr )
		{
			(void) HRstFLock(vRefNum, dirID, name);	/* unlock the directory locked by AppleShare */
			error = HDelete(vRefNum, dirID, name);	/* and try again */
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	CheckObjectLock(short vRefNum,
								long dirID,
								ConstStr255Param name)
{
	CInfoPBRec pb;
	OSErr error;
	
	error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
	if ( error == noErr )
	{
		/* check locked bit */
		if ( (pb.hFileInfo.ioFlAttrib & kioFlAttribLockedMask) != 0 )
		{
			error = fLckdErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpCheckObjectLock(const FSSpec *spec)
{
	return ( CheckObjectLock(spec->vRefNum, spec->parID, spec->name) );
}

/*****************************************************************************/

pascal	OSErr	GetFileSize(short vRefNum,
							long dirID,
							ConstStr255Param fileName,
							long *dataSize,
							long *rsrcSize)
{
	HParamBlockRec pb;
	OSErr error;
	
	pb.fileParam.ioNamePtr = (StringPtr)fileName;
	pb.fileParam.ioVRefNum = vRefNum;
	pb.fileParam.ioFVersNum = 0;
	pb.fileParam.ioDirID = dirID;
	pb.fileParam.ioFDirIndex = 0;
	error = PBHGetFInfoSync(&pb);
	if ( error == noErr )
	{
		*dataSize = pb.fileParam.ioFlLgLen;
		*rsrcSize = pb.fileParam.ioFlRLgLen;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpGetFileSize(const FSSpec *spec,
							   long *dataSize,
							   long *rsrcSize)
{
	return ( GetFileSize(spec->vRefNum, spec->parID, spec->name, dataSize, rsrcSize) );
}

/*****************************************************************************/

pascal	OSErr	BumpDate(short vRefNum,
						 long dirID,
						 ConstStr255Param name)
/* Given a file or directory, change its modification date to the current date/time. */
{
	CInfoPBRec pb;
	Str31 tempName;
	OSErr error;
	unsigned long secs;

	/* Protection against File Sharing problem */
	if ( (name == NULL) || (name[0] == 0) )
	{
		tempName[0] = 0;
		pb.hFileInfo.ioNamePtr = tempName;
		pb.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb.hFileInfo.ioNamePtr = (StringPtr)name;
		pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	pb.hFileInfo.ioVRefNum = vRefNum;
	pb.hFileInfo.ioDirID = dirID;
	error = PBGetCatInfoSync(&pb);
	if ( error == noErr )
	{
		GetDateTime(&secs);
		/* set mod date to current date, or one second into the future
			if mod date = current date */
		pb.hFileInfo.ioFlMdDat = (secs == pb.hFileInfo.ioFlMdDat) ? (++secs) : (secs);
		if ( pb.dirInfo.ioNamePtr == tempName )
		{
			pb.hFileInfo.ioDirID = pb.hFileInfo.ioFlParID;
		}
		else
		{
			pb.hFileInfo.ioDirID = dirID;
		}
		error = PBSetCatInfoSync(&pb);
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpBumpDate(const FSSpec *spec)
{
	return ( BumpDate(spec->vRefNum, spec->parID, spec->name) );
}

/*****************************************************************************/

pascal	OSErr	ChangeCreatorType(short vRefNum,
								  long dirID,
								  ConstStr255Param name,
								  OSType creator,
								  OSType fileType)
{
	CInfoPBRec pb;
	OSErr error;
	short realVRefNum;
	long parID;

	pb.hFileInfo.ioNamePtr = (StringPtr)name;
	pb.hFileInfo.ioVRefNum = vRefNum;
	pb.hFileInfo.ioDirID = dirID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	error = PBGetCatInfoSync(&pb);
	if ( error == noErr )
	{
		if ( (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) == 0 )	/* if file */
		{
			parID = pb.hFileInfo.ioFlParID;	/* save parent dirID for BumpDate call */

			/* If creator not 0x00000000, change creator */
			if ( creator != (OSType)0x00000000 )
			{
				pb.hFileInfo.ioFlFndrInfo.fdCreator = creator;
			}
			
			/* If fileType not 0x00000000, change fileType */
			if ( fileType != (OSType)0x00000000 )
			{
				pb.hFileInfo.ioFlFndrInfo.fdType = fileType;
			}
				
			pb.hFileInfo.ioDirID = dirID;
			error = PBSetCatInfoSync(&pb);	/* now, save the new information back to disk */

			if ( (error == noErr) && (parID != fsRtParID) ) /* can't bump fsRtParID */
			{
				/* get the real vRefNum in case a full pathname was passed */
				error = DetermineVRefNum(name, vRefNum, &realVRefNum);
				if ( error == noErr )
				{
					error = BumpDate(realVRefNum, parID, NULL);
						/* and bump the parent directory's mod date to wake up the Finder */
						/* to the change we just made */
				}
			}
		}
		else
		{
			/* it was a directory, not a file */
			error = notAFileErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpChangeCreatorType(const FSSpec *spec,
									 OSType creator,
									 OSType fileType)
{
	return ( ChangeCreatorType(spec->vRefNum, spec->parID, spec->name, creator, fileType) );
}

/*****************************************************************************/

pascal	OSErr	ChangeFDFlags(short vRefNum,
							  long dirID,
							  ConstStr255Param name,
							  Boolean	setBits,
							  unsigned short flagBits)
{
	CInfoPBRec pb;
	Str31 tempName;
	OSErr error;
	short realVRefNum;
	long parID;

	/* Protection against File Sharing problem */
	if ( (name == NULL) || (name[0] == 0) )
	{
		tempName[0] = 0;
		pb.hFileInfo.ioNamePtr = tempName;
		pb.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb.hFileInfo.ioNamePtr = (StringPtr)name;
		pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	pb.hFileInfo.ioVRefNum = vRefNum;
	pb.hFileInfo.ioDirID = dirID;
	error = PBGetCatInfoSync(&pb);
	if ( error == noErr )
	{
		parID = pb.hFileInfo.ioFlParID;	/* save parent dirID for BumpDate call */
		
		/* set or clear the appropriate bits in the Finder flags */
		if ( setBits )
		{
			/* OR in the bits */
			pb.hFileInfo.ioFlFndrInfo.fdFlags |= flagBits;
		}
		else
		{
			/* AND out the bits */
			pb.hFileInfo.ioFlFndrInfo.fdFlags &= ~flagBits;
		}
			
		if ( pb.dirInfo.ioNamePtr == tempName )
		{
			pb.hFileInfo.ioDirID = pb.hFileInfo.ioFlParID;
		}
		else
		{
			pb.hFileInfo.ioDirID = dirID;
		}
		
		error = PBSetCatInfoSync(&pb);	/* now, save the new information back to disk */

		if ( (error == noErr) && (parID != fsRtParID) ) /* can't bump fsRtParID */
		{
			/* get the real vRefNum in case a full pathname was passed */
			error = DetermineVRefNum(name, vRefNum, &realVRefNum);
			if ( error == noErr )
			{
				error = BumpDate(realVRefNum, parID, NULL);
					/* and bump the parent directory's mod date to wake up the Finder */
					/* to the change we just made */
			}
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpChangeFDFlags(const FSSpec *spec,
								 Boolean setBits,
								 unsigned short flagBits)
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, setBits, flagBits) );
}

/*****************************************************************************/

pascal	OSErr	SetIsInvisible(short vRefNum,
							   long dirID,
							   ConstStr255Param name)
	/* Given a file or directory, make it invisible. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, true, kIsInvisible) );
}

/*****************************************************************************/

pascal	OSErr	FSpSetIsInvisible(const FSSpec *spec)
	/* Given a file or directory, make it invisible. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, true, kIsInvisible) );
}

/*****************************************************************************/

pascal	OSErr	ClearIsInvisible(short vRefNum,
								 long dirID,
								 ConstStr255Param name)
	/* Given a file or directory, make it visible. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, false, kIsInvisible) );
}

/*****************************************************************************/

pascal	OSErr	FSpClearIsInvisible(const FSSpec *spec)
	/* Given a file or directory, make it visible. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, false, kIsInvisible) );
}

/*****************************************************************************/

pascal	OSErr	SetNameLocked(short vRefNum,
							  long dirID,
							  ConstStr255Param name)
	/* Given a file or directory, lock its name. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, true, kNameLocked) );
}

/*****************************************************************************/

pascal	OSErr	FSpSetNameLocked(const FSSpec *spec)
	/* Given a file or directory, lock its name. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, true, kNameLocked) );
}

/*****************************************************************************/

pascal	OSErr	ClearNameLocked(short vRefNum,
								long dirID,
								ConstStr255Param name)
	/* Given a file or directory, unlock its name. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, false, kNameLocked) );
}

/*****************************************************************************/

pascal	OSErr	FSpClearNameLocked(const FSSpec *spec)
	/* Given a file or directory, unlock its name. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, false, kNameLocked) );
}

/*****************************************************************************/

pascal	OSErr	SetIsStationery(short vRefNum,
								long dirID,
								ConstStr255Param name)
	/* Given a file, make it a stationery pad. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, true, kIsStationery) );
}

/*****************************************************************************/

pascal	OSErr	FSpSetIsStationery(const FSSpec *spec)
	/* Given a file, make it a stationery pad. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, true, kIsStationery) );
}

/*****************************************************************************/

pascal	OSErr	ClearIsStationery(short vRefNum,
								  long dirID,
								  ConstStr255Param name)
	/* Given a file, clear the stationery bit. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, false, kIsStationery) );
}

/*****************************************************************************/

pascal	OSErr	FSpClearIsStationery(const FSSpec *spec)
	/* Given a file, clear the stationery bit. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, false, kIsStationery) );
}

/*****************************************************************************/

pascal	OSErr	SetHasCustomIcon(short vRefNum,
								 long dirID,
								 ConstStr255Param name)
	/* Given a file or directory, indicate that it has a custom icon. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, true, kHasCustomIcon) );
}

/*****************************************************************************/

pascal	OSErr	FSpSetHasCustomIcon(const FSSpec *spec)
	/* Given a file or directory, indicate that it has a custom icon. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, true, kHasCustomIcon) );
}

/*****************************************************************************/

pascal	OSErr	ClearHasCustomIcon(short vRefNum,
								   long dirID,
								   ConstStr255Param name)
	/* Given a file or directory, indicate that it does not have a custom icon. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, false, kHasCustomIcon) );
}

/*****************************************************************************/

pascal	OSErr	FSpClearHasCustomIcon(const FSSpec *spec)
	/* Given a file or directory, indicate that it does not have a custom icon. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, false, kHasCustomIcon) );
}

/*****************************************************************************/

pascal	OSErr	ClearHasBeenInited(short vRefNum,
								   long dirID,
								   ConstStr255Param name)
	/* Given a file, clear its "has been inited" bit. */
{
	return ( ChangeFDFlags(vRefNum, dirID, name, false, kHasBeenInited) );
}

/*****************************************************************************/

pascal	OSErr	FSpClearHasBeenInited(const FSSpec *spec)
	/* Given a file, clear its "has been inited" bit. */
{
	return ( ChangeFDFlags(spec->vRefNum, spec->parID, spec->name, false, kHasBeenInited) );
}

/*****************************************************************************/

pascal	OSErr	CopyFileMgrAttributes(short srcVRefNum,
									  long srcDirID,
									  ConstStr255Param srcName,
									  short dstVRefNum,
									  long dstDirID,
									  ConstStr255Param dstName,
									  Boolean copyLockBit)
{
	UniversalFMPB pb;
	Str31 tempName;
	OSErr error;
	Boolean objectIsDirectory;

	pb.ciPB.hFileInfo.ioVRefNum = srcVRefNum;
	pb.ciPB.hFileInfo.ioDirID = srcDirID;

	/* Protection against File Sharing problem */
	if ( (srcName == NULL) || (srcName[0] == 0) )
	{
		tempName[0] = 0;
		pb.ciPB.hFileInfo.ioNamePtr = tempName;
		pb.ciPB.hFileInfo.ioFDirIndex = -1;	/* use ioDirID */
	}
	else
	{
		pb.ciPB.hFileInfo.ioNamePtr = (StringPtr)srcName;
		pb.ciPB.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	}
	error = PBGetCatInfoSync(&pb.ciPB);
	if ( error == noErr )
	{
		objectIsDirectory = ( (pb.ciPB.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 );
		pb.ciPB.hFileInfo.ioVRefNum = dstVRefNum;
		pb.ciPB.hFileInfo.ioDirID = dstDirID;
		if ( (dstName != NULL) && (dstName[0] == 0) )
		{
			pb.ciPB.hFileInfo.ioNamePtr = NULL;
		}
		else
		{
			pb.ciPB.hFileInfo.ioNamePtr = (StringPtr)dstName;
		}
		/* don't copy the hasBeenInited bit */
		pb.ciPB.hFileInfo.ioFlFndrInfo.fdFlags = ( pb.ciPB.hFileInfo.ioFlFndrInfo.fdFlags & ~kHasBeenInited );
		error = PBSetCatInfoSync(&pb.ciPB);
		if ( (error == noErr) && (copyLockBit) && ((pb.ciPB.hFileInfo.ioFlAttrib & kioFlAttribLockedMask) != 0) )
		{
			pb.hPB.fileParam.ioFVersNum = 0;
			error = PBHSetFLockSync(&pb.hPB);
			if ( (error != noErr) && (objectIsDirectory) )
			{
				error = noErr; /* ignore lock errors if destination is directory */
			}
		}
	}
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpCopyFileMgrAttributes(const FSSpec *srcSpec,
										 const FSSpec *dstSpec,
										 Boolean copyLockBit)
{
	return ( CopyFileMgrAttributes(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
								   dstSpec->vRefNum, dstSpec->parID, dstSpec->name,
								   copyLockBit) );
}

/*****************************************************************************/

pascal	OSErr	HOpenAware(short vRefNum,
						   long dirID,
						   ConstStr255Param fileName,
						   short denyModes,
						   short *refNum)
{
	HParamBlockRec pb;
	OSErr error;
	GetVolParmsInfoBuffer volParmsInfo;
	long infoSize = sizeof(GetVolParmsInfoBuffer);

	pb.ioParam.ioMisc = NULL;
	pb.fileParam.ioFVersNum = 0;
	pb.fileParam.ioNamePtr = (StringPtr)fileName;
	pb.fileParam.ioVRefNum = vRefNum;
	pb.fileParam.ioDirID = dirID;

	/* get volume attributes */
	/* this preflighting is needed because Foreign File Access based file systems don't */
	/* return the correct error result to the OpenDeny call */
	error = HGetVolParms(fileName, vRefNum, &volParmsInfo, &infoSize);
	if ( (error == noErr) && hasOpenDeny(volParmsInfo) )
	{
		/* if volume supports OpenDeny, use it and return */
			pb.accessParam.ioDenyModes = denyModes;
			error = PBHOpenDenySync(&pb);
			*refNum = pb.ioParam.ioRefNum;
	}
	else if ( (error == noErr) || (error == paramErr) )	/* paramErr is OK, it just means this volume doesn't support GetVolParms */
	{
		/* OpenDeny isn't supported, so try File Manager Open functions */
		
		/* If request includes write permission, then see if the volume is */
		/* locked by hardware or software. The HFS file system doesn't check */
		/* for this when a file is opened - you only find out later when you */
		/* try to write and the write fails with a wPrErr or a vLckdErr. */
		
		if ( (denyModes & dmWr) != 0 )
		{
			error = CheckVolLock(fileName, vRefNum);
		}
		else
		{
			error = noErr;
		}
		
		if ( error == noErr )
		{
			/* Set File Manager permissions to closest thing possible */
			if ( (denyModes == dmWr) || (denyModes == dmRdWr) )
			{
				pb.ioParam.ioPermssn = fsRdWrShPerm;
			}
			else
			{
				pb.ioParam.ioPermssn = denyModes % 4;
			}

			error = PBHOpenDFSync(&pb);				/* Try OpenDF */
			if ( error == paramErr )
			{
				error = PBHOpenSync(&pb);			/* OpenDF not supported, so try Open */
			}
			*refNum = pb.ioParam.ioRefNum;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpOpenAware(const FSSpec *spec,
							 short denyModes,
							 short *refNum)
{
	return ( HOpenAware(spec->vRefNum, spec->parID, spec->name, denyModes, refNum) );
}

/*****************************************************************************/

pascal	OSErr	HOpenRFAware(short vRefNum,
							 long dirID,
							 ConstStr255Param fileName,
							 short denyModes,
							 short *refNum)
{
	HParamBlockRec pb;
	OSErr error;
	GetVolParmsInfoBuffer volParmsInfo;
	long infoSize = sizeof(GetVolParmsInfoBuffer);

	pb.ioParam.ioMisc = NULL;
	pb.fileParam.ioFVersNum = 0;
	pb.fileParam.ioNamePtr = (StringPtr)fileName;
	pb.fileParam.ioVRefNum = vRefNum;
	pb.fileParam.ioDirID = dirID;

	/* get volume attributes */
	/* this preflighting is needed because Foreign File Access based file systems don't */
	/* return the correct error result to the OpenRFDeny call */
	error = HGetVolParms(fileName, vRefNum, &volParmsInfo, &infoSize);
	if ( (error == noErr) && hasOpenDeny(volParmsInfo) )
	{
		/* if volume supports OpenRFDeny, use it and return */
		if ( hasOpenDeny(volParmsInfo) )
		{
			pb.accessParam.ioDenyModes = denyModes;
			error = PBHOpenRFDenySync(&pb);
			*refNum = pb.ioParam.ioRefNum;
		}
	}
	else if ( (error == noErr) || (error == paramErr) )	/* paramErr is OK, it just means this volume doesn't support GetVolParms */
	{
		/* OpenRFDeny isn't supported, so try File Manager OpenRF function */
		
		/* If request includes write permission, then see if the volume is */
		/* locked by hardware or software. The HFS file system doesn't check */
		/* for this when a file is opened - you only find out later when you */
		/* try to write and the write fails with a wPrErr or a vLckdErr. */
		
		if ( (denyModes & dmWr) != 0 )
		{
			error = CheckVolLock(fileName, vRefNum);
		}
		else
		{
			error = noErr;
		}
		
		if ( error == noErr )
		{
			/* Set File Manager permissions to closest thing possible */
			if ( (denyModes == dmWr) || (denyModes == dmRdWr) )
			{
				pb.ioParam.ioPermssn = fsRdWrShPerm;
			}
			else
			{
				pb.ioParam.ioPermssn = denyModes % 4;
			}

			error = PBHOpenRFSync(&pb);
			*refNum = pb.ioParam.ioRefNum;
		}
	}

	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpOpenRFAware(const FSSpec *spec,
							   short denyModes,
							   short *refNum)
{
	return ( HOpenRFAware(spec->vRefNum, spec->parID, spec->name, denyModes, refNum) );
}

/*****************************************************************************/

pascal	OSErr	FSReadNoCache(short refNum,
							  long *count,
							  void *buffPtr)
{
	ParamBlockRec pb;
	OSErr error;

	pb.ioParam.ioRefNum = refNum;
	pb.ioParam.ioBuffer = (Ptr)buffPtr;
	pb.ioParam.ioReqCount = *count;
	pb.ioParam.ioPosMode = fsAtMark + noCacheMask;	/* fsAtMark + noCacheMask */
	pb.ioParam.ioPosOffset = 0;
	error = PBReadSync(&pb);
	*count = pb.ioParam.ioActCount;				/* always return count */
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSWriteNoCache(short refNum,
							   long *count,
							   const void *buffPtr)
{
	ParamBlockRec pb;
	OSErr error;

	pb.ioParam.ioRefNum = refNum;
	pb.ioParam.ioBuffer = (Ptr)buffPtr;
	pb.ioParam.ioReqCount = *count;
	pb.ioParam.ioPosMode = fsAtMark + noCacheMask;	/* fsAtMark + noCacheMask */
	pb.ioParam.ioPosOffset = 0;
	error = PBWriteSync(&pb);
	*count = pb.ioParam.ioActCount;				/* always return count */
	return ( error );
}

/*****************************************************************************/

/*
**	See if numBytes bytes of buffer1 are equal to buffer2.
*/
static	Boolean EqualMemory(const void *buffer1, const void *buffer2, unsigned long numBytes)
{
	register unsigned char *b1 = (unsigned char *)buffer1;
	register unsigned char *b2 = (unsigned char *)buffer2;

	if ( b1 != b2 )	/* if buffer pointers are same, then they are equal */
	{
		while ( numBytes > 0 )
		{
			/* compare the bytes and then increment the pointers */
			if ( (*b1++ - *b2++) != 0 )
			{
				return ( false );
			}
			--numBytes;
		}
	}
	
	return ( true );
}

/*****************************************************************************/

/*
**	Read any number of bytes from an open file using read-verify mode.
**	The FSReadVerify function reads any number of bytes from an open file
**	and verifies them against the data in the buffer pointed to by buffPtr.
**	
**	Because of a bug in the HFS file system, only non-block aligned parts of
**	the read are verified against the buffer data and the rest is *copied*
**	into the buffer.  Thus, you shouldn't verify against your original data;
**	instead, you should verify against a copy of the original data and then
**	compare the read-verified copy against the original data after calling
**	FSReadVerify. That's why this function isn't exported - it needs the
**	wrapper provided by FSWriteVerify.
*/
static	OSErr	FSReadVerify(short refNum,
							 long *count,
							 void *buffPtr)
{
	ParamBlockRec	pb;
	OSErr			result;

	pb.ioParam.ioRefNum = refNum;
	pb.ioParam.ioBuffer = (Ptr)buffPtr;
	pb.ioParam.ioReqCount = *count;
	pb.ioParam.ioPosMode = fsAtMark + rdVerify;
	pb.ioParam.ioPosOffset = 0;
	result = PBReadSync(&pb);
	*count = pb.ioParam.ioActCount;			/* always return count */
	return ( result );
}

/*****************************************************************************/

pascal	OSErr	FSWriteVerify(short refNum,
							  long *count,
							  const void *buffPtr)
{
	Ptr		verifyBuffer;
	long	position;
	long	bufferSize;
	long	byteCount;
	long	bytesVerified;
	Ptr		startVerify;
	OSErr	result;
	
	/*
	**	Allocate the verify buffer
	**	Try to get get a large enough buffer to verify in one pass.
	**	If that fails, use GetTempBuffer to get a buffer.
	*/
	bufferSize = *count;
	verifyBuffer = NewPtr(bufferSize);
	if ( verifyBuffer == NULL )
	{
		verifyBuffer = GetTempBuffer(bufferSize, &bufferSize);
	}
	if ( verifyBuffer != NULL )
	{		
		/* Save the current position */
		result = GetFPos(refNum, &position);
		if ( result == noErr )
		{
			/* Write the data */
			result = FSWrite(refNum, count, buffPtr);
			if ( result == noErr )
			{
				/* Restore the original position */
				result = SetFPos(refNum, fsFromStart, position);
				if ( result == noErr )
				{
					/*
					**	*count			= total number of bytes to verify
					**	bufferSize		= the size of the verify buffer
					**	bytesVerified	= number of bytes verified
					**	byteCount		= number of bytes to verify this pass
					**	startVerify		= position in buffPtr
					*/
					bytesVerified = 0;
					startVerify = (Ptr)buffPtr;
					while ( (bytesVerified < *count) && ( result == noErr ) )
					{
						if ( (*count - bytesVerified) > bufferSize )
						{
							byteCount = bufferSize;
						}
						else
						{
							byteCount = *count - bytesVerified;
						}
						/*
						**	Copy the write buffer into the verify buffer.
						**	This step is needed because the File Manager
						**	compares the data in any non-block aligned
						**	data at the beginning and end of the read-verify
						**	request back into the file system's cache
						**	to the data in verify Buffer. However, the
						**	File Manager does not compare any full blocks
						**	and instead copies them into the verify buffer
						**	so we still have to compare the buffers again
						**	after the read-verify request completes.
						*/
						BlockMoveData(startVerify, verifyBuffer, byteCount);
						
						/* Read-verify the data back into the verify buffer */
						result = FSReadVerify(refNum, &byteCount, verifyBuffer);
						if ( result == noErr )
						{
							/* See if the buffers are the same */
							if ( !EqualMemory(verifyBuffer, startVerify, byteCount) )
							{
								result = ioErr;
							}
							startVerify += byteCount;
							bytesVerified += byteCount;
						}
					}
				}
			}
		}
		DisposePtr(verifyBuffer);
	}
	else
	{
		result = memFullErr;
	}
	return ( result );
}

/*****************************************************************************/

pascal	OSErr	CopyFork(short srcRefNum,
						 short dstRefNum,
						 void *copyBufferPtr,
						 long copyBufferSize)
{
	ParamBlockRec srcPB;
	ParamBlockRec dstPB;
	OSErr srcError;
	OSErr dstError;

	if ( (copyBufferPtr == NULL) || (copyBufferSize == 0) )
		return ( paramErr );
	
	srcPB.ioParam.ioRefNum = srcRefNum;
	dstPB.ioParam.ioRefNum = dstRefNum;

	/* preallocate the destination fork and */
	/* ensure the destination fork's EOF is correct after the copy */
	srcError = PBGetEOFSync(&srcPB);
	if ( srcError != noErr )
		return ( srcError );
	dstPB.ioParam.ioMisc = srcPB.ioParam.ioMisc;
	dstError = PBSetEOFSync(&dstPB);
	if ( dstError != noErr )
		return ( dstError );

	/* reset source fork's mark */
	srcPB.ioParam.ioPosMode = fsFromStart;
	srcPB.ioParam.ioPosOffset = 0;
	srcError = PBSetFPosSync(&srcPB);
	if ( srcError != noErr )
		return ( srcError );

	/* reset destination fork's mark */
	dstPB.ioParam.ioPosMode = fsFromStart;
	dstPB.ioParam.ioPosOffset = 0;
	dstError = PBSetFPosSync(&dstPB);
	if ( dstError != noErr )
		return ( dstError );

	/* set up fields that won't change in the loop */
	srcPB.ioParam.ioBuffer = (Ptr)copyBufferPtr;
	srcPB.ioParam.ioPosMode = fsAtMark + noCacheMask;/* fsAtMark + noCacheMask */
	/* If copyBufferSize is greater than 512 bytes, make it a multiple of 512 bytes */
	/* This will make writes on local volumes faster */
	if ( (copyBufferSize >= 512) && ((copyBufferSize & 0x1ff) != 0) )
	{
		srcPB.ioParam.ioReqCount = copyBufferSize & 0xfffffe00;
	}
	else
	{
		srcPB.ioParam.ioReqCount = copyBufferSize;
	}
	dstPB.ioParam.ioBuffer = (Ptr)copyBufferPtr;
	dstPB.ioParam.ioPosMode = fsAtMark + noCacheMask;/* fsAtMark + noCacheMask */

	while ( (srcError == noErr) && (dstError == noErr) )
	{
		srcError = PBReadSync(&srcPB);
		dstPB.ioParam.ioReqCount = srcPB.ioParam.ioActCount;
		dstError = PBWriteSync(&dstPB);
	}

	/* make sure there were no errors at the destination */
	if ( dstError != noErr )
		return ( dstError );

	/* make sure the only error at the source was eofErr */
	if ( srcError != eofErr )
		return ( srcError );

	return ( noErr );
}

/*****************************************************************************/

pascal	OSErr	GetFileLocation(short refNum,
								short *vRefNum,
								long *dirID,
								StringPtr fileName)
{
	FCBPBRec pb;
	OSErr error;

	pb.ioNamePtr = fileName;
	pb.ioVRefNum = 0;
	pb.ioRefNum = refNum;
	pb.ioFCBIndx = 0;
	error = PBGetFCBInfoSync(&pb);
	if ( error == noErr )
	{
		*vRefNum = pb.ioFCBVRefNum;
		*dirID = pb.ioFCBParID;
	}
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpGetFileLocation(short refNum,
								   FSSpec *spec)
{
	return ( GetFileLocation(refNum, &(spec->vRefNum), &(spec->parID), spec->name) );
}

/*****************************************************************************/

pascal	OSErr	CopyDirectoryAccess(short srcVRefNum,
									long srcDirID,
									ConstStr255Param srcName,
									short dstVRefNum,
									long dstDirID,
									ConstStr255Param dstName)
{	
	OSErr error;
	GetVolParmsInfoBuffer infoBuffer;	/* Where PBGetVolParms dumps its info */
	long	dstServerAdr;				/* AppleTalk server address of destination (if any) */
	long	ownerID, groupID, accessRights;
	long	tempLong;

	/* See if destination supports directory access control */
	tempLong = sizeof(infoBuffer);
	error = HGetVolParms(dstName, dstVRefNum, &infoBuffer, &tempLong);
	if ( (error == noErr) && hasAccessCntl(infoBuffer) )
	{
		if ( hasAccessCntl(infoBuffer) )
		{
			dstServerAdr = infoBuffer.vMServerAdr;
			
			/* See if source supports directory access control and is on same server */
			tempLong = sizeof(infoBuffer);
			error = HGetVolParms(srcName, srcVRefNum, &infoBuffer, &tempLong);
			if ( error == noErr )
			{
				if ( hasAccessCntl(infoBuffer) && (dstServerAdr == infoBuffer.vMServerAdr) )
				{
					/* both volumes support directory access control and they are */
					/*  on same server, so copy the access information */
					error = HGetDirAccess(srcVRefNum, srcDirID, srcName, &ownerID, &groupID, &accessRights);
					if ( error == noErr )
					{
						error = HSetDirAccess(dstVRefNum, dstDirID, dstName, ownerID, groupID, accessRights);
					}
				}
				else
				{
					/* destination doesn't support directory access control or */
					/* they volumes aren't on the same server */
					error = paramErr;
				}
			}
		}
		else
		{
			/* destination doesn't support directory access control */
			error = paramErr;
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpCopyDirectoryAccess(const FSSpec *srcSpec,
									   const FSSpec *dstSpec)
{
	return ( CopyDirectoryAccess(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
								dstSpec->vRefNum, dstSpec->parID, dstSpec->name) );
}

/*****************************************************************************/

pascal	OSErr	HMoveRenameCompat(short vRefNum,
								  long srcDirID,
								  ConstStr255Param srcName,
								  long dstDirID,
								  ConstStr255Param dstpathName,
								  ConstStr255Param copyName)
{
	OSErr					error;
	GetVolParmsInfoBuffer	volParmsInfo;
	long					infoSize;
	short					realVRefNum;
	long					realParID;
	Str31					realName;
	Boolean					isDirectory;
	long					tempItemsDirID;
	long					uniqueTempDirID;
	Str31					uniqueTempDirName;
	unsigned short			uniqueNameoverflow;
	
	/* Get volume attributes */
	infoSize = sizeof(GetVolParmsInfoBuffer);
	error = HGetVolParms((StringPtr)srcName, vRefNum, &volParmsInfo, &infoSize);
	if ( (error == noErr) && hasMoveRename(volParmsInfo) )
	{
		/* If volume supports move and rename, so use it and return */
		error = HMoveRename(vRefNum, srcDirID, srcName, dstDirID, dstpathName, copyName);
	}
	else if ( (error == noErr) || (error == paramErr) ) /* paramErr is OK, it just means this volume doesn't support GetVolParms */
	{
		/* MoveRename isn't supported by this volume, so do it by hand */
		
		/* If copyName isn't supplied, we can simply CatMove and return */
		if ( copyName == NULL )
		{
			error = CatMove(vRefNum, srcDirID, srcName, dstDirID, dstpathName);
		}
		else
		{
			/* Renaming is required, so we have some work to do... */
			
			/* Get the object's real name, real parent ID and real vRefNum */
			error = GetObjectLocation(vRefNum, srcDirID, (StringPtr)srcName,
										&realVRefNum, &realParID, realName, &isDirectory);
			if ( error == noErr )
			{
				/* Find the Temporary Items Folder on that volume */
				error = FindFolder(realVRefNum, kTemporaryFolderType, kCreateFolder,
									&realVRefNum, &tempItemsDirID);
				if ( error == noErr )
				{
					/* Create a new uniquely named folder in the temporary items folder. */
					/* This is done to avoid the case where 'realName' or 'copyName' already */
					/* exists in the temporary items folder. */
					
					/* Start with current tick count as uniqueTempDirName */					
					NumToString(TickCount(), uniqueTempDirName);
					uniqueNameoverflow = 0;
					do
					{
						error = DirCreate(realVRefNum, tempItemsDirID, uniqueTempDirName, &uniqueTempDirID);
						if ( error == dupFNErr )
						{
							/* Duplicate name - change the first character to the next ASCII character */
							++uniqueTempDirName[1];
							/* Make sure it isn't a colon! */
							if ( uniqueTempDirName[1] == ':' )
							{
								++uniqueTempDirName[1];
							}
							/* Don't go too far... */
							++uniqueNameoverflow;
						}
					} while ( (error == dupFNErr) && (uniqueNameoverflow <= 64) ); /* 64 new files per 1/60th second - not likely! */
					if ( error == noErr )
					{
						/* Move the object to the folder with uniqueTempDirID for renaming */
						error = CatMove(realVRefNum, realParID, realName, uniqueTempDirID, NULL);
						if ( error == noErr )
						{
							/* Rename the object */	
							error = HRename(realVRefNum, uniqueTempDirID, realName, copyName);
							if ( error == noErr )
							{
								/* Move object to its new home */
								error = CatMove(realVRefNum, uniqueTempDirID, copyName, dstDirID, dstpathName);
								if ( error != noErr )
								{
									/* Error handling: rename object back to original name - ignore errors */
									(void) HRename(realVRefNum, uniqueTempDirID, copyName, realName);
								}
							}
							if ( error != noErr )
							{
								/* Error handling: move object back to original location - ignore errors */
								(void) CatMove(realVRefNum, uniqueTempDirID, realName, realParID, NULL);
							}
						}
						/* Done with ourTempDir, so delete it - ignore errors */
						(void) HDelete(realVRefNum, uniqueTempDirID, NULL);
					}
				}
			}
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpMoveRenameCompat(const FSSpec *srcSpec,
									const FSSpec *dstSpec,
									ConstStr255Param copyName)
{
	/* make sure the FSSpecs refer to the same volume */
	if (srcSpec->vRefNum != dstSpec->vRefNum)
		return (diffVolErr);
	return ( HMoveRenameCompat(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
					  dstSpec->parID, dstSpec->name, copyName) );
}

/*****************************************************************************/

pascal	OSErr	BuildAFPVolMountInfo(short flags,
									 char nbpInterval,
									 char nbpCount,
									 short uamType,
									 Str32 zoneName,
									 Str32 serverName,
									 Str27 volName,
									 Str31 userName,
									 Str8 userPassword,
									 Str8 volPassword,
									 AFPVolMountInfoPtr *afpInfoPtr)
{
	MyAFPVolMountInfoPtr	infoPtr;
	OSErr					error;
	
	/* Allocate the AFPXVolMountInfo record */
	infoPtr = (MyAFPVolMountInfoPtr)NewPtrClear(sizeof(MyAFPVolMountInfo));
	if ( infoPtr != NULL )
	{
		/* Fill in an AFPVolMountInfo record that can be passed to VolumeMount */
		infoPtr->length = sizeof(MyAFPVolMountInfo);
		infoPtr->media = AppleShareMediaType;
		infoPtr->flags = flags;
		infoPtr->nbpInterval = nbpInterval;
		infoPtr->nbpCount = nbpCount;
		infoPtr->uamType = uamType;
		
		infoPtr->zoneNameOffset = offsetof(MyAFPVolMountInfo, zoneName);
		infoPtr->serverNameOffset = offsetof(MyAFPVolMountInfo, serverName);
		infoPtr->volNameOffset = offsetof(MyAFPVolMountInfo, volName);
		infoPtr->userNameOffset = offsetof(MyAFPVolMountInfo, userName);
		infoPtr->userPasswordOffset = offsetof(MyAFPVolMountInfo, userPassword);
		infoPtr->volPasswordOffset = offsetof(MyAFPVolMountInfo, volPassword);
		
		BlockMoveData(zoneName, infoPtr->zoneName, sizeof(Str32));
		BlockMoveData(serverName, infoPtr->serverName, sizeof(Str32));
		BlockMoveData(volName, infoPtr->volName, sizeof(Str27));
		BlockMoveData(userName, infoPtr->userName, sizeof(Str31));
		BlockMoveData(userPassword, infoPtr->userPassword, sizeof(Str8));
		BlockMoveData(volPassword, infoPtr->volPassword, sizeof(Str8));
		
		*afpInfoPtr = (AFPVolMountInfoPtr)infoPtr;
		error = noErr;
	}
	else
	{
		error = memFullErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	RetrieveAFPVolMountInfo(AFPVolMountInfoPtr afpInfoPtr,
										short *flags,
										short *uamType,
										StringPtr zoneName,
										StringPtr serverName,
										StringPtr volName,
										StringPtr userName)
{
	StringPtr	tempPtr;
	OSErr		error;
		
	/* Retrieve the AFP mounting information from an AFPVolMountInfo record. */
	if ( afpInfoPtr->media == AppleShareMediaType )
	{
		*flags = afpInfoPtr->flags;
		*uamType = afpInfoPtr->uamType;
		
		if ( afpInfoPtr->zoneNameOffset != 0)
		{
			tempPtr = (StringPtr)((long)afpInfoPtr + afpInfoPtr->zoneNameOffset);
			BlockMoveData(tempPtr, zoneName, tempPtr[0] + 1);
		}
		
		if ( afpInfoPtr->serverNameOffset != 0)
		{
			tempPtr = (StringPtr)((long)afpInfoPtr + afpInfoPtr->serverNameOffset);
			BlockMoveData(tempPtr, serverName, tempPtr[0] + 1);
		}
		
		if ( afpInfoPtr->volNameOffset != 0)
		{
			tempPtr = (StringPtr)((long)afpInfoPtr + afpInfoPtr->volNameOffset);
			BlockMoveData(tempPtr, volName, tempPtr[0] + 1);
		}
		
		if ( afpInfoPtr->userNameOffset != 0)
		{
			tempPtr = (StringPtr)((long)afpInfoPtr + afpInfoPtr->userNameOffset);
			BlockMoveData(tempPtr, userName, tempPtr[0] + 1);
		}
		
		error = noErr;
	}
	else
	{
		error = paramErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	BuildAFPXVolMountInfo(short flags,
									  char nbpInterval,
									  char nbpCount,
									  short uamType,
									  Str32 zoneName,
									  Str32 serverName,
									  Str27 volName,
									  Str31 userName,
									  Str8 userPassword,
									  Str8 volPassword,
									  Str32 uamName,
									  unsigned long alternateAddressLength,
									  void *alternateAddress,
									  AFPXVolMountInfoPtr *afpXInfoPtr)
{
	Size					infoSize;
	MyAFPXVolMountInfoPtr	infoPtr;
	OSErr					error;
	
	/* Calculate the size of the AFPXVolMountInfo record */
	infoSize = sizeof(MyAFPXVolMountInfo) + alternateAddressLength - 1;
	
	/* Allocate the AFPXVolMountInfo record */
	infoPtr = (MyAFPXVolMountInfoPtr)NewPtrClear(infoSize);
	if ( infoPtr != NULL )
	{
		/* Fill in an AFPXVolMountInfo record that can be passed to VolumeMount */
		infoPtr->length = infoSize;
		infoPtr->media = AppleShareMediaType;
		infoPtr->flags = flags;
		if ( alternateAddressLength != 0 )
		{
			/* make sure the volMountExtendedFlagsBit is set if there's extended address info */
			infoPtr->flags |= volMountExtendedFlagsMask;
			/* and set the only extendedFlags bit we know about */
			infoPtr->extendedFlags = kAFPExtendedFlagsAlternateAddressMask;
		}
		else
		{
			/* make sure the volMountExtendedFlagsBit is clear if there's no extended address info */
			infoPtr->flags &= ~volMountExtendedFlagsMask;
			/* and clear the extendedFlags */
			infoPtr->extendedFlags = 0;
		}
		infoPtr->nbpInterval = nbpInterval;
		infoPtr->nbpCount = nbpCount;
		infoPtr->uamType = uamType;
		
		infoPtr->zoneNameOffset = offsetof(MyAFPXVolMountInfo, zoneName);		
		infoPtr->serverNameOffset = offsetof(MyAFPXVolMountInfo, serverName);
		infoPtr->volNameOffset = offsetof(MyAFPXVolMountInfo, volName);
		infoPtr->userNameOffset = offsetof(MyAFPXVolMountInfo, userName);
		infoPtr->userPasswordOffset = offsetof(MyAFPXVolMountInfo, userPassword);
		infoPtr->volPasswordOffset = offsetof(MyAFPXVolMountInfo, volPassword);
		infoPtr->uamNameOffset = offsetof(MyAFPXVolMountInfo, uamName);
		infoPtr->alternateAddressOffset = offsetof(MyAFPXVolMountInfo, alternateAddress);
		
		BlockMoveData(zoneName, infoPtr->zoneName, sizeof(Str32));
		BlockMoveData(serverName, infoPtr->serverName, sizeof(Str32));
		BlockMoveData(volName, infoPtr->volName, sizeof(Str27));
		BlockMoveData(userName, infoPtr->userName, sizeof(Str31));
		BlockMoveData(userPassword, infoPtr->userPassword, sizeof(Str8));
		BlockMoveData(volPassword, infoPtr->volPassword, sizeof(Str8));
		BlockMoveData(uamName, infoPtr->uamName, sizeof(Str32));
		BlockMoveData(alternateAddress, infoPtr->alternateAddress, alternateAddressLength);
		
		*afpXInfoPtr = (AFPXVolMountInfoPtr)infoPtr;
		error = noErr;
	}
	else
	{
		error = memFullErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	RetrieveAFPXVolMountInfo(AFPXVolMountInfoPtr afpXInfoPtr,
										 short *flags,
										 short *uamType,
										 StringPtr zoneName,
										 StringPtr serverName,
										 StringPtr volName,
										 StringPtr userName,
										 StringPtr uamName,
										 unsigned long *alternateAddressLength,
										 AFPAlternateAddress **alternateAddress)
{
	StringPtr	tempPtr;
	Ptr			alternateAddressStart;
	Ptr			alternateAddressEnd;
	Size		alternateAddressDataSize;
	OSErr		error;
	UInt8		addressCount;
		
	/* Retrieve the AFP mounting information from an AFPVolMountInfo record. */
	if ( afpXInfoPtr->media == AppleShareMediaType )
	{
		/* default to noErr */
		error = noErr;
		
		/* Is this an extended record? */
		if ( (afpXInfoPtr->flags & volMountExtendedFlagsMask) != 0 )
		{
			if ( ((afpXInfoPtr->extendedFlags & kAFPExtendedFlagsAlternateAddressMask) != 0) &&
				 (afpXInfoPtr->alternateAddressOffset != 0) )
			{
				
				alternateAddressStart = (Ptr)((long)afpXInfoPtr + afpXInfoPtr->alternateAddressOffset);
				alternateAddressEnd = alternateAddressStart + 1;	/* skip over alternate address version byte */
				addressCount = *(UInt8*)alternateAddressEnd;		/* get the address count */
				++alternateAddressEnd;								/* skip over alternate address count byte */
				/* alternateAddressEnd now equals &AFPAlternateAddress.fAddressList[0] */
				while ( addressCount != 0 )
				{
					/* parse the address list to find the end */
					alternateAddressEnd += *(UInt8*)alternateAddressEnd;	/* add length of each AFPTagData record */
					--addressCount;
				}
				/* get the size of the alternateAddressData */
				alternateAddressDataSize = alternateAddressEnd - alternateAddressStart;
				/* allocate memory for it */
				*alternateAddress = (AFPAlternateAddress *)NewPtr(alternateAddressDataSize);
				if ( *alternateAddress != NULL )
				{
					/* and return the data */
					BlockMoveData(alternateAddressStart, *alternateAddress, alternateAddressDataSize);
					*alternateAddressLength = alternateAddressDataSize;
				}
				else
				{
					/* no memory - fail now */
					error = memFullErr;
				}
			}
			
			if ( error == noErr )	/* fill in more output parameters if everything is OK */
			{
				if ( afpXInfoPtr->uamNameOffset != 0 )
				{
					tempPtr = (StringPtr)((long)afpXInfoPtr + afpXInfoPtr->uamNameOffset);
					BlockMoveData(tempPtr, uamName, tempPtr[0] + 1);
				}
			}
		}
		
		if ( error == noErr )	/* fill in more output parameters if everything is OK */
		{
			*flags = afpXInfoPtr->flags;
			*uamType = afpXInfoPtr->uamType;
			
			if ( afpXInfoPtr->zoneNameOffset != 0 )
			{
				tempPtr = (StringPtr)((long)afpXInfoPtr + afpXInfoPtr->zoneNameOffset);
				BlockMoveData(tempPtr, zoneName, tempPtr[0] + 1);
			}
			
			if ( afpXInfoPtr->serverNameOffset != 0 )
			{
				tempPtr = (StringPtr)((long)afpXInfoPtr + afpXInfoPtr->serverNameOffset);
				BlockMoveData(tempPtr, serverName, tempPtr[0] + 1);
			}
			
			if ( afpXInfoPtr->volNameOffset != 0 )
			{
				tempPtr = (StringPtr)((long)afpXInfoPtr + afpXInfoPtr->volNameOffset);
				BlockMoveData(tempPtr, volName, tempPtr[0] + 1);
			}
			
			if ( afpXInfoPtr->userNameOffset != 0 )
			{
				tempPtr = (StringPtr)((long)afpXInfoPtr + afpXInfoPtr->userNameOffset);
				BlockMoveData(tempPtr, userName, tempPtr[0] + 1);
			}
		}
	}
	else
	{
		error = paramErr;
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	GetUGEntries(short objType,
							 UGEntryPtr entries,
							 long reqEntryCount,
							 long *actEntryCount,
							 long *objID)
{
	HParamBlockRec pb;
	OSErr error = noErr;
	UGEntry *endEntryArray;

	pb.objParam.ioObjType = objType;
	*actEntryCount = 0;
	for ( endEntryArray = entries + reqEntryCount; (entries < endEntryArray) && (error == noErr); ++entries )
	{
		pb.objParam.ioObjNamePtr = (StringPtr)entries->name;
		pb.objParam.ioObjID = *objID;
		/* Files.h in the universal interfaces, PBGetUGEntrySync takes a CMovePBPtr */
		/* as the parameter. Inside Macintosh and the original glue used HParmBlkPtr. */
		/* A CMovePBPtr works OK, but this will be changed in the future  back to */
		/* HParmBlkPtr, so I'm just casting it here. */
		error = PBGetUGEntrySync(&pb);
		if ( error == noErr )
		{
			entries->objID = *objID = pb.objParam.ioObjID;
			entries->objType = objType;
			++*actEntryCount;
		}
	}
	
	return ( error );
}

/*****************************************************************************/