/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#include "fileutil.h"
#define FILE_NUM 13
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */

pascal Boolean FolderFilter(FileParam *pb);
pascal short FolderItems(short item,DialogPtr dlog);
OSErr FSpExchangeFilesCompat(const FSSpec *source, const FSSpec *dest);
Boolean	HasFileIDs(const GetVolParmsInfoBuffer *volParms);
static OSErr GenerateUniqueName(short volume,long *startSeed,long dir1,long dir2,StringPtr uniqueName);
Boolean FileIsInvisible(CInfoPBRec *hfi);

/**********************************************************************
 * various useful functions related to the filesystem
 **********************************************************************/

#pragma segment FileUtil
#define FILL(pb,name,vRef,dirId) do { 									\
	(pb).ioNamePtr = name;																\
	(pb).ioVRefNum = vRef;																\
	(pb).ioDirID = dirId; 																\
	} while (0)

void FileIDHack(void);	// JDB 980720 Hack to work around apple's fileID bug

/**********************************************************************
 * GetMyVR - get a volume ref number
 **********************************************************************/
short GetMyVR(UPtr name)
{
	HParamBlockRec vInfo;
	Str255	fileName;
	
	PCopy(fileName,name);	//	Make copy, name might get overwritten
	// PBHGetVInfo doesn't work right without ':' at end of volume name
	if (fileName[*fileName] != ':') PCatC(fileName,':');
	FILL(*((HFileInfo*)&vInfo),fileName,0,0);
	vInfo.volumeParam.ioVolIndex = -1;
	if (PBHGetVInfoSync((HParmBlkPtr)&vInfo))
		return(REAL_BIG);
	return(vInfo.volumeParam.ioVRefNum);
}

/************************************************************************
 * ParentSpec - get the FSSpec of a parent
 ************************************************************************/
OSErr ParentSpec(FSSpecPtr child,FSSpecPtr parent)
{
	CInfoPBRec pb;
	OSErr err;
	
	Zero(pb);
	pb.dirInfo.ioNamePtr = parent->name;
	pb.dirInfo.ioVRefNum = child->vRefNum;
	pb.dirInfo.ioDrDirID = child->parID;
	pb.dirInfo.ioFDirIndex = -1;		/* use ioDirID */
	err = PBGetCatInfoSync(&pb);
	if (!err)
	{
		*parent->name = 0;
		parent->parID = pb.dirInfo.ioDrParID;
	}
	return(err);
}

/**********************************************************************
 * get a name, given a vRefNum
 **********************************************************************/
short GetDirName(UPtr volName,short vRef, long dirId,UPtr name)
{
	CInfoPBRec catBlock;	/* to get the name of a directory */
	short err;
	
	if (volName && !vRef)
		//	Get vRef from volume name
		vRef = GetMyVR(volName);
	
	*name = 0;
	
	/*
	 * get name of directory
	 */
	catBlock.dirInfo.ioDrDirID = dirId;
	catBlock.dirInfo.ioNamePtr = name;
	catBlock.dirInfo.ioVRefNum = vRef;
	catBlock.dirInfo.ioFDirIndex = -1;		/* use ioDirID */
	if ((err=PBGetCatInfo(&catBlock,0))!=noErr) /* get working directory info */
		*name = 0;
	return(err);
}

/**********************************************************************
 * get a volume name, given a vRefNum
 **********************************************************************/
UPtr GetMyVolName(short refNum,UPtr name)
{
	HParamBlockRec vInfo;
		
	*name = 0;
	vInfo.volumeParam.ioNamePtr = name;
	vInfo.volumeParam.ioVRefNum = refNum;
	vInfo.volumeParam.ioVolIndex = 0;
	PBHGetVInfoSync((HParmBlkPtr)&vInfo); 				/* get the volume info */

	return(name);
}

/************************************************************************
 * IndexVRef - return vref's by index
 ************************************************************************/
OSErr IndexVRef(short index, short *vRef)
{
	HParamBlockRec myPB;
	int err;

	myPB.volumeParam.ioNamePtr = nil;
	myPB.volumeParam.ioVolIndex = index;
	myPB.volumeParam.ioCompletion = nil;
	
	if (err=PBHGetVInfoSync((HParmBlkPtr)&myPB)) return(err);
	*vRef = myPB.volumeParam.ioVRefNum;
	return(noErr);
}

/************************************************************************
 * BlessedVRef - find the vref of the blessed folder
 ************************************************************************/
short BlessedVRef(void)
{
	short	vRefNum;
	long	dirID;
	
	FindFolder(kOnSystemDisk,kSystemFolderType,false,&vRefNum,&dirID);
	return vRefNum;
}

/**********************************************************************
 * MakeResFile - create a resource file in a given directory
 **********************************************************************/
int MakeResFile(UPtr name,int vRef,long dirId,long creator,long type)
{
	int err;
	FSSpec spec;
	
	FSMakeFSSpec(vRef,dirId,name,&spec);
	FSpCreateResFile(&spec,creator,type,smSystemScript);
	err=ResError();
	// (jp) NetWare servers incorrectly report noMacDskErr when attempting to
	//			create a resource file (the signature bytes are apparently wrong)
	//			We can create a file with both forks, however.
	if (err == noMacDskErr)
		err=FSpCreate(&spec,creator,type,smSystemScript);
	return (err==dupFNErr ? noErr : err);
}

/**********************************************************************
 * DirIterate - iterate over the files in a directory.
 **********************************************************************/
short DirIterate(short vRef,long dirId,CInfoPBRec *hfi)
{
	OSErr err;
	short idx;
	StringPtr name;
	do
	{
	//	Save off bits
		idx = hfi->hFileInfo.ioFDirIndex;
		name = hfi->hFileInfo.ioNamePtr;

		Zero ( *hfi );
		name [ 0 ] = 0;
		
		hfi->hFileInfo.ioNamePtr = name;
		hfi->hFileInfo.ioFDirIndex = ++idx;		
		hfi->hFileInfo.ioDirID = dirId;
		hfi->hFileInfo.ioVRefNum = vRef;

		err = PBGetCatInfoSync((CInfoPBPtr)hfi);
	} while (!err && FileIsInvisible(hfi));	// ignore invisible files
	
#ifdef DEBUG
	if (RunType!=Production && err==-43)
	{
		CInfoPBRec cpb;
		Str63 lName;
		OSErr myErr;
		
		Zero(cpb);
		Zero(lName);
		cpb.dirInfo.ioNamePtr = lName;
		cpb.dirInfo.ioDrDirID = dirId;
		cpb.dirInfo.ioVRefNum = vRef;
		myErr = PBGetCatInfoSync(&cpb);
		ASSERT(!myErr);
		ASSERT(cpb.dirInfo.ioDrNmFls==idx-1);
	}
#endif
	return err;
}

/**********************************************************************
 * FileIsInvisible - is this file invisible?
 **********************************************************************/
Boolean FileIsInvisible(CInfoPBRec *hfi)
{
	return (hfi->hFileInfo.ioFlFndrInfo.fdFlags & fInvisible) ||
		(HaveOSX() && hfi->hFileInfo.ioNamePtr[0] && hfi->hFileInfo.ioNamePtr[1]=='.');
}

/**********************************************************************
 * CopyFBytes - copy bytes from one file to another
 **********************************************************************/
int CopyFBytes(short fromRefN,long fromOffset,long length,short toRefN,long toOffset)
{
	int err;
	Handle buffer;
	long size;
	long count;
	long fromEnd = fromOffset + length;
	long toEnd;
	
	if (err = GetEOF(toRefN,&toEnd)) return(err);
	
	if (toEnd < toOffset+length-1)
		if (err = SetEOF(toRefN,toOffset + length - 1)) return(err);
	toEnd = toOffset + length;
		
	buffer = NewIOBHandle(255,MIN(OPTIMAL_BUFFER,length));
	if (buffer==nil) return(WarnUser(MEM_ERR,MemError()));
	LDRef(buffer);
	size = GetHandleSize_(buffer);
	
	do
	{
		CycleBalls();
		count = size > length ? length : size;
		
		if (err = SetFPos(fromRefN,fsFromStart,fromEnd-count)) break;
		if (err = ARead(fromRefN,&count, (UPtr) *buffer)) break;
		
		if (err = SetFPos(toRefN,fsFromStart,toEnd-count)) break;
		if (err = NCWrite(toRefN,&count, (UPtr) *buffer)) break;
		
		length -= count;
		fromEnd -= count;
		toEnd -= count;
	}
	while (length);
	ZapHandle(buffer);
	return(err);
}


#define HNLSIZE 2048
/************************************************************************
 * NuntNewline - find a newline in a file
 ************************************************************************/
OSErr HuntNewline(short refN,long aroundSpot,long *newline,Boolean *realNl)
{
	UHandle buffer = (UHandle) NuHTempOK(HNLSIZE);
	long spot, count;
	UPtr nl1,nl2,end;
	UPtr aSpot;
	short err;
	
	if (!buffer) return(WarnUser(MEM_ERR,MemError()));
	LDRef(buffer);
	
	/*
	 * read in a buffer containing aSpot
	 */
	spot = MAX(0,aroundSpot-HNLSIZE/2);
	count = HNLSIZE;
	
	if (err = SetFPos(refN,fsFromStart,spot))
		{FileSystemError(READ_MBOX,"\p",err);goto done;}
	
	err = ARead(refN,&count, (UPtr) *buffer);
	if (err == eofErr && count>0) err = noErr;		/* ignore running off the end of the file as long as we got
																									 some bytes */
	if (err)
		{FileSystemError(READ_MBOX,"\p",err);goto done;}
	
	aSpot = (UPtr) *buffer + (aroundSpot-spot);
	end = (UPtr) *buffer + count;
	
	/*
	 * search both forwards and backwards for newlines
	 */
	for (nl1 = aSpot;nl1>=*buffer;nl1--) if (*nl1 == '\015') break;
	for (nl2 = aSpot;nl2<end;nl2++) if (*nl2 == '\015') break;
	
	/*
	 * take the closest newline to the desired spot
	 */
	if (nl1<*buffer)
	{
		if (nl2<end) aSpot = nl2;
	}
	else if (nl2>end) aSpot = nl1;
	else
		aSpot = ((nl2-aSpot)<(aSpot-nl1)) ? nl2 : nl1;
		
	*realNl = *aSpot == '\015';
	*newline = spot + (aSpot-*buffer)+1;
		
done:
	ZapHandle(buffer);
	return(err);
}

/************************************************************************
 * TruncOpenFile - truncate an open file to a given spot
 ************************************************************************/
OSErr TruncOpenFile(short refN, long spot)
{
	short err;
	
	if (err=SetFPos(refN,fsFromStart,spot)) return(err);
	return(SetEOF(refN,spot));
}

/************************************************************************
 * TruncAtMark - truncate an open file at the current spot
 ************************************************************************/
OSErr TruncAtMark(short refN)
{
	short err;
	long spot;
	
	if (err=GetFPos(refN,&spot)) return(err);
	return(SetEOF(refN,spot));
}

/************************************************************************
 * StdFilespot - figure out where a stdfile dialog should go
 ************************************************************************/
void StdFileSpot(Point *where, short id)
{
	Rect r,in;
	DialogTHndl dTempl; 			
	if ((dTempl=(DialogTHndl)GetResource_('ALRT',id)) ||
			(dTempl=(DialogTHndl)GetResource_('DLOG',id)))
	{
		BitMap	screenBits;
		
		r = (*dTempl)->boundsRect;
		GetQDGlobalsScreenBits(&screenBits);
		in = screenBits.bounds;
		in.top += GetMBarHeight();
		ThirdCenterRectIn(&r,&in);
		where->h = r.left;
		where->v = r.top;
	}
	else
	{
		where->h = 100;
		where->v = 100;
	}
}

/************************************************************************
 * AFSHOpen - like FSOpen, but takes a dirId and permissions, too.
 ************************************************************************/
short AFSHOpen(UPtr name,short vRefN,long dirId,short *refN,short perm)
{
	HParamBlockRec pb;
	int err;
	Str255 newName;
	
	Zero(pb);
	PCopy(newName,name);
	if (err=MyResolveAlias(&vRefN,&dirId,newName,nil)) return(err);
	pb.ioParam.ioNamePtr = newName;
	pb.ioParam.ioVRefNum = vRefN;
	pb.ioParam.ioPermssn = perm;
	pb.ioParam.ioMisc = nil;
	pb.fileParam.ioDirID = dirId;
	err = PBHOpenSync((HParmBlkPtr)&pb);
	if (!err) *refN = pb.ioParam.ioRefNum;
	return(err);
}

/************************************************************************
 * ARFHOpen - like RFOpen, but with dirId and permissions
 ************************************************************************/
short ARFHOpen(UPtr name,short vRefN,long dirId,short *refN,short perm)
{
	HParamBlockRec pb;
	int err;
	Str255 newName;
	
	PCopy(newName,name);
	Zero(pb);
	if (err=MyResolveAlias(&vRefN,&dirId,newName,nil)) return(err);	
	pb.ioParam.ioCompletion = nil;
	pb.ioParam.ioNamePtr = newName;
	pb.ioParam.ioVRefNum = vRefN;
	pb.ioParam.ioVersNum = 0;
	pb.ioParam.ioPermssn = perm;
	pb.ioParam.ioMisc = nil;
	pb.fileParam.ioDirID = dirId;
	err = PBHOpenRFSync((HParmBlkPtr)&pb);
	if (!err) *refN = pb.ioParam.ioRefNum;
	return(err);
}

/************************************************************************
 * VolumeMargin - is there enough space on a volume for something?
 ************************************************************************/
OSErr VolumeMargin(short vRef,long spaceNeeded)
{
	long margin = GetRLong(VOLUME_MARGIN);
	
	if (margin && VolumeFree(vRef)<spaceNeeded+margin) return(dskFulErr);

	return(noErr);
}

