/*
	File:		MoreFilesExtras.c

	Contains:	A collection of useful high-level File Manager routines

	Version:	MoreFiles

	Copyright:	© 1992-2001 by 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.

	File Ownership:

		DRI:				Jim Luther

		Other Contact:		Apple Macintosh Developer Technical Support
							<http://developer.apple.com/bugreporter/>

		Technology:			DTS Sample Code

	Writers:

		(JL)	Jim Luther

	Change History (most recent first):

		 <2>	  2/7/01	JL		[2500429]  Changed null output parameters to real variables when
									calling GetSharedLibrary to prevent crashes with older versions
									of CFM. Added standard header. Updated names of includes. Added
									C function implementations of accessors that used to be macros
									since the generated Pascal headers no longer contain
									implementations. Updated various other routines to use new
									calling convention of the accessor functions.
		<1>		12/06/99	JL		MoreFiles 1.5.
*/

#include <MacTypes.h>
#if !TARGET_CARBON
#include <Traps.h>
#include <FSM.h>
#include <Disks.h>
#else
#include <HFSVolumes.h>
#endif
#include <OSUtils.h>
#include <MacErrors.h>
#include <MacMemory.h>
#include <Files.h>
#include <Devices.h>
#include <Finder.h>
#include <Folders.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 "MoreDesktopMgr.h"
#include "FSpCompat.h"

#include "MoreFilesExtras.h"

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

/* Functions to get information out of GetVolParmsInfoBuffer. */

/* version 1 field getters */

pascal	short	GetVolParmsInfoVersion(const GetVolParmsInfoBuffer *volParms)
{
	return ( volParms->vMVersion );
}

pascal	long	GetVolParmsInfoAttrib(const GetVolParmsInfoBuffer *volParms)
{
	return ( volParms->vMAttrib );
}

pascal	Handle	GetVolParmsInfoLocalHand(const GetVolParmsInfoBuffer *volParms)
{
	return ( volParms->vMLocalHand );
}

pascal	long	GetVolParmsInfoServerAdr(const GetVolParmsInfoBuffer *volParms)
{
	return ( volParms->vMServerAdr );
}

/* version 2 field getters (assume zero result if version < 2) */

pascal	long	GetVolParmsInfoVolumeGrade(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMVersion >= 2) ? volParms->vMVolumeGrade : 0 );
}

pascal	long	GetVolParmsInfoForeignPrivID(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMVersion >= 2) ? volParms->vMForeignPrivID : 0 );
}

/* version 3 field getters (assume zero result if version < 3) */

pascal	long	GetVolParmsInfoExtendedAttributes(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMVersion >= 3) ? volParms->vMExtendedAttributes : 0 );
}

/* attribute bits supported by all versions of GetVolParmsInfoBuffer */

pascal	Boolean	isNetworkVolume(const GetVolParmsInfoBuffer *volParms)
{
	return ( volParms->vMServerAdr != 0 );
}

pascal	Boolean	hasLimitFCBs(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bLimitFCBs)) != 0 );
}

pascal	Boolean	hasLocalWList(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bLocalWList)) != 0 );
}

pascal	Boolean	hasNoMiniFndr(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoMiniFndr)) != 0 );
}

pascal	Boolean	hasNoVNEdit(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoVNEdit)) != 0 );
}

pascal	Boolean	hasNoLclSync(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoLclSync)) != 0 );
}

pascal	Boolean	hasTrshOffLine(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bTrshOffLine)) != 0 );
}

pascal	Boolean	hasNoSwitchTo(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoSwitchTo)) != 0 );
}

pascal	Boolean	hasNoDeskItems(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoDeskItems)) != 0 );
}

pascal	Boolean	hasNoBootBlks(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoBootBlks)) != 0 );
}

pascal	Boolean	hasAccessCntl(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bAccessCntl)) != 0 );
}

pascal	Boolean	hasNoSysDir(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bNoSysDir)) != 0 );
}

pascal	Boolean	hasExtFSVol(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasExtFSVol)) != 0 );
}

pascal	Boolean	hasOpenDeny(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasOpenDeny)) != 0 );
}

pascal	Boolean	hasCopyFile(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasCopyFile)) != 0 );
}

pascal	Boolean	hasMoveRename(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasMoveRename)) != 0 );
}

pascal	Boolean	hasDesktopMgr(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasDesktopMgr)) != 0 );
}

pascal	Boolean	hasShortName(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasShortName)) != 0 );
}

pascal	Boolean	hasFolderLock(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasFolderLock)) != 0 );
}

pascal	Boolean	hasPersonalAccessPrivileges(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasPersonalAccessPrivileges)) != 0 );
}

pascal	Boolean	hasUserGroupList(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasUserGroupList)) != 0 );
}

pascal	Boolean	hasCatSearch(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasCatSearch)) != 0 );
}

pascal	Boolean	hasFileIDs(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasFileIDs)) != 0 );
}

pascal	Boolean	hasBTreeMgr(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasBTreeMgr)) != 0 );
}

pascal	Boolean	hasBlankAccessPrivileges(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasBlankAccessPrivileges)) != 0 );
}

pascal	Boolean	supportsAsyncRequests(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bSupportsAsyncRequests)) != 0 );
}

pascal	Boolean	supportsTrashVolumeCache(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bSupportsTrashVolumeCache)) != 0 );
}

/* attribute bits supported by version 3 and greater versions of GetVolParmsInfoBuffer */

pascal	Boolean	volIsEjectable(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bIsEjectable)) != 0 );
}

pascal	Boolean	volSupportsHFSPlusAPIs(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsHFSPlusAPIs)) != 0 );
}

pascal	Boolean	volSupportsFSCatalogSearch(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsFSCatalogSearch)) != 0 );
}

pascal	Boolean	volSupportsFSExchangeObjects(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsFSExchangeObjects)) != 0 );
}

pascal	Boolean	volSupports2TBFiles(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupports2TBFiles)) != 0 );
}

pascal	Boolean	volSupportsLongNames(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsLongNames)) != 0 );
}

pascal	Boolean	volSupportsMultiScriptNames(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsMultiScriptNames)) != 0 );
}

pascal	Boolean	volSupportsNamedForks(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsNamedForks)) != 0 );
}

pascal	Boolean	volSupportsSubtreeIterators(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bSupportsSubtreeIterators)) != 0 );
}

pascal	Boolean	volL2PCanMapFileBlocks(const GetVolParmsInfoBuffer *volParms)
{
	return ( (GetVolParmsInfoExtendedAttributes(volParms) & (1L << bL2PCanMapFileBlocks)) != 0 );
}

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

/* Functions for testing ioACUser bits. */

pascal	Boolean	userIsOwner(SInt8 ioACUser)
{
	return ( (ioACUser & kioACUserNotOwnerMask) == 0 );
}

pascal	Boolean	userHasFullAccess(SInt8 ioACUser)
{
	return ( (ioACUser & acUserAccessMask) == acUserFull );
}

pascal	Boolean	userHasDropBoxAccess(SInt8 ioACUser)
{
	return ( (ioACUser & acUserAccessMask) == acUserDropBox );
}

pascal	Boolean	userHasBulletinBoard(SInt8 ioACUser)
{
	return ( (ioACUser & acUserAccessMask) == acUserBulletinBoard );
}

pascal	Boolean	userHasNoAccess(SInt8 ioACUser)
{
	return ( (ioACUser & acUserAccessMask) == acUserNone );
}

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

/* 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;
		Ptr							mainAddr;
		Str255						errMessage;
		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, &mainAddr, errMessage);
			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;
			}
		}
	}
}

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

static 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 );
}

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