/************************************************************************
 * MyAllocate - do a PBAllocate call
 ************************************************************************/
int MyAllocate(short refN,long size)
{
	IOParam pb;
	
	pb.ioCompletion = nil;
	pb.ioRefNum = refN;
	pb.ioReqCount = size;
	return(PBAllocateSync((ParmBlkPtr)&pb));
}

/************************************************************************
 * SFPutOpen - open a file for write, using stdfile
 ************************************************************************/
short SFPutOpen(FSSpecPtr spec,long creator,long type,short *refN,ModalFilterYDUPP filter,DlgHookYDUPP hook,short id,FSSpecPtr defaultSpec,PStr windowTitle, PStr message)
{
	FInfo				info;
	ScriptCode	script;
	OSErr				theError;
	short				ditlID;

	if (!MommyMommy(ATTENTION,nil)) return(1);

	if (UseNavServices ()) {
		switch (id) {
			case SAVEAS_DLOG:
				ditlID = SAVEAS_NAV_DITL;
				break;
			default:
				ditlID = 0;
				break;
		}
		theError = SFPutOpenNav (spec, creator, type, refN, ditlID, &script, defaultSpec, windowTitle, message);
	}
	if (theError)
		return (theError);

	/*
	 * create && open the file
	 */
	if (theError=FSpCreate(spec,creator,type, script))
	{
		if (theError == dupFNErr) theError = noErr;
		else
		{
			FileSystemError(COULDNT_SAVEAS,spec->name,theError);
			return(theError);
		}
	}

	if (theError=FSpOpenDF(spec,fsRdWrPerm,refN))
	{
		FileSystemError(COULDNT_SAVEAS,spec->name,theError);
		FSpDelete(spec);
		return(theError);
	}
	
	if (!(theError=FSpGetFInfo(spec,&info)))
	{
		info.fdType = type;
		FSpSetFInfo(spec,&info);
	}

	if (theError=SetEOF(*refN,0))
	{
		FileSystemError(COULDNT_SAVEAS,spec->name,theError);
		FSpDelete(spec);
		return(theError);
	}
	
	WhackFinder(spec);
	
	return(noErr);	
}

/**********************************************************************
 * FSpModDate - get the mod date of a file
 **********************************************************************/
uLong FSpModDate(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	
	if (FSpGetHFileInfo(spec,&hfi)) return(0);
	return(hfi.hFileInfo.ioFlMdDat);
}

/************************************************************************
 * Snarf - read a whole file into a handle
 ************************************************************************/
OSErr Snarf(FSSpecPtr spec, Handle *hp, long limit)
{
	short refN;
	long bytes;
	short err;
	
	if (!(err=AFSpOpenDF(spec,spec,fsRdPerm,&refN)))
	{
		if (!(err=GetEOF(refN,&bytes)))
		{
			if (limit) bytes = MIN(bytes,limit);
			*hp = NuHTempOK(bytes);
			if (!*hp) err = MemError();
			else if (err=ARead(refN,&bytes,(UPtr)(LDRef(*hp))))
			{
				ZapHandle(*hp);
				*hp = nil;
			}
			else UL(*hp);
		}
		MyFSClose(refN);
	}
	return(err);
}

/************************************************************************
 * SnarfRoman - read a whole file into a handle, and make it roman text if we can
 ************************************************************************/
OSErr SnarfRoman(FSSpecPtr spec, Handle *hp, long limit)
{
	// grab the actual text
	OSErr err = Snarf(spec,hp,limit);
	
	if (!err) err = SniffAndConvertHandleToRoman(hp);
	
	if (err) ZapHandle(*hp);
	
	return err;
}

/************************************************************************
 * Blat - blat a handle out to a text file
 ************************************************************************/
OSErr Blat(FSSpecPtr spec,Handle text,Boolean append)
{
	OSErr err;
	
	LDRef(text);
	err = BlatPtr(spec,*text,GetHandleSize_(text),append);
	UL(text);
	return(err);
}

/************************************************************************
 * BlatPtr - blat text out to a text file
 ************************************************************************/
OSErr BlatPtr(FSSpecPtr spec,Ptr text,long size,Boolean append)
{
	FSSpec newSpec;
	OSErr err;
	short refN;
		
	(void) FSpCreate(spec,DefaultCreator(),'TEXT',smSystemScript);
	
	if (err=AFSpOpenDF(spec,&newSpec,fsRdWrPerm,&refN))
		FileSystemError(TEXT_WRITE,spec->name,err);
	else
	{
		if (append && (err=SetFPos(refN,fsFromLEOF,0)))
			FileSystemError(TEXT_WRITE,spec->name,err);
		if (err=AWrite(refN,&size, (UPtr) text))
			FileSystemError(TEXT_WRITE,spec->name,err);
		else if (err=TruncAtMark(refN))
			FileSystemError(TEXT_WRITE,spec->name,err);
		MyFSClose(refN);
	}
	return(err);
}

/**********************************************************************
 * FileTypeOf - get the type of a file
 **********************************************************************/
OSType FileTypeOf(FSSpecPtr spec)
{
	FInfo info;
	if (!AFSpGetFInfo(spec,spec,&info)) return(info.fdType);
	else return(nil);
}

/**********************************************************************
 * FlushFile - flush a file buffer
 **********************************************************************/
OSErr FlushFile(short refN)
{
	ParamBlockRec pb;
	
	Zero(pb);
	pb.ioParam.ioRefNum = refN;
	return(PBFlushFileSync(&pb));
}

/**********************************************************************
 * MyUpdateResFile - udpate a resource file, and MAKE darn SURE
 **********************************************************************/
OSErr MyUpdateResFile(short resFile)
{
	OSErr err = noErr;
	
	if (GetResFileAttrs(resFile)&mapChanged)
	{
		UpdateResFile(resFile);
		if (!(err=ResError()) && !PrefIsSet(PREF_CORVAIR))
			err = ,  MakeDarnSure(resFile);
	}
	return(err);
}

/**********************************************************************
 * MakeDarnSure - a file is intact on disk
 **********************************************************************/
OSErr MakeDarnSure(short refN)
{
	OSErr err;
	FSSpec spec;
	
	if (!(err = FlushFile(refN)))
		if (!(err = GetFileByRef(refN,&spec)))
			err = FlushVol(nil,spec.vRefNum);
	return(err);
}

/**********************************************************************
 * FileCreatorOf - get the creator of a file
 **********************************************************************/
OSType FileCreatorOf(FSSpecPtr spec)
{
	FInfo info;
	if (!AFSpGetFInfo(spec,spec,&info)) return(info.fdCreator);
	else return(nil);
}

/************************************************************************
 * IsText - is a file of type TEXT or not?
 ************************************************************************/
Boolean IsText(FSSpecPtr spec)
{
	FInfo info;
	FSSpec newSpec;
	short err;
	
	err = AFSpGetFInfo(spec,&newSpec,&info);
	return(!err && info.fdType=='TEXT');	
}

/************************************************************************
 * SanitizeFN - make a filename more palatable
 ************************************************************************/
PStr SanitizeFN(PStr callerShortName,PStr name,short badCharId,short repCharId,Boolean kill8)
{
	short i,j;
	short where;
	Str63 badChars;
	Str63 repChars;
	Str255 newName;
	Str255 shortName;
	UPtr quoteExtensionUnquote = repChars;
	
	GetRString(badChars,badCharId);
	GetRString(repChars,repCharId);
	*newName = MIN(255,*name);
	where = 0;
	for (i=1;i<=*newName;i++)
	{
		if (name[i]<' ')
		{
			if (repChars[1]!=bulletChar) newName[++where] = repChars[1];
			continue;
		}
		else if (kill8 && name[i]>'~')
		{
			if (repChars[2]!=bulletChar) newName[++where] = repChars[2];
			continue;
		}
		else for (j=3;j<=*badChars;j++)
		{
			if (name[i] == badChars[j])
			{
				if (repChars[j]!=bulletChar) newName[++where] = repChars[j];
				goto loop;
			}
		}
		newName[++where] = name[i];
		loop:;
	}
	*newName = where;
	if (newName[1]=='.') newName[1] = '_';	/* initial period bad */
	
	// Stupid filename convention crap that I thought we'd gotten rid of
	// With MacOS...
	i = SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote(newName,shortName,quoteExtensionUnquote,31);
	*shortName = MIN(*shortName,i);
	PCopy(callerShortName,shortName);
	if (*quoteExtensionUnquote)
	{
		PCatC(callerShortName,'.');
		PCat(callerShortName,quoteExtensionUnquote);
	}
	
	return(callerShortName);
}

/************************************************************************
 * Mac2OtherName - transmogrify mac name to acceptable outworld name
 ************************************************************************/
PStr Mac2OtherName(PStr other,PStr mac)
{
	Str31 translitted;
	
	PSCopy(translitted,mac);
	TransLitRes(translitted+1,*translitted,ktMacUSHidden);
	return(SanitizeFN(other,translitted,OTHER_FN_BAD,OTHER_FN_REP,true));
}

/************************************************************************
 * ResolveAliasNoMount - resolve an alias, but don't mount any volumes
 ************************************************************************/
OSErr ResolveAliasNoMount(FSSpecPtr alias,FSSpecPtr orig,Boolean *wasAlias)
{
	FInfo info;
	short err;
	FSSpec spec;
	AliasHandle ah;
	short refN;
	short count=1;
	Boolean junk;
	short oldResF = CurResFile();
	
	// If it's a folder, it's not an alias.
	if (FSpIsItAFolder(alias))
	{
		if (*wasAlias) *wasAlias = false;
		if (orig) *orig = *alias;
		return noErr;
	}
	
	/*
	 * is it an alias?
	 */
	if (err=FSpGetFInfo(alias,&info)) return(err);
	
	// This used to just copy alias into orig
	// That meant that the alias fsspec would get
	// turned into the original file.  There are places
	// where that was not helpful.  It may have been relied
	// on elsewhere, however, so we'll preserve
	// that behavior in the case where no orig pointer
	// is given
	if (!orig) orig = alias;
	else *orig = *alias;
	
	if (wasAlias) *wasAlias = (info.fdFlags & kIsAlias)!=0;
	if ((info.fdFlags & kIsAlias)==0) return(noErr);
	
	/*
	 * it's an alias; open it and extract the record
	 */
	if (0>(refN=FSpOpenResFile(alias,fsRdPerm))) return(err);
	ah = GetResource_('alis',0);
	if (ah) DetachResource((Handle)ah);
	CloseResFile(refN);
	UseResFile (oldResF);

	if (!ah) return(resNotFound);

	/*
	 * resolve the record
	 */
	FSMakeFSSpec(Root.vRef,Root.dirId,"\p",&spec);	/* Eudora Folder as base */
	err=MatchAlias(&spec,
											kARMSearch | 					/* allow id search */
											kARMSearchRelFirst |	/* darn fileid's; denigrate */
											kARMNoUI,							/* don't bug the user */
																						/* note we do not specify kARMMountVol */
										  ah,&count,orig,&junk,nil,nil);
	
	ZapHandle(ah);
	return(err);
}

/************************************************************************
 * SpinOn - spin until a return code is not inProgress or cacheFault
 ************************************************************************/
short SpinOnLo(volatile OSErr *rtnCodeAddr,long maxTicks,Boolean allowCancel,Boolean forever,Boolean remainCalm, Boolean allowMouseDown)
{
	long ticks=TickCount();
	long startTicks=ticks+120;
	long now;
#ifdef CTB
	extern ConnHandle CnH;
#endif
	Boolean oldCommandPeriod = CommandPeriod;
	Boolean slow = False;
	static short slowThresh;
	
	if (!slowThresh) slowThresh = GetRLong(SPIN_LENGTH);
		
	if (allowCancel) YieldTicks = 0;
	if (allowCancel || *rtnCodeAddr==inProgress || *rtnCodeAddr==cacheFault)
	{
		CommandPeriod = False;
		do
		{
			now = TickCount();
			if (now>startTicks && now-ticks>slowThresh) {slow = True;if (!InAThread()) CyclePendulum(); else MyYieldToAnyThread();ticks=now;}
			if (slow && !InAThread()) YieldTicks = 0;
			MiniEventsLo((!remainCalm || GetNumBackgroundThreads()) ? 0 : 300, allowMouseDown ? MINI_MASK|mDownMask : MINI_MASK);
			if (CommandPeriod  && !forever) return(userCancelled);
			if (maxTicks && startTicks+maxTicks < now+120) break;
		}
		while (*rtnCodeAddr == inProgress || *rtnCodeAddr == cacheFault);
		if (CommandPeriod) return(userCancelled);
		CommandPeriod = oldCommandPeriod;
	}
	return(*rtnCodeAddr);
}

/**********************************************************************
 * FSpTrash - trash a file
 **********************************************************************/
OSErr FSpTrash(FSSpecPtr spec)
{
	FSSpec trashSpec;
	OSErr err;
	FSSpec exist, newExist;
	
	if (!(err=GetTrashSpec(spec->vRefNum,&trashSpec)))
	{
		if (!FSMakeFSSpec(trashSpec.vRefNum,trashSpec.parID,spec->name,&exist))
		{	
			newExist = exist;
			UniqueSpec(&newExist,31);
			FSpRename(&exist,newExist.name);
		}
		err = SpecMove(spec,&trashSpec);
	}
	return(err);
}

/**********************************************************************
 * UniqueSpec - make a unique filename
 **********************************************************************/
UniqueSpec(FSSpecPtr spec,short max)
{
	short i;
	CInfoPBRec hfi;
	Str31 dfName;
	Str31 dfQuoteExtensionUnquote;
	Str15 iAscii;
	OSErr err;
	
	//
	// Now that we've entered unix-land, we have to pay attention to
	// "extensions".
	//
	max = SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote(spec->name,dfName,dfQuoteExtensionUnquote,max);		
	
	for (i=1;i<9999;i++)
	{
		/*
		 * does file exist?
		 */
		err = HGetCatInfo(spec->vRefNum,spec->parID,spec->name,&hfi);
#ifdef NEVER
		if (RunType!=Production) Dprintf("\pHGetCatInfo %p: %d;sc;g",spec->name,err);
#endif
		if (err==fnfErr) return(noErr);
		
		/*
		 * oops.  file exists.  increment number on end of filename
		 */
		PCopy(spec->name,dfName);
		*iAscii = 0;
		PLCat(iAscii,i);
		if (*spec->name + *iAscii < max)
			PCat(spec->name,iAscii);
		else
		{
			BMD(iAscii+1,spec->name+(max+1)-*iAscii,*iAscii);
			*spec->name = max;
		}
		
		//
		// add "extension"
		if (*dfQuoteExtensionUnquote)
		{
			PSCatC(spec->name,'.');
			PSCat(spec->name,dfQuoteExtensionUnquote);
		}
	}
	return(dupFNErr);
}

/**********************************************************************
 * SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote -
 *   we hate Windows
 **********************************************************************/
short SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote(PStr name,PStr dfName,PStr dfQuoteExtensionUnquote,short max)
{		
	UPtr spot;
	Str31 localQuoteExtensionUnquote;
	
	PCopy(dfName,name);
	*dfQuoteExtensionUnquote = 0;
	
	if (spot = PRIndex(dfName,'.'))
	{
		// found the last period
		// create the "extension"
		MakePStr(localQuoteExtensionUnquote,spot+1,*dfName-(spot-dfName));
		PCopy(dfQuoteExtensionUnquote,localQuoteExtensionUnquote);
		
		// the "extension" must be nonzero and mustn't be, like, rilly big
		if (*dfQuoteExtensionUnquote && *dfQuoteExtensionUnquote < *dfName-2 && *dfQuoteExtensionUnquote<8)
		{
			*dfName = spot-dfName-1;
			max -= *dfQuoteExtensionUnquote + 1;
		}
		else
			*dfQuoteExtensionUnquote = 0;
	}
	
	return max;
}


/**********************************************************************
 * TweakFileType - set a file's type, and make sure the Finder catches on
 **********************************************************************/
OSErr TweakFileType(FSSpecPtr spec,OSType type,OSType creator)
{
	FInfo info;
	OSErr err;
	FSSpec dirSpec;
	
	/*
	 * get parent info
	 */
	if (!(err=FSpGetFInfo(spec,&info)))
	{
		info.fdType = type;
		info.fdCreator = creator;
		info.fdFlags &= ~kHasBeenInited;
		if (type=='APPL') info.fdFlags |= fHasBundle;
		if (!(err=FSpSetFInfo(spec,&info)))
		{
			if (!(err=FSMakeFSSpec(spec->vRefNum,spec->parID,"\p",&dirSpec)))
				err = FSpTouch(&dirSpec);
		}
	}
	return(err);
}

/**********************************************************************
 * FSpTouch - set a file's mod date to now
 **********************************************************************/
OSErr FSpTouch(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	OSErr err;
	Str31 name;
	
	PSCopy(name,spec->name);
	if (!(err=HGetCatInfo(spec->vRefNum,spec->parID,name,&hfi)))
	{
		hfi.hFileInfo.ioFlMdDat = LocalDateTime();
		PSCopy(name,spec->name);
		err = HSetCatInfo(spec->vRefNum,spec->parID,name,&hfi);
	}
	return(err);
}

/**********************************************************************
 * FSpExists - see if a file exists
 **********************************************************************/
OSErr FSpExists(FSSpecPtr spec)
{
	FInfo info;
	return(FSpGetFInfo(spec,&info));
}

/**********************************************************************
 * FSpRFSane - is a resource file sane?
 **********************************************************************/
OSErr FSpRFSane(FSSpecPtr spec,Boolean *sane)
{
	OSErr err;
	
	err = utl_RFSanity(spec,sane);
	return(err);
}

/**********************************************************************
 * FSpKillRFork - kill the resource fork of a file
 **********************************************************************/
OSErr FSpKillRFork(FSSpecPtr spec)
{
	OSErr err;
	short refN;
	
	if (!(err=FSpOpenRF(spec,fsRdWrPerm,&refN)))
	{
		err = SetEOF(refN,0);
		MyFSClose(refN);
	}
	return err;
}

/************************************************************************
 * AliasFolderType - does alias'es filetype represent a folder?
 ************************************************************************/
Boolean AliasFolderType(OSType type)
{
	OSType types[] = {kExportedFolderAliasType,kContainerServerAliasType,
		kContainerFloppyAliasType,kContainerFolderAliasType,kContainerHardDiskAliasType,
		kMountedFolderAliasType,kSharedFolderAliasType};
	short i = sizeof(types)/sizeof(OSType);
	
	while(i--) if (type==types[i]) return(True);
	return(False);
}

/************************************************************************
 * IsItAFolder - is the specified file a folder?
 ************************************************************************/
Boolean IsItAFolder(short vRef,long inDirId,UPtr name)
{
	CInfoPBRec hfi;
	short err;
	
	hfi.hFileInfo.ioCompletion=nil;
	hfi.hFileInfo.ioNamePtr=name;
	hfi.hFileInfo.ioVRefNum=vRef;
	hfi.hFileInfo.ioDirID=inDirId;
	hfi.hFileInfo.ioFDirIndex=0;
	if (err=PBGetCatInfoSync((CInfoPBPtr)&hfi)) return(false);
	return(0!=(hfi.hFileInfo.ioFlAttrib&0x10));
}

/************************************************************************
 * HFIIsFolder - is this thing a folder?  special case since we have hfi already
 ************************************************************************/
Boolean HFIIsFolder(CInfoPBRec *hfi)
{
	return 0!=(hfi->hFileInfo.ioFlAttrib&0x10);
}

/************************************************************************
 * HFIIsFolderOrAlias - is this thing a folder?  special case since we have hfi already
 ************************************************************************/
Boolean HFIIsFolderOrAlias(CInfoPBRec *hfi)
{
	if (hfi->hFileInfo.ioFlFndrInfo.fdFlags & kIsAlias)
		return(AliasFolderType(hfi->hFileInfo.ioFlFndrInfo.fdType));
	else
		return HFIIsFolder(hfi);
}

/************************************************************************
 * FolderSizeHi - how big is a folder?
 ************************************************************************/
void FolderSizeHi(short vRef,long dirID,uLong *cumSize)
{
	CInfoPBRec hfi;
	Str63 name;
	
	hfi.hFileInfo.ioNamePtr = name;
	*cumSize = 0;
	
	FolderSize(vRef,dirID,&hfi,cumSize);
	
	return;
}

/************************************************************************
 * FolderSize - how big is a folder?
 ************************************************************************/
void FolderSize(short vRef,long dirID,CInfoPBRec *hfi,uLong *cumSize)
{
	short index;
	
	for (hfi->hFileInfo.ioFDirIndex=index=0;!DirIterate(vRef,dirID,hfi);hfi->hFileInfo.ioFDirIndex=++index)
	{
		if (HFIIsFolder(hfi))
			FolderSize(vRef,hfi->dirInfo.ioDrDirID,hfi,cumSize);
		else
			*cumSize += hfi->hFileInfo.ioFlLgLen + hfi->hFileInfo.ioFlRLgLen;
	}
}

/************************************************************************
 * HGetCatInfo - get cat info for a file?
 ************************************************************************/
short HGetCatInfo(short vRef,long inDirId,UPtr name,CInfoPBRec *hfi)
{
	Zero(*hfi);
	hfi->hFileInfo.ioCompletion=nil;
	hfi->hFileInfo.ioNamePtr=name;
	hfi->hFileInfo.ioVRefNum=vRef;
	hfi->hFileInfo.ioDirID=inDirId;
	if (!name || !*name)
	{
		hfi->hFileInfo.ioFDirIndex = -1;
		hfi->dirInfo.ioDrDirID = inDirId;
	}
	else
		hfi->hFileInfo.ioFDirIndex=0;
	return(PBGetCatInfoSync((CInfoPBPtr)hfi));
}

/************************************************************************
 * HSetCatInfo - get cat info for a file?
 ************************************************************************/
short HSetCatInfo(short vRef,long inDirId,UPtr name,CInfoPBRec *hfi)
{
	hfi->hFileInfo.ioCompletion=nil;
	hfi->hFileInfo.ioNamePtr=name;
	hfi->hFileInfo.ioVRefNum=vRef;
	hfi->hFileInfo.ioDirID=inDirId;
	if (!name || !*name)
	{
		hfi->hFileInfo.ioFDirIndex = -1;
		hfi->dirInfo.ioDrDirID = inDirId;
	}
	else
		hfi->hFileInfo.ioFDirIndex=0;
	return(PBSetCatInfoSync((CInfoPBPtr)hfi));
}

/**********************************************************************
 * ExtractCreatorFromBndl - figure out what an app's creator used to be
 **********************************************************************/
OSErr ExtractCreatorFromBndl(FSSpecPtr spec,OSType *creator)
{
	OSErr err;
	short refN;
	Handle bndl;
	short oldResF = CurResFile();
	
	if (-1!=(refN=FSpOpenResFile(spec,fsRdPerm)))
	{
		if (bndl = GetIndResource('BNDL',1))
		{
			*creator = *(long*)*bndl;
			err = noErr;
		}
		else err = resNotFound;
		CloseResFile(refN);
		UseResFile (oldResF);
	}
	else err = ResError();
	return(err);
}

/************************************************************************
 * AFSpGetCatInfo - cat info, resolving aliases
 ************************************************************************/
short AFSpGetCatInfo(FSSpecPtr spec,FSSpecPtr newSpec,CInfoPBRec *hfi)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(HGetCatInfo(newSpec->vRefNum,newSpec->parID,newSpec->name,hfi));
}

/************************************************************************
 * FolderFileCount - count the files in a folder
 ************************************************************************/
short FolderFileCount(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	FSSpec newSpec;
	
	if (AFSpGetCatInfo(spec,&newSpec,&hfi)) return(-1);
	return(hfi.hFileInfo.ioFlStBlk);
}

/**********************************************************************
 * RemoveDir - remove a directory
 **********************************************************************/
OSErr RemoveDir(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	Str255 name;
	FSSpec die;
	OSErr err = noErr;
	long dirId;
	FSSpec folder;
	
	folder = *spec;
	IsAlias(&folder,&folder);
	dirId = SpecDirId(&folder);
	Zero(hfi);
	hfi.hFileInfo.ioNamePtr = name;
	
	while (!err)
	{
		hfi.hFileInfo.ioFDirIndex=1;
		hfi.hFileInfo.ioDirID = dirId;
		hfi.hFileInfo.ioVRefNum = folder.vRefNum;
		if (!(err=PBGetCatInfoSync((CInfoPBPtr)&hfi)))
		if (!(err=FSMakeFSSpec(folder.vRefNum,dirId,name,&die)))
			err = FSpDelete(&die);
	}
	return(ChainDelete(spec));
}

/************************************************************************
 * ChainDelete - delete an entire alias chain
 ************************************************************************/
OSErr ChainDelete(FSSpecPtr spec)
{
	FSSpec chain;
	Boolean wasAlias, isFolder;
	
	chain = *spec;
	if (!ResolveAliasFile(&chain,False,&isFolder,&wasAlias) && wasAlias)
		ChainDelete(&chain);
	return(FSpDelete(spec));
}

/************************************************************************
 * Move - Move a file or directory
 ************************************************************************/
short HMove(short vRef,long fromDirId,UPtr fromName,long toDirId,UPtr toName)
{
	CMovePBRec pb;
	
	pb.ioNamePtr = fromName;
	pb.ioDirID = fromDirId;
	pb.ioNewDirID = toDirId;
	pb.ioNewName = toName;
	pb.ioVRefNum = vRef;
				
	return(PBCatMoveSync((CMovePBPtr)&pb));
}


/************************************************************************
 * AHGetFileInfo - get info on a file
 ************************************************************************/
short AHGetFileInfo(short vRef,long dirId,UPtr name,CInfoPBRec *hfi)
{
	short err;
	Str255 newName;
	
	PCopy(newName,name);
	if (err=MyResolveAlias(&vRef,&dirId,newName,nil)) return(err);
	WriteZero(hfi,sizeof(*hfi));
	FILL(hfi->hFileInfo,newName,vRef,dirId);
	return(PBHGetFInfoSync((HParmBlkPtr)hfi));
}

#pragma segment FileUtil2

/************************************************************************
 * FSpGetHFileInfo - get info, don't resolve alias
 ************************************************************************/
OSErr FSpGetHFileInfo(FSSpecPtr spec,CInfoPBRec *hfi)
{
	Str63 name;
	PCopy(name,spec->name);
	Zero(*hfi);
	FILL(hfi->hFileInfo,name,spec->vRefNum,spec->parID);
	return(PBGetCatInfoSync(hfi));
}

short AHSetFileInfo(short vRef,long dirId,UPtr name,CInfoPBRec *hfi)
{
	short err;
	Str255 newName;
	
	PCopy(newName,name);
	if (err=MyResolveAlias(&vRef,&dirId,newName,nil)) return(err);
	FILL(hfi->hFileInfo,newName,vRef,dirId);
	return(PBHSetFInfoSync((HParmBlkPtr)hfi));
}

/************************************************************************
 * I am indebted to Tim Maroney (tim@toad.com) for the following routines.
 ************************************************************************/
static Boolean good, noSys, needWrite, allowFloppy, allowDesktop;

pascal Boolean FolderFilter(FileParam *pb)
{
#pragma unused(pb)
				return true;
}

pascal short FolderItems(short item,DialogPtr dlog)
{
#pragma unused(dlog)

				if (item == 2) {
								good = true;
								item = 3;
				}
				return item;
}

Boolean
GetFolder(char *name,short *volume,long *folder,Boolean writeable,Boolean system,Boolean floppy,Boolean desktop)
{
	Boolean	results;
	
	results = GetFolderNav (name, volume, folder);
	return (results);
}

/************************************************************************
 * CopyFork - copy the resource fork from one file to another
 ************************************************************************/
short CopyFork(short vRef,long dirId,UPtr name,short fromVRef,
								long fromDirId,Uptr fromName,Boolean rFork,Boolean progress)
{
	short err;
	short fromRef,toRef;
	Handle buffer=NuHTempOK(OPTIMAL_BUFFER);
	long bSize=OPTIMAL_BUFFER;
	long eof=0;
	
	if (!buffer) return(MemError());
	LDRef(buffer);
	if (!(err=(rFork?ARFHOpen:AFSHOpen)(fromName,fromVRef,fromDirId,&fromRef,fsRdPerm)))
	{
		if (!(err=(rFork?ARFHOpen:AFSHOpen)(name,vRef,dirId,&toRef,fsRdWrPerm)))
		{
			GetEOF(fromRef,&eof);
			for (bSize=MIN(OPTIMAL_BUFFER,eof);!err&&eof;bSize=MIN(OPTIMAL_BUFFER,eof))
			{
				if (!(err=ARead(fromRef,&bSize,(UPtr)*buffer)))
				{
					if (progress) ByteProgress(nil,-1,bSize);
					eof -= bSize;
					err = AWrite(toRef,&bSize,(UPtr)*buffer);
					if (progress) ByteProgress(nil,-1,bSize);
				}
			}
			TruncAtMark(toRef);
			MyFSClose(toRef);
		}
		MyFSClose(fromRef);
	}
	ZapHandle(buffer);
	return(err);	
}

/**********************************************************************
 * FSpDupFile - duplicate a file
 **********************************************************************/
OSErr FSpDupFile(FSSpecPtr to,FSSpecPtr from,Boolean replace,Boolean progress)
{
	OSErr err;
	Boolean hasRFork = FSpRFSize(from)!=0;
#ifdef DEBUG
	Str255 s;
#endif
	
	if (hasRFork)
	{
		FSpCreateResFile(to,'----','----',smSystemScript);
		err = ResError();
	}
	else 
		err =	FSpCreate(to,'----','----',smSystemScript);
	
	if (err && err==dupFNErr && replace) err = noErr;
#ifdef DEBUG
	if (err)
	{
		ComposeString(s,"\pFSpDupFile: create failed %d.%d.%p; %d",to->vRefNum,to->parID,to->name,err);
		AlertStr(OK_ALRT,Note,s);
	}
#endif
	if (err) return(err);
	
	if (progress) ByteProgress(nil,0,2*(FSpDFSize(to)+FSpRFSize(to)));
	if (!hasRFork || !(err=FSpCopyRFork(to,from,progress)))
	{
		
		if (!(err=FSpCopyDFork(to,from,progress)))
		{
			if (!progress)
				MiniEvents();
			else
				Progress(100,0,nil,nil,nil);
			if (!(err=FSpCopyFInfo(to,from)))
				return(noErr);
		}
#ifdef DEBUG
		else
		{
			ComposeString(s,"\pFSpDupFile: dfork failed %d.%d.%p->%d.%d.%p; %d",from->vRefNum,from->parID,from->name,to->vRefNum,to->parID,to->name,err);
			AlertStr(OK_ALRT,Note,s);
		}
#endif
	}
#ifdef DEBUG
	else if (hasRFork)
	{
		ComposeString(s,"\pFSpDupFile: rfork failed %d.%d.%p->%d.%d.%p; %d",from->vRefNum,from->parID,from->name,to->vRefNum,to->parID,to->name,err);
		AlertStr(OK_ALRT,Note,s);
	}
#endif
	
		
		
	
	FSpDelete(to);
	return err;
}

/**********************************************************************
 * FSpDupFolder - duplicate a folder
 **********************************************************************/
OSErr FSpDupFolder(FSSpecPtr toSpec,FSSpecPtr fromSpec,Boolean replace,Boolean progress)
{
	CInfoPBRec hfi;
	FSSpec	to,from;
	OSErr	err = noErr;
	
	to = *toSpec;
	from = *fromSpec;
	hfi.hFileInfo.ioNamePtr = from.name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!err && !DirIterate(from.vRefNum,from.parID,&hfi))
	{
		PCopy(to.name,from.name);
		if ((hfi.hFileInfo.ioFlAttrib & ioDirMask))
		{
			//	copy folder
			long	saveFrom,saveTo,createdDirID;
			
			//	save current parID's so we can reuse specs
			//	point specs to the folder
			if (!FSpDirCreate(&to,smSystemScript,&createdDirID))
			{
				saveFrom = from.parID; from.parID = SpecDirId(&from);
				saveTo = to.parID; to.parID = createdDirID;
				//	recurse
				err = FSpDupFolder(&to,&from,replace,progress);
				//	restore the parID's
				from.parID = saveFrom;
				to.parID = saveTo;
			}
		}
		else
		{
			//	copy file
			FSpDupFile(&to,&from,replace,progress);
		}
	}
	return err;
}

/************************************************************************
 * CopyFInfo - copy the file info from one file to another
 ************************************************************************/
short CopyFInfo(short vRef,long dirId,UPtr name,short fromVRef,
								long fromDirId,Uptr fromName)
{
	short err;
	CInfoPBRec hfi;
	
	if (!(err=HGetCatInfo(fromVRef,fromDirId,fromName,&hfi)))
	{
		hfi.hFileInfo.ioFlFndrInfo.fdFlags &= ~fInited;	// make the finder move it someplace rational
		Zero(hfi.hFileInfo.ioFlFndrInfo.fdLocation);
		err = HSetCatInfo(vRef,dirId,name,&hfi);
	}
	return(err);
}

/************************************************************************
 * MyResolveAlias - resolve an alias
 ************************************************************************/
short MyResolveAlias(short *vRef,long *dirId,UPtr name,Boolean *wasAlias)
{
	FSSpec theSpec;
	Boolean folder;
	long haveAlias;
	short err=noErr;
	Boolean wasIt;
	
	if (wasAlias) *wasAlias = False;
	if (!Gestalt(gestaltAliasMgrAttr,&haveAlias) && haveAlias&0x1)
	{
		if (!(err=FSMakeFSSpec(*vRef,*dirId,name,&theSpec)) &&
				!(err=ResolveAliasFile(&theSpec,True,&folder,&wasIt)))
		{
			if (wasIt)
			{
				*vRef = theSpec.vRefNum;
				*dirId = theSpec.parID;
				PCopy(name,theSpec.name);
				name[*name+1] = 0;
				if (wasAlias) *wasAlias = True;
			}
		}
	}
	return(err);
}

/**********************************************************************
 * SimpleResolveAlias - resolve an alias without all the fuss
 **********************************************************************/
OSErr SimpleResolveAlias(AliasHandle alias,FSSpecPtr spec)
{
	Boolean junk;
	FSSpec localSpec;
	
	if (!spec) spec = &localSpec;	// allow the caller to pass nil
	Zero(*spec);
	return(ResolveAlias(nil,alias,spec,&junk));
}

/**********************************************************************
 * SimpleResolveAliasNoUI - resolve an alias without bugging the user
 **********************************************************************/
OSErr SimpleResolveAliasNoUI(AliasHandle alias,FSSpecPtr spec)
{
	Boolean junk;
	FSSpec localSpec;
	short justOne = 1;
	
	if (!spec) spec = &localSpec;	// allow the caller to pass nil
	
	Zero(*spec);
	return(MatchAlias(nil,kARMMountVol|kARMNoUI|kARMMultVols|kARMSearch,alias,&justOne,spec,&junk,nil,nil));
}

/************************************************************************
 * ExchangeAndDel - exchange two files, deleting one
 ************************************************************************/
OSErr ExchangeAndDel(FSSpecPtr tmpSpec,FSSpecPtr spec)
{
	short err;
	
	if (err=ExchangeFiles(tmpSpec,spec))
	{
		FileSystemError(TEXT_WRITE,tmpSpec->name,err);
		return(err);
	}
	FSpDelete(tmpSpec);
	return(noErr);
}

/************************************************************************
 * ExchangeFiles - FSpExchangeFiles, with support for dopey AFP servers
 ************************************************************************/
OSErr ExchangeFiles(FSSpecPtr tmpSpec,FSSpecPtr spec)
{
	OSErr err = FSpExchangeFiles(tmpSpec,spec);

	if (err) err = FSpExchangeFilesCompat(tmpSpec,spec);
	return(err);
}

/************************************************************************
 * FSpExchangeFilesCompat - do FSpExchangeFiles if FSpExchangeFiles not supported
 *		from MoreFiles
 ************************************************************************/
OSErr FSpExchangeFilesCompat(const FSSpec *source, const FSSpec *dest)
{
	HParamBlockRec			pb;
	CInfoPBRec				catInfoSource, catInfoDest;
	OSErr					result;
	Str31					unique1, unique2;
	StringPtr				unique1Ptr, unique2Ptr, swapola;
#ifdef YOU_CARE_MORE_ABOUT_FILEIDS_THAN_FUNCTIONING
	OSErr					result2;
	GetVolParmsInfoBuffer	volInfo;
#endif
	long					theSeed, temp;
	
	/* Make sure the source and destination are on the same volume */
	if ( source->vRefNum != dest->vRefNum )
	{
		result = diffVolErr;
		goto errorExit3;
	}
	
	/* Try PBExchangeFiles first since it preserves the file ID reference */
	pb.fidParam.ioNamePtr = (StringPtr) &(source->name);
	pb.fidParam.ioVRefNum = source->vRefNum;
	pb.fidParam.ioDestNamePtr = (StringPtr) &(dest->name);
	pb.fidParam.ioDestDirID = dest->parID;
	pb.fidParam.ioSrcDirID = source->parID;

	result = PBExchangeFilesSync(&pb);

	/* Note: The compatibility case won't work for files with *Btree control blocks. */
	/* Right now the only *Btree files are created by the system. */
	if ( result != noErr )
	{
		// this code bails if fileid's are supported, because
		// the fileid can't be preserved
		// well, folks, I don't care!  SD 4/13/03
#ifdef YOU_CARE_MORE_ABOUT_FILEIDS_THAN_FUNCTIONING
		pb.ioParam.ioNamePtr = NULL;
		pb.ioParam.ioBuffer = (Ptr) &volInfo;
		pb.ioParam.ioReqCount = sizeof(volInfo);
		result2 = PBHGetVolParmsSync(&pb);
		
		/* continue if volume has no fileID support (or no GetVolParms support) */
		if ( (result2 == noErr) && HasFileIDs(&volInfo) )
		{
			goto errorExit3;
		}
#endif

		/* Get the catalog information for each file */
		/* and make sure both files are *really* files */
		catInfoSource.hFileInfo.ioVRefNum = source->vRefNum;
		catInfoSource.hFileInfo.ioFDirIndex = 0;
		catInfoSource.hFileInfo.ioNamePtr = (StringPtr) &(source->name);
		catInfoSource.hFileInfo.ioDirID = source->parID;
		catInfoSource.hFileInfo.ioACUser = 0; /* ioACUser used to be filler2 */
		result = PBGetCatInfoSync(&catInfoSource);
		if ( result != noErr )
		{
			goto errorExit3;
		}
		if ( (catInfoSource.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
		{
			result = notAFileErr;
			goto errorExit3;
		}
		
		catInfoDest.hFileInfo.ioVRefNum = dest->vRefNum;
		catInfoDest.hFileInfo.ioFDirIndex = 0;
		catInfoDest.hFileInfo.ioNamePtr = (StringPtr) &(dest->name);
		catInfoDest.hFileInfo.ioDirID = dest->parID;
		catInfoDest.hFileInfo.ioACUser = 0; /* ioACUser used to be filler2 */
		result = PBGetCatInfoSync(&catInfoDest);
		if ( result != noErr )
		{
			goto errorExit3;
		}
		if ( (catInfoDest.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
		{
			result = notAFileErr;
			goto errorExit3;
		}
		
		/* generate 2 filenames that are unique in both directories */
		theSeed = 0x64666A6C;	/* a fine unlikely filename */
		unique1Ptr = (StringPtr)&unique1;
		unique2Ptr = (StringPtr)&unique2;
		
		result = GenerateUniqueName(source->vRefNum, &theSeed, source->parID, dest->parID, unique1Ptr);
		if ( result != noErr )
		{
			goto errorExit3;
		}

		GenerateUniqueName(source->vRefNum, &theSeed, source->parID, dest->parID, unique2Ptr);
		if ( result != noErr )
		{
			goto errorExit3;
		}

		/* rename source to unique1 */
		pb.fileParam.ioNamePtr = (StringPtr) &(source->name);
		pb.ioParam.ioMisc = (Ptr) unique1Ptr;
		pb.ioParam.ioVersNum = 0;
		result = PBHRenameSync(&pb);
		if ( result != noErr )
		{
			goto errorExit3;
		}
		
		/* rename dest to unique2 */
		pb.ioParam.ioMisc = (Ptr) unique2Ptr;
		pb.ioParam.ioVersNum = 0;
		pb.fileParam.ioNamePtr = (StringPtr) &(dest->name);
		pb.fileParam.ioDirID = dest->parID;
		result = PBHRenameSync(&pb);
		if ( result != noErr )
		{
			goto errorExit2;	/* back out gracefully by renaming unique1 back to source */
		}
			
		/* If files are not in same directory, swap their locations */
		if ( source->parID != dest->parID )
		{
			/* move source file to dest directory */
			pb.copyParam.ioNamePtr = unique1Ptr;
			pb.copyParam.ioNewName = NULL;
			pb.copyParam.ioNewDirID = dest->parID;
			pb.copyParam.ioDirID = source->parID;
			result = PBCatMoveSync((CMovePBPtr) &pb);
			if ( result != noErr )
			{
				goto errorExit1;	/* back out gracefully by renaming both files to original names */
			}
			
			/* move dest file to source directory */
			pb.copyParam.ioNamePtr = unique2Ptr;
			pb.copyParam.ioNewDirID = source->parID;
			pb.copyParam.ioDirID = dest->parID;
			result = PBCatMoveSync((CMovePBPtr) &pb);
			if ( result != noErr)
			{
				/* life is very bad.  We'll at least try to move source back */
				pb.copyParam.ioNamePtr = unique1Ptr;
				pb.copyParam.ioNewName = NULL;
				pb.copyParam.ioNewDirID = source->parID;
				pb.copyParam.ioDirID = dest->parID;
				(void) PBCatMoveSync((CMovePBPtr) &pb);	/* ignore errors */
				goto errorExit1;	/* back out gracefully by renaming both files to original names */
			}
		}
		
		/* Make unique1Ptr point to file in source->parID */
		/* and unique2Ptr point to file in dest->parID */
		/* This lets us fall through to the rename code below */
		swapola = unique1Ptr;
		unique1Ptr = unique2Ptr;
		unique2Ptr = swapola;

		/* At this point, the files are in their new locations (if they were moved) */
		/* Source is named Unique1 (name pointed to by unique2Ptr) and is in dest->parID */
		/* Dest is named Unique2 (name pointed to by unique1Ptr) and is in source->parID */
		/* Need to swap attributes except mod date and swap names */

		/* swap the catalog info by re-aiming the CInfoPB's */
		catInfoSource.hFileInfo.ioNamePtr = unique1Ptr;
		catInfoDest.hFileInfo.ioNamePtr = unique2Ptr;
		
		catInfoSource.hFileInfo.ioDirID = source->parID;
		catInfoDest.hFileInfo.ioDirID = dest->parID;
		
		/* Swap the original mod dates with each file */
		temp = catInfoSource.hFileInfo.ioFlMdDat;
		catInfoSource.hFileInfo.ioFlMdDat = catInfoDest.hFileInfo.ioFlMdDat;
		catInfoDest.hFileInfo.ioFlMdDat = temp;
		
		/* Here's the swap (ignore errors) */
		(void) PBSetCatInfoSync(&catInfoSource); 
		(void) PBSetCatInfoSync(&catInfoDest);
		
		/* rename unique2 back to dest */
errorExit1:
		pb.ioParam.ioMisc = (Ptr) &(dest->name);
		pb.ioParam.ioVersNum = 0;
		pb.fileParam.ioNamePtr = unique2Ptr;
		pb.fileParam.ioDirID = dest->parID;
		(void) PBHRenameSync(&pb);	/* ignore errors */

		/* rename unique1 back to source */
errorExit2:
		pb.ioParam.ioMisc = (Ptr) &(source->name);
		pb.ioParam.ioVersNum = 0;
		pb.fileParam.ioNamePtr = unique1Ptr;
		pb.fileParam.ioDirID = source->parID;
		(void) PBHRenameSync(&pb); /* ignore errors */
	}
errorExit3: { /* null statement */ }
	return ( result );
}

/**********************************************************************
 * HasFileIDs - does volume support file ID's?
 **********************************************************************/
Boolean	HasFileIDs(const GetVolParmsInfoBuffer *volParms)
{
	return ( (volParms->vMAttrib & (1L << bHasFileIDs)) != 0 );
}

/************************************************************************
 * GenerateUniqueName - generates a name that is unique in both dir1 and dir2
 ************************************************************************/
static OSErr GenerateUniqueName(short volume,long *startSeed,long dir1,long dir2,StringPtr uniqueName)
{
	OSErr			error = noErr;
	long			i;
	CInfoPBRec		cinfo;
	unsigned char	hexStr[16];
	
	for ( i = 0; i < 16; ++i )
	{
		if ( i < 10 )
		{
			hexStr[i] = 0x30 + i;
		}
		else
		{
			hexStr[i] = 0x37 + i;
		}
	}
	
	cinfo.hFileInfo.ioVRefNum = volume;
	cinfo.hFileInfo.ioFDirIndex = 0;
	cinfo.hFileInfo.ioNamePtr = uniqueName;

	while ( error != fnfErr )
	{
		(*startSeed)++;		
		cinfo.hFileInfo.ioNamePtr[0] = 8;
		for ( i = 1; i <= 8; i++ )
		{
			cinfo.hFileInfo.ioNamePtr[i] = hexStr[((*startSeed >> ((8-i)*4)) & 0xf)];
		}
		cinfo.hFileInfo.ioDirID = dir1;
		error = fnfErr;
		for ( i = 1; i <= 2; i++ )
		{
			error = error & PBGetCatInfoSync(&cinfo);
			cinfo.hFileInfo.ioDirID = dir2;
			if ( (error != fnfErr) && (error != noErr) )
			{
				return ( error );
			}
		}
	}
	return ( noErr );
}

/**********************************************************************
 * MorphDesktop - if a spec points to the boot disk desktop and the
 *  requested volume is not the desktop, then set the spec to the requested
 *  volume's desktop
 **********************************************************************/
OSErr MorphDesktop(short vRef,FSSpecPtr where)
{
	FSSpec desk;
	OSErr err;
	
	if (*where->name) return(noErr);	// not pointing to folder
	if (SameVRef(vRef,where->vRefNum)) return(noErr);	// same volume
	if (err=FindFolder(kOnSystemDisk,kDesktopFolderType,False,&desk.vRefNum,&desk.parID)) return(err);
	if (SameVRef(vRef,desk.vRefNum)) return(noErr);	// same volume as system
	if (SameVRef(where->vRefNum,desk.vRefNum) && desk.parID==where->parID)
	{
		// ok, spec points to desktop folder
		// point instead to desktop folder on volume
		err=FindFolder(vRef,kDesktopFolderType,False,&where->vRefNum,&where->parID);
	}
	return(err);
}

/************************************************************************
 * AFSpIsItAFolder - is a file a folder?
 ************************************************************************/
Boolean AFSpIsItAFolder(FSSpecPtr spec)
{
	FInfo info;
	
	FSpGetFInfo(spec,&info);
	if ((info.fdFlags & kIsAlias) && info.fdType==kContainerFolderAliasType)
		return(True);
	return(FSpIsItAFolder(spec));
}

/************************************************************************
 * FSWriteP - write a Pascal string
 ************************************************************************/
short FSWriteP(short refN,UPtr pString)
{
	long count = *pString;
	return(AWrite(refN,&count,pString+1));
}

/************************************************************************
 * GetFileByRef - figure out the name & vol of a file from an open file
 ************************************************************************/
short GetFileByRef(short refN,FSSpecPtr specPtr)
{
	FCBPBRec fcb;
	short err;
	Str63 name;
	
	fcb.ioCompletion = nil;
	fcb.ioVRefNum = nil;
	fcb.ioRefNum = refN;
	fcb.ioFCBIndx = 0;
	fcb.ioNamePtr = name;
	if (err=PBGetFCBInfo(&fcb,False)) return(err);
	return(FSMakeFSSpec(fcb.ioFCBVRefNum,fcb.ioFCBParID,name,specPtr));
}

/************************************************************************
 * VolumeFree - return the free space on a volume
 ************************************************************************/
long VolumeFree(short vRef)
{
	HParamBlockRec pb;
	
	pb.volumeParam.ioNamePtr = nil;
	pb.volumeParam.ioVolIndex = 0;
	pb.volumeParam.ioVRefNum = vRef;
	if (PBHGetVInfoSync((HParmBlkPtr)&pb)) return(0);
	return(pb.volumeParam.ioVFrBlk*pb.volumeParam.ioVAlBlkSiz);
}

/************************************************************************
 * FSTabWrite - write, expanding tabs
 ************************************************************************/
short FSTabWrite(short refN,long *count,UPtr buf)
{
	UPtr p;
	UPtr end = buf+*count;
	long written=0;
	short err=noErr;
	long writing;
	static short charsOnLine=0;
	UPtr nl;
	short stops=0;
	
	if (!FakeTabs) return(FSZWrite(refN,count,buf));
	for (p=buf;p<end;p=buf=p+1)
	{
		nl = buf-charsOnLine-1;
		while (p<end && *p!=tabChar)
		{
			if (*p=='\015') nl=p;
			p++;
		}
		writing = p-buf;
		err=FSZWrite(refN,&writing,buf);
		written += writing;
		if (err) break;
		charsOnLine = p-nl-1;
		if (p<end)
		{
			if (!stops) stops = GetRLong(TAB_DISTANCE);
			writing = stops-(charsOnLine)%stops;
			charsOnLine = 0;
			err = FSZWrite(refN,&writing, (UPtr) "              ");
			written += writing;
			if (err) break;
		}
	}
	*count = written;
	return(err);
}

/**********************************************************************
 * AWrite - write async
 **********************************************************************/
short ARead(short refN,long *count,UPtr buf)
{
	OSErr err;
	ParamBlockRec pb;
	
#ifdef TWO
	if (!SyncRW)
	{
		Zero(pb);
		pb.ioParam.ioRefNum = refN;
		pb.ioParam.ioBuffer = (char *) buf;
		pb.ioParam.ioReqCount = *count;
		*count = 0;
		
#ifdef NEVER
		if (VM) HoldMemory(&pb,sizeof(pb));
		if (VM) HoldMemory(buf,*count);
#endif
		PBReadAsync(&pb);
		// (jp) 'ioResult' is now volatile in Universal Headers 3.4
		err = SpinOn(&pb.ioParam.ioResult,0,False,True);
		*count = pb.ioParam.ioActCount;
#ifdef NEVER
		if (VM) UnholdMemory(&pb,sizeof(pb));
		if (VM) UnholdMemory(buf,*count);
#endif
	}
	else
#endif
		err = FSRead(refN,count,buf);

	return(err);
}

/************************************************************************
 * NCWriteP - write a Pascal string
 ************************************************************************/
short NCWriteP(short refN,UPtr pString)
{
	long count = *pString;
	return(NCWrite(refN,&count,pString+1));
}

/************************************************************************
 * AWriteP - write a Pascal string
 ************************************************************************/
short AWriteP(short refN,UPtr pString)
{
	long count = *pString;
	return(AWrite(refN,&count,pString+1));
}

/**********************************************************************
 * AWrite - write async
 **********************************************************************/
short AWrite(short refN,long *count,UPtr buf)
{
	OSErr err;
	ParamBlockRec pb;
	
#ifdef TWO
	if (!*count) return(noErr);
	if (!SyncRW)
	{
		Zero(pb);
		pb.ioParam.ioRefNum = refN;
		pb.ioParam.ioBuffer = (char *) buf;
		pb.ioParam.ioReqCount = *count;
		
#ifdef NEVER
		if (VM) HoldMemory(&pb,sizeof(pb));
		if (VM) HoldMemory(buf,*count);
#endif
		PBWriteAsync(&pb);
		err = SpinOn(&pb.ioParam.ioResult,0,False,True);
		*count = pb.ioParam.ioActCount;
#ifdef NEVER
		if (VM) UnholdMemory(&pb,sizeof(pb));
		if (VM) UnholdMemory(buf,*count);
#endif
	}
	else
#endif
		err = FSWrite(refN,count,buf);
	
	return(err);
}

/**********************************************************************
 * WipeSpec - wipe a file
 **********************************************************************/
OSErr WipeSpec(FSSpecPtr spec)
{
	short refN;
	long eof;
	OSErr err;
	
	if (!(err=FSpOpenDF(spec,fsRdWrPerm,&refN)))
	{
		if (!(err=GetEOF(refN,&eof)))
			err=WipeDiskArea(refN,0,eof);
		MyFSClose(refN);
		if (!(err=FSpOpenRF(spec,fsRdWrPerm,&refN)))
		{
			if (!(err=GetEOF(refN,&eof)))
				err=WipeDiskArea(refN,0,eof);
			MyFSClose(refN);
		}
	}
	if (!err)
	{
		FlushVol(nil,spec->vRefNum);
		err = FSpDelete(spec);
	}
	
	if (err && err!=fnfErr) FileSystemError(WIPE_ERROR,spec->name,err);
	
	return(err);
}

/**********************************************************************
 * WipeDiskArea - wipe part of a disk
 **********************************************************************/
OSErr WipeDiskArea(short refN,long offset, long len)
{
	long bSize = MIN(len,OPTIMAL_BUFFER);
	UHandle h;
	long size;
	UPtr spot,end;
	OSErr err;
	
	if (!bSize) return(noErr);
	h = (UHandle) NuHTempBetter(bSize);
	if (!h) return(MemError());
	
	/*
	 * fill it with returns
	 */
	end = LDRef(h) + bSize;
	for (spot=*h;spot<end;spot++) *spot = ' ';
	spot[-1] = '\015';
	for (spot=*h;spot<end;spot+=50) *spot = '\015';
	
	/*
	 * blat it over the disk area
	 */
	if (!(err=SetFPos(refN,fsFromStart,offset)))
		for (size=bSize;len;size=MIN(bSize,len))
		{
			err = NCWrite(refN,&size,*h);
			if (err) break;
			len -= size;
		}
	
	ZapHandle(h);
	return(err);
}


/**********************************************************************
 * NCWrite - Write, avoiding the cache
 **********************************************************************/
short NCWrite(short refN,long *count,UPtr buf)
{
	OSErr err;
	ParamBlockRec pb;
	
	if (SyncRW) return(FSWrite(refN,count,buf));
	Zero(pb);
	pb.ioParam.ioRefNum = refN;
	pb.ioParam.ioBuffer = (char *) buf;
	pb.ioParam.ioReqCount = *count;
	pb.ioParam.ioPosMode = 1<<5;	/* no cache */
	
#ifdef NEVER
	if (VM) HoldMemory(&pb,sizeof(pb));
	if (VM) HoldMemory(buf,*count);
#endif
	PBWriteAsync(&pb);
	err = SpinOn(&pb.ioParam.ioResult,0,False,True);
	*count = pb.ioParam.ioActCount;
#ifdef NEVER
	if (VM) UnholdMemory(&pb,sizeof(pb));
	if (VM) UnholdMemory(buf,*count);
#endif
	return(err);
}


/************************************************************************
 * EnsureNewline - make sure there is a newline at or just before the
 * current file position.
 ************************************************************************/
OSErr EnsureNewline(short refN)
{
	Str15 chars;
	long offset,count;
	short err;
	
	/*
	 * where are we?
	 */
	if (err=GetFPos(refN,&offset)) return(err);
	
	/*
	 * BOF counts as newline
	 */
	if (!offset) return(noErr);
	
	/*
	 * back up one character
	 */
	if (err=SetFPos(refN,fsFromStart,offset-1)) return(err);
	
	/*
	 * read it
	 */
	count = 1;
	if (err=ARead(refN,&count,chars)) return(err);
	
	/*
	 * is newline?
	 */
	if (*chars == '\015') return(noErr);
	
	/*
	 * make it so
	 */
	return(FSWriteP(refN,Cr));
}

/************************************************************************
 * AFSpOpenDF - OpenDF, but resolve the alias first
 ************************************************************************/
OSErr AFSpOpenDF (FSSpecPtr spec,FSSpecPtr newSpec, SignedByte permission, short *refNum)
{
	OSErr err;
	Boolean folder, wasIt;
	short localRef;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	err = FSpOpenDF(newSpec,permission,&localRef);
	if (!err) *refNum = localRef;
	return err;
}


/************************************************************************
 * AFSpOpenRF
 ************************************************************************/
OSErr AFSpOpenRF (FSSpecPtr spec,FSSpecPtr newSpec, SignedByte permission, short *refNum)
{
	OSErr err;
	Boolean folder, wasIt;
	short localRef;
	
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	err = FSpOpenRF(newSpec,permission,&localRef);
	if (!err) *refNum = localRef;
	return err;
}

/************************************************************************
 * AFSpDelete
 ************************************************************************/
OSErr AFSpDelete(FSSpecPtr spec,FSSpecPtr newSpec)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(FSpDelete(newSpec));
}

/************************************************************************
 * AFSpGetFInfo
 ************************************************************************/
OSErr AFSpGetFInfo (FSSpecPtr spec,FSSpecPtr newSpec, FInfo *fndrInfo)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(FSpGetFInfo (newSpec,fndrInfo));
}

/************************************************************************
 * FSpFileSize - get the size of a file
 ************************************************************************/
long FSpFileSize(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	
	if (!AFSpGetHFileInfo(spec,&hfi)) return(hfi.hFileInfo.ioFlLgLen+hfi.hFileInfo.ioFlRLgLen);
	else return(0);
}

/**********************************************************************
 * 
 **********************************************************************/
OSErr AFSpSetMod(FSSpecPtr spec,uLong mod)
{
	CInfoPBRec hfi;
	OSErr err;
	
	if (err=AFSpGetHFileInfo(spec,&hfi)) return(err);
	hfi.hFileInfo.ioFlMdDat = mod;
	return(AFSpSetHFileInfo(spec,&hfi));
}

/**********************************************************************
 * 
 **********************************************************************/
uLong AFSpGetMod(FSSpecPtr spec)
{
	CInfoPBRec hfi;

	if (!AFSpGetHFileInfo(spec,&hfi)) return(hfi.hFileInfo.ioFlMdDat);
	else return(0);
}

/************************************************************************
 * FSpDFSize - get the size of the data fork a file
 ************************************************************************/
long FSpDFSize(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	
	if (!AFSpGetHFileInfo(spec,&hfi)) return(hfi.hFileInfo.ioFlLgLen);
	else return(0);
}

/************************************************************************
 * FSpRFSize - get the size of the data fork a file
 ************************************************************************/
long FSpRFSize(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	
	if (!AFSpGetHFileInfo(spec,&hfi)) return(hfi.hFileInfo.ioFlRLgLen);
	else return(0);
}

/************************************************************************
 * FSpSetFXInfo - set the FXInfo for a file
 ************************************************************************/
OSErr FSpSetFXInfo(FSSpecPtr spec,FXInfo *fxInfo)
{
	OSErr err;
	CInfoPBRec hfi;
	
	if (!(err=AFSpGetHFileInfo(spec,&hfi)))
	{
		hfi.hFileInfo.ioFlXFndrInfo = *fxInfo;
		err = HSetCatInfo(spec->vRefNum,spec->parID,spec->name,&hfi);
	}
	return(err);
}


/************************************************************************
 * AFSpSetFInfo
 ************************************************************************/
OSErr AFSpSetFInfo (FSSpecPtr spec,FSSpecPtr newSpec, FInfo *fndrInfo)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(FSpSetFInfo (newSpec,fndrInfo));
}

/************************************************************************
 * AFSpSetFLock
 ************************************************************************/
OSErr AFSpSetFLock (FSSpecPtr spec,FSSpecPtr newSpec)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(FSpSetFLock(newSpec));
}

/************************************************************************
 * AFSpRstFLock
 ************************************************************************/
OSErr AFSpRstFLock (FSSpecPtr spec,FSSpecPtr newSpec)
{
	OSErr err;
	Boolean folder, wasIt;
	*newSpec = *spec;
	if (err=ResolveAliasFile(newSpec,True,&folder,&wasIt)) return(err);
	
	return(FSpRstFLock(newSpec));
}

/************************************************************************
 * IsAlias - is a file an alias?
 ************************************************************************/
Boolean IsAlias(FSSpecPtr spec,FSSpecPtr newSpec)
{
	Boolean folder, wasIt=false;
	*newSpec = *spec;
	ResolveAliasFile(newSpec,True,&folder,&wasIt);	// if error, wasIt will either
																									// be set correctly or else still
																									// be false
	return(wasIt);
}

/************************************************************************
 * IsAliasNoMount - is a file an alias (but don't mount it if so)?
 ************************************************************************/
Boolean IsAliasNoMount(FSSpecPtr spec,FSSpecPtr newSpec)
{
	Boolean isAlias=false;
	*newSpec = *spec;
	
	ResolveAliasNoMount(spec,newSpec,&isAlias);
	
	return(isAlias);
}

/************************************************************************
 * ResolveAliasOrElse - Resolve an alias or fail
 ************************************************************************/
OSErr ResolveAliasOrElse(FSSpecPtr spec,FSSpecPtr newSpec,Boolean *wasIt)
{
	Boolean folder, isAlias=false;
	OSErr err;
	FSSpec resolvedSpec = *spec;
	err = ResolveAliasFile(&resolvedSpec,true,&folder,&isAlias);	// if error, isAlias will either
																													// be set correctly or else still
																													// be false
	if (wasIt) *wasIt = isAlias;
	if (newSpec) *newSpec =  err ? *spec : resolvedSpec;	// if error, put original file there
	return(err);
}

/**********************************************************************
 * SubFolderSpec - get the FSSpec for the signature folder
 **********************************************************************/
OSErr SubFolderSpec(short nameId,FSSpecPtr spec)
{
	Str63 string;
	OSErr err;
	static StackHandle specStack;
	CSpec cSpec;
	short i;
	
	// clear cache?
	if (!spec)
	{
		if (specStack) (*specStack)->elCount = 0;
		return noErr;
	}
	
	// search for folder in cache
	if (specStack)
		for (i=0;i<(*specStack)->elCount;i++)
		{
			StackItem(&cSpec,i,specStack);
			if (cSpec.count == nameId)
			{
				*spec = cSpec.spec;
				return noErr;
			}
		}
	
	// not in cache.  Go look for it
	if (!(err=FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(string,nameId),spec)))
	{
		/*
		 * maybe the folder is an alias
		 */
		IsAlias(spec,spec);
		
		/*
		 * point inside the folder
		 */
		spec->parID = SpecDirId(spec);
		*spec->name = 0;
		
		/*
		 * cache it, now that we have it
		 */
		if (specStack || !StackInit(sizeof(CSpec),&specStack))
		{
			cSpec.spec = *spec;
			cSpec.count = nameId;
			StackPush(&cSpec,specStack);
		}
	}
	return(err);
}

/************************************************************************
 * FindSubFolderSpec - Find our sub folder of a specific system folder
 ************************************************************************/
OSErr FindSubFolderSpec(long domain,long folder,short subfolderID,Boolean create,FSSpecPtr spec)
{
	FSSpec localSpec;
	OSErr err = FindFolder(domain,folder,create,&localSpec.vRefNum,&localSpec.parID);
	
	if (!err)
		err = SubFolderSpecOf(&localSpec,subfolderID,create,spec);
	
	return err;
}

/************************************************************************
 * SubFolderSpecOf - find a subfolder of a given fsspec
 ************************************************************************/
OSErr SubFolderSpecOf(FSSpecPtr inSpec,short subfolderID,Boolean create,FSSpecPtr subSpec)
{
	Str63 subfolderName;
	
	GetRString(subfolderName,subfolderID);
	return SubFolderSpecOfStr(inSpec,subfolderName,create,subSpec);
}

OSErr SubFolderSpecOfStr(FSSpecPtr inSpec,PStr subfolderName,Boolean create,FSSpecPtr subSpec)
{
	FSSpec localSpec = *inSpec;
	long dirID;
	
	PCopy(localSpec.name,subfolderName);
	if (create) FSpDirCreate(&localSpec,smSystemScript,&dirID);
	
	IsAlias(&localSpec,&localSpec);
	localSpec.parID = SpecDirId(&localSpec);
	if (localSpec.parID==0) return fnfErr;
	if (subSpec)
	{
		*localSpec.name = 0;
		*subSpec = localSpec;
	}
	return noErr;
}

/************************************************************************
 * StuffFolderSpec - find the stuff folder
 ************************************************************************/
OSErr StuffFolderSpec(FSSpecPtr spec)
{
	FSSpec localSpec;
	Str31 name;
	OSErr err = GetFileByRef(AppResFile,&localSpec);
	
	if (!err) err = FSMakeFSSpec(localSpec.vRefNum,localSpec.parID,GetRString(name,STUFF_FOLDER),&localSpec);
	if (!err)
	{
		IsAlias(&localSpec,&localSpec);
		spec->vRefNum = localSpec.vRefNum;
		spec->parID = SpecDirId(&localSpec);
		*spec->name = 0;
	}
	return err;
}

/************************************************************************
 * SpecInSubfolderOf - is a spec in a folder or subfolder
 ************************************************************************/
Boolean SpecInSubfolderOf(FSSpecPtr att,FSSpecPtr folder)
{
	FSSpec parent = *att;
	
	for(;;)
	{
		if (SameVRef(parent.vRefNum,folder->vRefNum) && parent.parID==folder->parID)
			return(true);
		if (parent.parID==2) return(false);
		if (ParentSpec(&parent,&parent)) return(false);
		
	}
}




/************************************************************************
 * FSMakeFID - make a fileid for a spec
 ************************************************************************/
OSErr FSMakeFID(FSSpecPtr spec,long *fid)
{
	HParamBlockRec fidpb;
	short err;
	
	Zero(fidpb);

	fidpb.fidParam.ioCompletion = nil;
	fidpb.fidParam.ioNamePtr = spec->name;
	fidpb.fidParam.ioVRefNum = spec->vRefNum;
	fidpb.fidParam.ioSrcDirID = spec->parID;
	
	err = PBCreateFileIDRefSync((HParmBlkPtr)&fidpb);
	FileIDHack();
	if (err==fidExists || err==afpIDExists) err = noErr;	/* ignore; ioFileID is good */
	
	if (!err) *fid = fidpb.fidParam.ioFileID;
	return(err);
}

/************************************************************************
 * FileIDHack - hack to work around apple fileID bug.
 *	 JDB 980720 
 *
 *		There's a problem with OS 8.1 machines that create fileIDs for
 *	files saved onto standard HFS partitions.  If a PBSetFInfo is done
 *	too soon afterwards (like happens with SetMod later during attachment
 *	receiving), the fileID can become corrupt and the attachments can;t be
 *	located agan.
 *  SD 8/6/98 - The workaround is to GetCatInfo on a different file.
 ************************************************************************/
void FileIDHack(void)
{
	long sysVers = 0;
	long affected = 0;
	OSErr err = noErr;
	FSSpec spec;
	CInfoPBRec info;
	
	// get the system version of this machine
	err = Gestalt(gestaltSystemVersion, &sysVers);
	if ((err == noErr))
	{
		// is this system version affected by the bug?
		affected = GetRLong(FILEID_AFFECTED_SYSVERSION);
		if (affected == sysVers)
		{
			Zero(spec);
			GetFileByRef(SettingsRefN,&spec);
			AFSpGetCatInfo(&spec,&spec,&info);
		}
	}
}

/************************************************************************
 * FSResolveFID - resolve a vRef & fileid into a spec
 ************************************************************************/
OSErr FSResolveFID(short vRef,long fid,FSSpecPtr spec)
{
	HParamBlockRec fidpb;
	short err;
	Str63 name;

	Zero(fidpb);
	
	*name = 0;
	fidpb.fidParam.ioCompletion = nil;
	fidpb.fidParam.ioNamePtr = name;
	fidpb.fidParam.ioFileID = fid;
	fidpb.fidParam.ioVRefNum = vRef;
	
	err = PBResolveFileIDRefSync((HParmBlkPtr)&fidpb);
	
	if (!err) err = FSMakeFSSpec(vRef,fidpb.fidParam.ioSrcDirID,name,spec);
	
	return(err);
}

/************************************************************************
 * SpecMove - move a file from one place to another
 ************************************************************************/
OSErr SpecMove(FSSpecPtr moveMe,FSSpecPtr moveTo)
{
	FInfo info;
	
	if (!FSpGetFInfo(moveMe,&info))
	{
		info.fdFlags &= ~fInited;
		Zero(info.fdLocation);
		FSpSetFInfo(moveMe,&info);
	}
	return HMove(moveMe->vRefNum,moveMe->parID,moveMe->name,moveTo->parID,nil);
}

/************************************************************************
 * SpecMoveAndRename - move a file from one place to another, and rename
 ************************************************************************/
OSErr SpecMoveAndRename(FSSpecPtr moveMe,FSSpecPtr moveTo)
{
	OSErr err;
	
	if (err = SpecMove(moveMe,moveTo))
	  return err;
	
	if (err = HRename(moveTo->vRefNum,moveTo->parID,moveMe->name,moveTo->name))
	{
		OSErr undoErr = HMove(moveTo->vRefNum,moveTo->parID,moveMe->name,moveMe->parID,nil);
		ASSERT(!undoErr);	// this would be a bad place to error, as it
											// would mean we couldn't move the file back
	}
	
	return err;
}

/************************************************************************
 * DiskSpunUp - is the disk a'spinnin'?
 ************************************************************************/
Boolean DiskSpunUp(void)
{
	// They killed HardDiskPowered -- Those bxxxxxxs!!
	// Even worse, they made it return false when they killed it, not true
	// They should go to hell.  They should go to hell and they should die.
	// if (HardDiskPowered && !HardDiskPowered()) return false;
	return true;
}

/************************************************************************
 * GetTrashSpec - get an FSSpec describing the trash;
 ************************************************************************/
OSErr GetTrashSpec(short vRef,FSSpecPtr spec)
{
	spec->name[0] = 0;
	spec->vRefNum = vRef;
	return(FindFolder(vRef,kTrashFolderType,kCreateFolder,&vRef,&spec->parID));
}

/************************************************************************
 * DTRef - return the ref number for the desktop db
 ************************************************************************/
OSErr DTRef(short vRef, short *dtRef)
{
	DTPBRec pb;
	short err;
	
	pb.ioNamePtr = nil;
	pb.ioVRefNum = vRef;
	pb.ioCompletion = nil;
	
	if (err=PBDTGetPath(&pb)) return(err);
	
	*dtRef = pb.ioDTRefNum;
	return(pb.ioResult);
}

/************************************************************************
 * DTGetAppl - find an app for a creator
 ************************************************************************/
OSErr DTGetAppl(short vRef,short dtRef,OSType creator,FSSpecPtr appSpec)
{
	DTPBRec pb;
	short err;
	Str63 appName;
	
	pb.ioCompletion = nil;
	pb.ioNamePtr = appName;
	pb.ioDTRefNum = dtRef;
	pb.ioIndex = 0;
	pb.ioFileCreator = creator;
	
	if (err=PBDTGetAPPL(&pb,False)) return(err);
	if (pb.ioResult) return(pb.ioResult);
	
	return(FSMakeFSSpec(vRef,pb.ioAPPLParID,appName,appSpec));
}

/************************************************************************
 * DTFindAppl - find which dtRef an application lives in
 ************************************************************************/
short DTFindAppl(OSType creator)
{
	OSErr err;
	short volIndex;
	short vRef;
	short dtRef;
	FSSpec junk;
	
	for (volIndex=1;!IndexVRef(volIndex,&vRef);volIndex++)
		if (!(err=DTRef(vRef,&dtRef)))
			if (!(err=DTGetAppl(vRef,dtRef,creator,&junk)))
				return(dtRef);
	return(0);
}

/************************************************************************
 * DTSetComment - set the comment for an attachment
 ************************************************************************/
OSErr DTSetComment(FSSpecPtr spec,PStr comment)
{
	DTPBRec pb;
	short dtRef;
	OSErr err;
	
	if (!HaveTheDiseaseCalledOSX() && !(err=DTRef(spec->vRefNum,&dtRef)))
	{
		Zero(pb);
		pb.ioNamePtr = spec->name;
		pb.ioDTRefNum = dtRef;
		pb.ioDTBuffer = (char *) comment+1;
		pb.ioDTReqCount = MIN(200,*comment);
		pb.ioDirID = spec->parID;
		return(PBDTSetCommentSync(&pb));
	}
	return(err);
}

/************************************************************************
 * SameSpec - do two specs refer to same file?
 ************************************************************************/
Boolean SameSpec(FSSpecPtr sp1,FSSpecPtr sp2)
{
	return(sp1->parID==sp2->parID && SameVRef(sp1->vRefNum,sp2->vRefNum) &&
				 StringSame(sp1->name,sp2->name));
}

/************************************************************************
 * SpecDirId - find the dirId of the directory referenced by a spec 
 ************************************************************************/
long SpecDirId(FSSpecPtr spec)
{
	CInfoPBRec hfi;
	FSSpec newSpec;
	Str31 name;
	
	Zero(hfi);
	hfi.hFileInfo.ioNamePtr = name;
	AFSpGetCatInfo(spec,&newSpec,&hfi);
	return(hfi.hFileInfo.ioDirID);
}

/************************************************************************
 * CanWrite - can we write on a file?  Only one way to tell on a macintosh
 ************************************************************************/
OSErr CanWrite(FSSpecPtr spec, Boolean *can)
{
	FSSpec newSpec = *spec;
	short refN;
	Byte buff=13;
	long len;
	CInfoPBRec hfi;
	OSErr err;
	Boolean b;
	
	*can = False;
	if (!(err = ResolveAliasFile(&newSpec,True,&b,&b)) && 
			!(err=AFSpGetHFileInfo(&newSpec,&hfi)))
	if (!(err=FSpOpenDF(&newSpec,fsRdWrPerm,&refN)))
	{
		len = 1;
		if (!(err=SetFPos(refN,fsFromLEOF,0)))
			if (!FSWrite(refN,&len,&buff))
			{
				*can = True;
				SetFPos(refN,fsFromLEOF,-1);
				if (!GetFPos(refN,&len)) TruncOpenFile(refN,len);
			}
		MyFSClose(refN);
		AFSpSetHFileInfo(&newSpec,&hfi);	/* restore mod date */
	}
	else if ((err==permErr||err==afpAccessDenied) && !(err=FSpOpenDF(&newSpec,fsRdPerm,&refN)))
	{
		MyFSClose(refN);
		*can = False;
	}
	return(err);
}

#ifdef DEBUG
/**********************************************************************
 * MyFSClose - call FSClose
 **********************************************************************/
OSErr MyFSClose(short refN)
{
	if (!PrefIsSet(PREF_CORVAIR)) MakeDarnSure(refN);
	return(FSClose(refN));
}
#endif

/**********************************************************************
 * NewTempSpec - make a temp file spec
 **********************************************************************/
OSErr NewTempSpec(short vRef,long dirId,PStr name,FSSpecPtr spec)
{
	long tempId;
	OSErr err;
	Str31 fName;
	static unsigned char n;

	if (err = FindTemporaryFolder(vRef,dirId,&tempId,&vRef)) return err;
	
	n++;
	
	if (name) PCopy(fName,name);
	else
	{
		NumToString(TickCount(),fName);
		PCatC(fName,'+');
		PLCat(fName,n);
	}
	
	err = FSMakeFSSpec(vRef,tempId,fName,spec);
	return(UniqueSpec(spec,27));
}

/**********************************************************************
 * FindTemporaryFolder - find the Temporary Folder
 *	use spool folder if not available on server
 **********************************************************************/
OSErr FindTemporaryFolder(short vRef,long dirId,long *tempDirId,short *tempVRef)
{
	OSErr err = noErr;
	
//	tell FindFolder to forget everything it knows, and look at the disk
	err = InvalidateFolderDescriptorCache ( 0, 0L );
	ASSERT ( noErr == err );
	err = FindFolder(vRef,kTemporaryFolderType,True,tempVRef,tempDirId);
	
#ifdef DEBUG
	// Some versions of OS X will return noErr and a bad value for the temp folder
	// if it existed once, but does no longer. So, we check to see if it exists.
	//	*** Darn their eyes! ***
	if ( noErr == err )
	{
		CInfoPBRec pb;
		
		Zero ( pb );
		pb.dirInfo.ioFDirIndex	= -1;		// use ioVRefNum and ioDrDirID only
		pb.dirInfo.ioVRefNum 	= *tempVRef;
		pb.dirInfo.ioDrDirID	= *tempDirId;
		err = PBGetCatInfoSync ( &pb );
		ASSERT ( noErr == err );
	}
#endif
		
	// If findfolder fails, or if it returns a different volume, use
	// the spool folder
	if (err || *tempVRef!=vRef)
	{
		err = noErr;
		*tempVRef = vRef;
		if (dirId) *tempDirId = dirId;
		else
		{
			FSSpec netbootSucksSpec;
			
			if (SubFolderSpec(SPOOL_FOLDER,&netbootSucksSpec) || netbootSucksSpec.vRefNum!=vRef)
				*tempDirId = 2;
			else
				*tempDirId = netbootSucksSpec.parID;
		}
	}
	return(err);
}

/**********************************************************************
 * 
 **********************************************************************/
OSErr AddUniqueExt(FSSpecPtr spec,short extId)
{
	FSSpec newSpec;
	short n = 0;
	Str31 extStr, nStr;
	OSErr err;
	
	PCopy(extStr,"\p.");
	PCatR(extStr,extId);
	*nStr = 0;
	
	for (;;)
	{
		newSpec = *spec;
		*newSpec.name = MIN(*newSpec.name,31-*extStr-*nStr);
		if (n++) PCat(newSpec.name,nStr);
		PCat(newSpec.name,extStr);
		if (err=FSpExists(&newSpec)) break;
		NumToString(n,nStr);
	}
	
	if (err==fnfErr)
	{
		err = FSpRename(spec,newSpec.name);
		if (!err) PCopy(spec->name,newSpec.name);
	}
	
	return(err);
}
		
/**********************************************************************
 * NewTempSpec - make a temp file spec
 **********************************************************************/
OSErr NewTempExtSpec(short vRef,PStr name,short extId,FSSpecPtr spec)
{
	long dirId;
	OSErr err = FindTemporaryFolder(vRef,0L,&dirId,&vRef);
	Str31 fName;
	static short n;
	
	if (err) return(err);
	
	do
	{
		n++;
		if (n>REAL_BIG) n = 0;
		
		if (name) PCopy(fName,name);
		else
		{
			NumToString(TickCount(),fName);
			PCatC(fName,'+');
			PLCat(fName,n);
		}
		PCatC(fName,'.');
		PCatR(fName,extId);
		
	}
	while (!FSMakeFSSpec(vRef,dirId,fName,spec));
	if (err==fnfErr) err = noErr;
	return(err);
}

/************************************************************************
 * MakeAFinderAlias - make an alias to a file
 ************************************************************************/
OSErr MakeAFinderAlias(FSSpecPtr originalSpec,FSSpecPtr aliasSpec)
{
	AliasHandle alias=nil;
	short err;
	FSSpec spec, localSpec;
	FInfo origInfo,aliasInfo;
	short refN;
	short oldResF = CurResFile();
	
	err = FSMakeFSSpec(aliasSpec->vRefNum,aliasSpec->parID,"\p",&spec); 
	
	if (!(err=NewAlias(&spec,originalSpec,&alias)) && !(err=FSpGetFInfo(originalSpec,&origInfo)))
	{
		err = FSMakeFSSpec(aliasSpec->vRefNum,aliasSpec->parID,aliasSpec->name[0]?aliasSpec->name:originalSpec->name,&localSpec);
		
		/*
		 * does file exist?
		 */
		if (!FSpGetFInfo(&localSpec,&aliasInfo))
		{
			/*
			 * don't replace real file with alias
			 */
			if (!IsAlias(&localSpec,&spec))
			{
				err = dupFNErr;
				goto done;
			}
			else if (SameSpec(originalSpec,&spec))
				goto done;	/* we already have an alias to the file in ? */
		}
		
		/*
		 * create alias file
		 */
		FSpCreateResFile(&localSpec,origInfo.fdCreator,origInfo.fdType,smSystemScript);
		err=ResError();
		if (err = dupFNErr) err = noErr;	/* ignore; we'll change an existing alias */
		
		/*
		 * write alias into it
		 */
		if (!err)
		{
			err = FSpGetFInfo(&localSpec,&aliasInfo);
			aliasInfo.fdFlags |= kIsAlias;
			err = FSpSetFInfo(&localSpec,&aliasInfo);

			/*
			 * now we have an alias file; stick the alias into it
			 */
			if (0<=(refN=FSpOpenResFile(&localSpec,fsRdWrPerm)))
			{
				AddResource_(alias,'alis',0,"");
				if (!(err=ResError())) alias=nil;
				CloseResFile(refN);
			}
			else err = ResError();
			if (err) {FSpDelete(&localSpec);}
		}
	}
done:
	if (!err) *aliasSpec = localSpec;
	ZapHandle(alias);
	UseResFile (oldResF);
	return(err);
}

/************************************************************************
 * SimpeMakeFSSpec - make an FSSpec, but don't hit the filesystem
 ************************************************************************/
void SimpleMakeFSSpec(short vRef,long dirId,PStr name, FSSpecPtr spec)
{
	spec->vRefNum = vRef;
	spec->parID = dirId;
	PSCopy(spec->name,name);
}

/************************************************************************
 * FindMyFile - Like FindFile, only Eudora-related
 ************************************************************************/
OSErr FindMyFile(FSSpecPtr spec,long whereToLook,short fileName)
{
	FSSpec mySpec;
	OSErr err = fnfErr;
	Str31 nameStr;
	
	if (whereToLook & kStuffFolderBit)
	{
		if (!(err=GetFileByRef(AppResFile,&mySpec)))
		if (!(err=FSMakeFSSpec(mySpec.vRefNum,mySpec.parID,GetRString(nameStr,STUFF_FOLDER),&mySpec)))
		{
			IsAlias(&mySpec,&mySpec);
			mySpec.parID = SpecDirId(&mySpec);
			if (!(err=FSMakeFSSpec(mySpec.vRefNum,mySpec.parID,GetRString(nameStr,fileName),&mySpec)))
			{
				IsAlias(&mySpec,&mySpec);
				*spec = mySpec;
				return noErr;
			}
		}
	}
	
	return err;
}

#ifdef DEBUG
#undef FSpDirCreate
#undef DirCreate
/************************************************************************
 * FSpDirCreate - call FSpDirCreate but pacify SpotLight
 ************************************************************************/
OSErr MyFSpDirCreate(FSSpecPtr spec, ScriptCode scriptTag, long *createdDirID)
{
	OSErr err;
	SLDisable();
	err = FSpDirCreate(spec,scriptTag,createdDirID);
	SLEnable();
	return(err);
}

/************************************************************************
 * MyDirCreate - call DirCreate but pacify SpotLight
 ************************************************************************/
OSErr MyDirCreate(short vRefNum, long parentDirID, PStr directoryName, long *createdDirID)
{
	OSErr err;
	SLDisable();
	err = DirCreate( vRefNum,  parentDirID,  directoryName, createdDirID);
	SLEnable();
	return(err);
}

/************************************************************************
 * MyFSpDelete - bottleneck for deleting files
 ************************************************************************/
#undef FSpDelete
OSErr MyFSpDelete(FSSpecPtr spec)
{
	OSErr err;
	
	ASSERT(!SameSpec(spec,&SettingsSpec));
	
	err = FSpDelete(spec);
#ifdef NEVER
	if (RunType!=Production && spec.vRefNum==AttFolderSpec.vRefNum && spec.parID==AttFolderspec.parID)
		Dprintf("\pFSpDelete %d.%d.Ò%pÓ",spec->vRefNum,spec->parID,spec->name);
#endif
	return err;
}

/************************************************************************
 * MyCloseResFile - bottleneck for closing resource files
 ************************************************************************/
#undef CloseResFile
void MyCloseResFile(short refN)
{	
	ASSERT(refN!=ThreadGlobals.tSettingsRefN);
	
	CloseResFile(refN);
}
#define CloseResFile MyCloseResFile
#endif

/************************************************************************
 * MakeUniqueUntitledSpec - Make a unique "untitled" name in some folder
 ************************************************************************/
void MakeUniqueUntitledSpec (short vRefNum, long dirID, short strResID, FSSpec *spec)

{
	Str31		name,
					s;
	long		suffix;
	Byte		saveLen;

	//	Make a unique "untitled" name
	GetRString (name, strResID);
	suffix = 2;
	saveLen = *name;
	while (!FSMakeFSSpec (vRefNum, dirID, name, spec)) {
		//	No error means that the file/folder exists. Change the file name by adding a numeric suffix
		*name = saveLen;	//	Remove any suffix
		NumToString (suffix++, s);
		PCatC (name, ' ');
		PCat (name, s);
	}
}


OSErr MisplaceItem (FSSpec *spec)

{
	FSSpec	misplacedFolder,
					exist,
					newExist;
	OSErr		theError;
	long		dirID;
	
	// Find the Misplaced Items folder
	if (theError = SubFolderSpec (MISPLACED_FOLDER, &misplacedFolder)) {
		SimpleMakeFSSpec (Root.vRef, Root.dirId, GetRString (misplacedFolder.name, MISPLACED_FOLDER), &misplacedFolder);
		theError = FSpDirCreate (&misplacedFolder, smSystemScript, &dirID);
		if (!theError)
			misplacedFolder.parID = dirID;
	}
	if (!theError) {
		IsAlias (&misplacedFolder, &misplacedFolder);
		if (!FSMakeFSSpec (misplacedFolder.vRefNum, misplacedFolder.parID, spec->name, &exist)) {
			newExist = exist;
			UniqueSpec (&newExist, 31);
			FSpRename (&exist, newExist.name);
		}
		theError = SpecMove (spec, &misplacedFolder);
	}
	return (theError);
}


OSErr FSpGetLongName ( FSSpec *spec, TextEncoding destEncoding, Str255 longName ) {
	OSErr	err = noErr;
	HFSUniStr255	uniName;
	
	if (destEncoding==kTextEncodingUnknown) destEncoding = CreateTextEncoding(kTextEncodingMacRoman,0,0);
	
//	Get the unicode name
	err = FSpGetLongNameUnicode ( spec, &uniName );
	if ( err == noErr ) {
		UnicodeToTextInfo	info;
		
	//	Convert the name back to UTF-8 or something
		err = CreateUnicodeToTextInfoByEncoding ( destEncoding, &info );
		if ( err == noErr ) {
			//err = ConvertFromUnicodeToPString ( info, uniName.length * sizeof ( UniChar ), uniName.unicode, longName );
			long charsUsed=0, charsOut=0;
			err = ConvertFromUnicodeToText ( info, uniName.length * sizeof ( UniChar ), uniName.unicode, 
																			 kUnicodeUseFallbacksMask|kUnicodeLooseMappingsMask, 0, nil, nil, nil, 127, &charsUsed, &charsOut, longName+1 );
			*longName = charsOut;
			ASSERT(!err || err==kTECUsedFallbacksStatus);
			if (*longName) err = noErr;	// if we got something, use it
			
  		(void) DisposeUnicodeToTextInfo ( &info );
  		}
		}

	return err;
	}

OSErr FSpGetLongNameUnicode ( FSSpec *spec, HFSUniStr255 *longName ) {
	OSErr	err = noErr;
	FSRef	aRef;

	err = FSpMakeFSRef ( spec, &aRef );
	if ( err == noErr ) {
		err = FSGetCatalogInfo ( &aRef, kFSCatInfoNone, NULL, longName, NULL, NULL );
		}

	return err;
	}


OSErr FSpSetLongName ( FSSpec *spec, TextEncoding srcEncoding, ConstStr255Param longName, FSSpec *newSpec ) {
	OSErr				err = noErr;
	HFSUniStr255		uniName;
	TextToUnicodeInfo	info;

	if (srcEncoding == kTextEncodingUnknown) srcEncoding = CreateTextEncoding(kTextEncodingMacRoman,0,0);
	
	err = CreateTextToUnicodeInfoByEncoding ( srcEncoding, &info );
	if ( err == noErr ) {
		ByteCount uniStrLen;
		err = ConvertFromPStringToUnicode ( info, longName, 255 * sizeof ( UniChar ), &uniStrLen, uniName.unicode );
		uniName.length = uniStrLen/2;
		if ( err == noErr )
			err = FSpSetLongNameUnicode ( spec, &uniName, newSpec );
		DisposeTextToUnicodeInfo ( &info );
		}
	return err;
	}


OSErr FSpSetLongNameUnicode ( FSSpec *spec, ConstHFSUniStr255Param longName, FSSpec *newSpec ) {
	OSErr	err = noErr;
	FSRef	aRef;

	err = FSpMakeFSRef ( spec, &aRef );
	if ( err == noErr ) {
		FSRef newRef;
		FSRef *refPtr = newSpec != NULL ? &newRef : NULL;
		err = FSRenameUnicode ( &aRef, longName->length, longName->unicode, kTextEncodingUnicodeDefault, refPtr );
	//	Convert the FSRef back into a FSSpec for the caller
		if ( err == noErr && refPtr != NULL )
			(void) FSGetCatalogInfo ( refPtr, kFSCatInfoNone, NULL, NULL, newSpec, NULL );
		}

	return err;
	}


OSErr MakeUniqueLongFileName ( short vRefNum, long dirID, StringPtr name, TextEncoding srcEncoding, short maxLen ) {
	OSStatus			err;
	Str31				s;
	HFSUniStr255		uniName;
	TextToUnicodeInfo	info;
	FSRef				parent;
	FSRefParam			fs;
	
	ASSERT ( name != NULL );	
	ASSERT ( maxLen >= 63 );	

//	Make an FSRef to the parent directory
	Zero ( s );
	Zero ( fs );
	fs.ioVRefNum		= vRefNum;
	fs.ioDirID			= dirID;
	fs.ioNamePtr		= s;
	fs.newRef			= &parent;
	if ( noErr != ( err = PBMakeFSRefSync ( &fs )))
		return err;
		
	if ( srcEncoding == kTextEncodingUnknown )
		srcEncoding = CreateTextEncoding ( kTextEncodingMacRoman, 0, 0 );

	err = CreateTextToUnicodeInfoByEncoding ( srcEncoding, &info );
	if ( err == noErr ) {
		Str255		base, suffix;
		long		nextFile = 1;
		FSRef		ref;
	
	//	Split the file name into base name and suffix
		SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote ( name, base, suffix, maxLen );
		base [ ++base [ 0 ]] = ' ';	// tack a space on the end
		
	//	Set up the parameter block
		Zero ( fs );
		fs.ref				= &parent;
		fs.name				= uniName.unicode;
		fs.newRef			= &ref;

		while ( true ) {
			ByteCount uniStrLen;

		//	See if the file exists
			err = ConvertFromPStringToUnicode ( info, name, 255 * sizeof ( UniChar ), &uniStrLen, uniName.unicode );
			fs.nameLength = uniStrLen / sizeof ( UniChar );

			err = PBMakeFSRefUnicodeSync ( &fs );
			if ( err != noErr )
				break;

			NumToString ( nextFile++, s );
		
		//	If the new string will be too long, then trim the base
			if ( base[0] + suffix[0] + s[0] > maxLen - 1 ) {
				short newLen;
				base[0] = newLen = maxLen - ( suffix[0] + s[0]);
				base [ newLen ] = ' ';
				base [ newLen - 1 ] = 'É';
				}
		
		//	Build the whole string
			PCopy ( name, base );
			PCat ( name, s );
			PCat ( name, "\p." );
			PCat ( name, suffix );
			}

		ASSERT ( err != noErr );	
	//	Map fnfErr --> success
		if ( err == fnfErr )
			err = noErr;

		DisposeTextToUnicodeInfo ( &info );
		}
	
	return err;
	}

/**********************************************************************
 * The following block of functions is intended to allow Eudora to wait
 * for access to its files, in case other programs are using them.  It will
 * wait up to a certain number of seconds (currently 10) for files
 * to become free.  It waits if it gets opWrErr or permErr.  This latter
 * is what most calls seem to return for busy files, though you'd think
 * opWrErr would be more appropriate.  Unfortunately, permErr is what
 * you also get for a locked file, so if we get it we have to rule it out.
 * We get afpAccessDenied for permission errors, even local unix ones,
 * so at least we don't have to worry about that
 **********************************************************************/

#undef FSpOpenResFile
short FSpOpenResFilePersistent(FSSpecPtr spec,short mode)
{
	uLong ticks = TickCount();
	short refN;
	
	for(;;)
	{
		refN = FSpOpenResFile(spec,mode);
		if (refN==-1)
		{
			OSErr err = ResError();
			if ((err==opWrErr || err==permErr)&&!FSpIsLocked(spec))
			{
				if (TickCount()-ticks > GetRLong(LOCKED_FILE_PERSISTENCE)*60) break;
				YieldCPUNow();
			}
			else break;
		}
		else
			break;
	}
	return refN;
}
#define FSpOpenResFile FSpOpenResFilePersistent

#undef FSpOpenDF
OSErr FSpOpenDFPersistent(FSSpecPtr spec,short mode,short *refN)
{
	uLong ticks = TickCount();
	OSErr err;
	
	for(;;)
	{
		err = FSpOpenDF(spec,mode,refN);
		if ((err==opWrErr || err==permErr)&&!FSpIsLocked(spec))
		{
			if (TickCount()-ticks > GetRLong(LOCKED_FILE_PERSISTENCE)*60) break;
			YieldCPUNow();
		}
		else
			break;
	}
	return err;
}
#define FSpOpenDF FSpOpenDFPersistent

#undef FSpOpenRF
OSErr FSpOpenRFPersistent(FSSpecPtr spec,short mode,short *refN)
{
	uLong ticks = TickCount();
	OSErr err;
	
	for(;;)
	{
		err = FSpOpenRF(spec,mode,refN);
		if ((err==opWrErr || err==permErr)&&!FSpIsLocked(spec))
		{
			if (TickCount()-ticks > GetRLong(LOCKED_FILE_PERSISTENCE)*60) break;
			YieldCPUNow();
		}
		else
			break;
	}
	return err;
}
#define FSpOpenRF FSpOpenRFPersistent

Boolean FSpIsLocked(FSSpecPtr spec)
{
	CInfoPBRec cfi;
	if (!HGetCatInfo(spec->vRefNum,spec->parID,spec->name,&cfi))
	{
		return (cfi.hFileInfo.ioFlAttrib&kioFlAttribLockedMask)!=0;
	}
	return false;
}

/**********************************************************************
 * end file wait functions
 **********************************************************************/

/**********************************************************************
 * IsPDFFile - is a spec a pdf file?
 **********************************************************************/
Boolean IsPDFFile(FSSpecPtr spec,OSType fileType)
{
	if (fileType == 'PDF ') return true;
	// Avi must die.
	if (EndsWithR(spec->name,PDF_QUOTE_EXTENSION_UNQUOTE)) return true;
	return false;
}

/**********************************************************************
 * SpecEndsWithExtensionR - does a spec (long name) end with an extension
 *  on a list?
 **********************************************************************/
Boolean SpecEndsWithExtensionR(FSSpecPtr spec,short resID)
{
	Str255 longName;
	
	if (FSpGetLongName(spec,0,longName)) PCopy(longName,spec->name);
	
	return EndsWithItem(longName,resID);
}