/*
	File:		alExt.c

	Contains:	External interfaces for alias manager.

	Written by:	Prashant Patel

	Copyright:	© 1989-1991 by Apple Computer, Inc., all rights reserved.

    This file is used in these builds: BigBang

	Change History (most recent first):

		<42>	 2/27/91	PP		ich,#ich-AMgr-0095:Allow ejectable FileShared volumes to be auto
									mounted from client side.
		<41>	 2/11/91	PP		ich,<go5 Decision>:Do not allow CanonifyFile to work for an MFS
									volume. Remove any MFS Volume alias support (files within volume
									work). ich,#ich-AMgr-0094:Hints for null filenames are invalid.
		<40>	  2/5/91	stb		gs: fix casing
		<39>	 1/31/91	PP		ich,#b4q4 WhiteBoard:FSMakeFSSpec does not work for MFS volume
									itself. Use the old AL_canonifyFile whenever MakeFSSpec fails.
		<38>	 1/25/91	PP		ich,#81507:FollowFinderAlias gives priority to original volume
									mounting over relative path target in same folder.
									FollowFinderAlias is now an internal trap selector so Finder can
									call and use the same algorithm. It has an additional logon
									parameter.
		<37>	 1/22/91	PP		ich,#ICH-AMGR-0089:In GetNextSpec call, FollowFinderAlias is
									called with a copy of target as fromFile.
		<36>	 1/11/91	PP		(ich) Set relative path search hint only if it is valid.
		<35>	 1/10/91	PP		(ich) In ResolveAliasFile, handle end case of 10 chain alias
									correctly. Kind is correctly checked for minimal fullpath
									aliases. In ResolveAliasFile, isAlias bit is ignored for
									folders. For FileShare aliases, if remote info switch occurs,
									reassign hint parent dir. For multiple volume search, preserve
									the original target's volume ref num.
		<34>	  1/2/91	PP		(ich) CatSearch time out is in miliseconds. Fix bug related to
									optimization of not slow searching already searched volume.
		<33>	12/21/90	LN		change constStrxxxParameter to ConstStrxxxParam as per change in
									Types.h
		<32>	12/20/90	PP		(ich) Force an update of alias if alias has moved instead of
									target for ResolveAliasFile call. FilterFile moved here from
									alUtil.c. UpdateAList is split into NewCandidate and
									AddCandidate. useCanceledErr does not conflict with found
									candidates.
		<31>	12/13/90	PP		(PP/ngk) Ignore UpdateAlias result code in ResolveAlias if
									MatchAlias was successful and still return target. (ich) Do slow
									search on volume aliases correctly. Minimal fullpath does
									additional param checking. In StoreHint, check for bad vref and
									for bad dirID always return dirNFErr. During multiple volume and
									fast searches, do not update needsUpdate if found count has not
									changed. Call to FSMakeFSSpec is moved to AL_canonifyFile.
		<30>	11/29/90	PP		(PP/ngk) In StoreHint, if relative first search cannot return a
									hint (i.e. no relative path stored or other error), still try to
									return a hint if other search rule was specified.  (PP/ich)
									ResolveAliasFile should follow exactly same algorithm for
									finding targets as Finder does.  Return hint for afpAccessDenied
									files.  Remove alias record version 1 compatibility code.
		<29>	10/30/90	PP		AL_isItSelf is made external. If relative had priority but found
									by absolute, do not set needsUpdate if the fromFile and Target
									volumes are different. If resolving a server alias on client, do
									not do FastSearch on switched auxiliary data but try to check if
									root volume is mounted.
		<28>	10/21/90	gbm		Change hfileInfo to hFileInfo for the last time (plus one).
	    <28>	10/10/90	JL		Change hfileInfo to hFileInfo for the last time.
		<27>	10/10/90	PP		For creation dates on AppleShare volumes, need to store raw data
									instead of time adjusted to current machine's date.
									ResolveAliasFile returns resNotFound if alias bit is set but no
									alias resource. StoreHint returns correct vRefNum for relative
									first search.
		<26>	  9/1/90	PP		When relative path found a wrong kind match, reset the volume
									flag to force a find volume.  If MakeFSSpec fails, try
									CanonifyFile.
		<25>	 8/29/90	PP		If during relative search, nothing was added to found list,
									search for the target volume. For minimal fullpath aliases, if
									the path ends in a colon, make it a directory alias.
		<24>	 8/28/90	dnf		Change calls to CatSearch to use new param block names
									(ioSearchBits, ioSearchInfo1, ioSearchInfo2)
		<23>	 8/17/90	PP		Fast Search on multiple volumes mean search by ID followed by
									search by name in parent directory. Volume creation date cannot
									be ontained by calling GetCatInfo on dirID of 2.
		<22>	 8/10/90	PP		Fix bug related to resolving aliases for renamed volume targets.
		<21>	 8/10/90	PP		Handle MFS volume aliases in ResolveAliasFile.
		<20>	  8/7/90	PP		Suppress warnings in AliasVolumeMount.
		<19>	  8/6/90	PP		Support auto volume mount for foreign file systems other than
									AppleShare. If fromFile is set for ResolveAlias, do relative
									search first.
		<18>	 7/19/90	PP		When FileShare returns info about next shared folder, check it's
									name and creation date against the info in alias record. If
									relative has first priority and found by absolute, set
									needsUpdate.
		<17>	 7/11/90	PP		Fix bug in ResolveAliasFile related to returning targetIsFolder
									correctly when resolveAliasChains is false.
		<16>	  7/9/90	PP		Fix bug related to returning needsUpdate true if the candidate
									buffer is greater than 1.
		<15>	  7/2/90	PP		Add ResolveFinderAlias. Adopted from Ian Hendry's routine for
									DTS Goodies disk.
		<14>	 6/28/90	PP		Convert CanonicalFileSpec to FSSpec.
		<13>	 6/15/90	PP		Remove "SelectAlias" feature.  Correctly compute IsItSelf if
									there are no bridges.
		<12>	 5/31/90	PP		For aliases to volumes, optimize the resolution.  Fix a bug
									related to leaf name greater than 63 chars for fullpath minimal
									alias.
		<11>	 5/16/90	PP		FileShare CreateID works. Undef code that uses it.
		<10>	  5/3/90	PP		Closer integration of FileShare and aliases. Incorporate Diet
									Clinic tips.
		 <9>	 4/10/90	PP		Change once more hFileInfo to hfileInfo to stay consistent with
									Files.h
		 <8>	  4/5/90	PP		Code size optimization. Support kARMsearchRelFirst and return
									hint when alias not resolved.
		 <7>	  3/8/90	PP		Implement NewAliasMinimal and NewAliasMinimalFromFullPath.
									Change "hfileInfo" to "hFileInfo'" to match Files.h change.
		 <6>	 2/27/90	PP		AppleShare foreign volumes should not become fuzzy if creation
									date has changed.
		 <5>	  2/6/90	PP		Fix messed up comments during last check-in.
		 <4>	  2/6/90	PP		No SelectAlias dialog for volume aliases.  For HFS volumes,
									match resolved aliases to be same kind (file or directory).
		 <3>	 1/21/90	PP		Add support for HD and Ejectable volumes auto mount.  Store
									driver name for ejectable non-floppy volumes in alias record.
									Fix relative path returning wrong type bug.  Add concept of
									fuzzy volume.
	   <2.8>	12/15/89	prp		Fix CanonifyFile bug for MFS disks. Make ValidateHandle calls.
	   <2.7>	11/27/89	prp		Fix incorrect dirID returned for AbsPath CanonifyFile call.
	   <2.6>	11/16/89	prp		MatchAlias sets needsUpdate correctly if multiple answers are
									returned or if one answer on different volume is returned.
									SelectAlias mounts the volume automatically.Resolve or Select
									for relative path aliases mounts the volumes correctly.
	   <2.5>	10/30/89	prp		¥ In MatchAlias, call file filter for fast matches also. ¥ Use
									AL_findVolume routine to find the volume correctly. ¥
									AL_findByID can return fnfErr for directories. Handling this
									corrects the bug related finding directories whose dirIDs have
									changed. ¥ For files/directories fast search, use the absolute
									path search correctly. ¥ SelectAlias calls MatchAlias with
									correct filter proc params instead of NULL. ¥ SelectAlias
									returns nsvErr if the volume is not mounted for a volume alias.
	   <2.4>	10/17/89	prp		Aliases did not work on 68000 machines. Fixed odd addressing in
									variable length info structure.
	   <2.3>	10/13/89	prp		Handle NULL as well as zero length input for Filename for
									CanonifyFile call.
	   <2.2>	 10/2/89	prp		Added 'GetAliasInfo' routine.
	   <2.1>	 9/18/89	prp		UpdateAlias, MatchAlias and SelectAlias has a new API with
									additional parameters.
	   <2.0>	  9/6/89	prp		Added DisposeAlias back until DPMgr is in sync.
	   <1.9>	  9/6/89	prp		Changes from CodeReview.
	   <1.8>	 8/11/89	prp		Handle eofErr as a valid legal error from CatSearch.
	   <1.7>	  8/7/89	prp		Add SelectAlias with dialog support. Add automatic volume mount
									support.
	   <1.6>	 6/23/89	prp		Variable names "server" and "zone" changed to "serverName" and
									"zoneName" in order to avoid conflict with globally defined
									symbols in the BigBang build.
	   <1.5>	 6/12/89	prp		In CanonifyFile, if the file name does not exist, return fnfErr
									in addition to correct canonical form.
	   <1.4>	 6/12/89	prp		CanonifyFile handles all different cases. No HPurge and MoveHH
									in New and Update. No separate emphasize relative path bit.
									Consolidated New and Update routines. Added slow search. Cleaned
									up IFNDEFs.
	   <1.3>	  6/6/89	prp		In MatchAlias, EqualString test was inverted. Also, during
									UpdateAlias, if an error occurs, do not dispose of the alias
									handle.
	   <1.2>	  6/1/89	prp		Make sure ioNamePtr is always assigned some value before a
									'PB...' file manager call is made.
	   <1.1>	 5/31/89	prp		Moved AliasRecord to private definition file.
	   <1.0>	 5/30/89	prp		Initial release

	To Do:
*/

/*EASE$$$ READ ONLY COPY of file ÒalExt.cÓ
** 2.8	prp 12/15/1989 Fix CanonifyFile bug for MFS disks. Make ValidateHandle
**		calls.
** 2.7	prp 11/27/1989 Fix incorrect dirID returned for AbsPath CanonifyFile
**		call.
** 2.6	prp 11/16/1989 MatchAlias sets needsUpdate correctly if multiple
**		answers are returned or if one answer on different volume is
**		returned. SelectAlias mounts the volume automatically.Resolve or
**		Select for relative path aliases mounts the volumes correctly.
** 2.5	prp 10/30/1989 ¥ In MatchAlias, call file filter for fast matches also.
**		¥ Use AL_findVolume routine to find the volume correctly. ¥
**		AL_findByID can return fnfErr for directories. Handling this corrects
**		the bug related finding directories whose dirIDs have changed. ¥ For
**		files/directories fast search, use the absolute path search
**		correctly. ¥ SelectAlias calls MatchAlias with correct filter proc
**		params instead of NULL. ¥ SelectAlias returns nsvErr if the volume is
**		not mounted for a volume alias.
** 2.4	prp 10/17/1989 Aliases did not work on 68000 machines. Fixed odd
**		addressing in variable length info structure.
** 2.3	prp 10/13/1989 Handle NULL as well as zero length input for Filename
**		for CanonifyFile call.
** 2.2	prp 10/02/1989 Added 'GetAliasInfo' routine.
** 2.1	prp 09/18/1989 UpdateAlias, MatchAlias and SelectAlias has a new API
**		with additional parameters.
** 2.0	prp 09/06/1989 Added DisposeAlias back until DPMgr is in sync.
** 1.9	prp 09/06/1989 Changes from CodeReview.
** 1.8	prp 08/11/1989 Handle eofErr as a valid legal error from CatSearch.
** 1.7	prp 08/07/1989 Add SelectAlias with dialog support. Add automatic
**		volume mount support.
** 1.6	prp 06/23/1989 Variable names "server" and "zone" changed to
**		"serverName" and "zoneName" in order to avoid conflict with globally
**		defined symbols in the BigBang build.
** 1.5	prp 06/12/1989 In CanonifyFile, if the file name does not exist, return
**		fnfErr in addition to correct canonical form.
** 1.4	prp 06/12/1989 CanonifyFile handles all different cases. No HPurge and
**		MoveHH in New and Update. No separate emphasize relative path bit.
**		Consolidated New and Update routines. Added slow search. Cleaned up
**		IFNDEFs.
** 1.3	prp 06/06/1989 In MatchAlias, EqualString test was inverted. Also,
**		during UpdateAlias, if an error occurs, do not dispose of the alias
**		handle.
** 1.2	prp 06/01/1989 Make sure ioNamePtr is always assigned some value before
**		a 'PB...' file manager call is made.
** 1.1	prp 05/31/1989 Moved AliasRecord to private definition file.
** 1.0	prp 05/30/1989 Initial release
** END EASE MODIFICATION HISTORY */

/*********************************************************************
*
*	File:		alExt.c
*	Project: 	Alias Manager
*	Contains:	external interfaces
*	Written by:	Prashant Patel
*
*	Copyright 1989 by Apple Computer, Inc.
*	All Rights Reserved.
*
**********************************************************************/

/**********************************************************************
 ***************		Public Include files		*******************
 *********************************************************************/
 
 #include "Aliases.h"		
 
/**********************************************************************
 ***************		 Private Include files		*******************
 *********************************************************************/
 
 #include "aliasPriv.h"		

/**********************************************************************
 ***************		External C Include files		***************
 *********************************************************************/

#ifndef __MEMORY__
#include <Memory.h>
#endif
#ifndef __FILES__
#include <files.h>
#endif
#ifndef __EVENTS__
#include <Events.h>
#endif
#ifndef __ERRORS__
#include <errors.h>
#endif
#ifndef __STRING__
#include <String.h>
#endif
#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif
#ifndef __OSUTILS__
#include <OSUtils.h>
#endif
#ifndef __RESOURCES__
#include <Resources.h>
#endif
#ifndef __DEVICES__
#include <Devices.h>
#endif
#ifndef __SYSEQU__
#include <SysEqu.h>
#endif

/*********************************************************************
******** private definitions  ****************************************
*********************************************************************/

/* match alias search kind */
 enum { kFindByID,				// find by directory ID or file ID
 		kFindByName,			// find by name in parent directory
 		kFindByNum,				// find by file number in parent directory
 		kFindByAbsPath,			// find by absolute path 
		kFindByRelPath			// find by relative path 
};
typedef short MatchAliasSearchKind;

typedef struct {

	// MatchAlias input parameters	
	const FSSpec 	*fromFile; 			// aliased from file
	unsigned long 	rulesMask;			// search rules mask
	short 			aliasCount;			// # of entries in alias list
	FSSpecPtr 		aliasList;			// alias list buffer
	Boolean 		needsUpdate;		// does alias needs updating?
	AliasFilterProcPtr aliasFilter; 	// filter proc for alias match
	Ptr 			yourDataPtr;		// a data param passed to filter proc

	// local variables in the structure
	AliasPtrPriv 	aptr;				// pointer to locked AliasRecord
	short 			vref;				// volume ref num of mounted volume
	short 			curCnt;				// current output alias list count
	long 			newParentDir;		// target's new parent's directory ID
	Str63 			newTargetName;		// new name of traget
	long 			newFileNum;			// new file number of the target
	long 			hintParentDirAbs; 	// if not found, location of possible parent directory for absolute searches
	long 			hintParentDirRel; 	// if not found, location of possible parent directory for relative searches
	Boolean 		quitFlag;			// quit the search, client filter tells us so
	Boolean 		isVolumeFuzzy;		// Is found volume fuzzy? meaning different creation date
	Boolean 		foundByName;		// Did we find the match by name in parent directory?
	Boolean 		foundByRelativeFirst; // Was target found by relative search first or direct serach?

	/* following variables are used during slow search of a volume. */
	Boolean 		CatSearchAvailable; // Is CatSearch available?
	CatPositionRec 	prec; 				// CatSearch IO position record
	long 			curDir; 			// current directory that we are searching, start at root
	short 			curIdx;		  		// current index into directory stack
	short 			curIoFDirIndex; 	// current index into the directory being searched
	LongsHandle 	dirStack; 			// directory stack

	/* following variables are used for handling shared volumes (FileShare type) */
	AuxAliasInfo 	auxAliasRecord;		// auxiliary alias record
	Boolean 		auxAliasSwitch;		// Did we switch to auxiliary alias record?
	Boolean 		auxRealDirSwitch;   // Did we switch to auxiliary real parent dirID?
	Boolean 		searchedAllVols;	// Did search all volumes on the server side?
	Boolean 		volumeAliasFuzzy;	// Should extra checks be required for volume aliases?

} MatchAliasParamBlk;

#define SCGetExpFldr     6;				// Get Exported Folder info

/**********************************************************************
 ***************		 Private Macros		***************************
 *********************************************************************/

 /*** exit the routine if input buffer is full or quitFlag by client is set ***/ 

 #define mExitIfQuitOrBufFull(quitFlag, cnt,lim) \
 if (quitFlag || (cnt >= lim)) \
 	goto EXIT;
 


/**********************************************************************
 *** Create a new alias (common for real, minimal and minimal 
 from fullpath)									***********************
 *********************************************************************/

static pascal OSErr AL_newAliasCommon(	const FSSpec *fromFile, //I aliased from file
						const FSSpec *target,		//I aliased to file
						AliasHandlePriv *alias,		//O alias record handle
						NewAliasType type,			//I what type of new alias
						MinimalFullpathSpec *fullpath) //I fullpath spec
{	
	OSErr err; 					// result code
	Boolean dummy;				// dummy wasChanged flag not needed during NewAlias creation
	
	*alias = (AliasHandlePriv) NewHandleClear(sizeof(AliasRecordPriv));
	if (*alias == NULL) 		// no heap space
		return (MemError());
		
	err = AL_fillAlias (fromFile, target, kCreateAlias, *alias, &dummy, type, fullpath);
	if (err != noErr) {
		DisposHandle((Handle)*alias);	// fatal error, return NULL
		*alias = NULL;
	}
	
	return (err);
		
} // end of AL_newAliasCommon routine

/**********************************************************************
 ***************		Create a new alias		***********************
 *********************************************************************/

pascal OSErr AL_newAlias(	const FSSpec *fromFile, //I aliased from file
						const FSSpec *target,		//I aliased to file
						AliasHandlePriv *alias)		//O alias record handle
{	
	return (AL_newAliasCommon(fromFile, target, alias, kCompleteAlias, NULL));
		
} // end of AL_newAlias routine

/**********************************************************************
 ***************		Create a minimal new alias		***************
 *********************************************************************/

pascal OSErr AL_newAliasMinimal(const FSSpec *target,	//I aliased to file
						AliasHandlePriv *alias)			//O alias record handle
{	
	return (AL_newAliasCommon(NULL, target, alias, kMinimalAlias, NULL));

} // end of AL_newAliasMinimal routine

/**********************************************************************
 *************** Create a minimal new alias from fullpath *************
 *********************************************************************/

pascal OSErr AL_newAliasMinimalFromFullpath(short fullpathLength, //I length of fullpath
						const unsigned char *fullpath, 	//I fullpath
						const Str32 zoneName,			//I zone name
						const Str31 serverName,			//I server name
						AliasHandlePriv *alias)			//O alias record handle
{	
	OSErr err; 				// result code
	FSSpec target;			// a dummy target set up for NewAlias
	MinimalFullpathSpec minimalFullpath; // pass the fullpath spec to NewAlias
	Str255 name;			// fileName/volumeName from the given path

	if (fullpath == NULL || fullpathLength <= 0 || !AL_isFullpath(fullpath, fullpathLength) ||
		(zoneName != NULL && Length(zoneName) > (sizeof(Str32)-1)) || 
		(serverName != NULL && Length(serverName) > (sizeof(Str31)-1)))
		return (paramErr);

	target.vRefNum = 0;
	target.parID = kInvalidValue;

	AL_getFilename (fullpath, fullpathLength, name); // get the filename component
	if (Length(name) > (sizeof(Str63)-1)) // make sure leaf is valid
		return (paramErr);
	AL_copyPString(name, target.name);

	// store volume name from full path name
	AL_getVolname (fullpath, fullpathLength, name); // get volume name
	if (Length(name) > (sizeof(Str27)-1)) // make sure volume name is valid
		return (paramErr);

	minimalFullpath.fullpathLength = fullpathLength;
	minimalFullpath.fullpath = fullpath;
	minimalFullpath.zoneName = zoneName;
	minimalFullpath.serverName = serverName;
	
	err = AL_newAliasCommon(NULL, &target, alias, kMinimalFromFullpathAlias, &minimalFullpath);
	
	/* Case where fullpath ends in a colon or a series of colons,
	Mark the alias as a directory alias. */

	if (err == noErr && Length(target.name) == 0)
		(**alias)->thisAliasKind = kDirAlias;
		
	return (err);

} // end of AL_newAliasMinimalFromFullpath routine


/**********************************************************************
 ***************		Resolve an alias	***************************
 *********************************************************************/

pascal OSErr AL_resolveAlias(const FSSpec *fromFile, 	//I aliased from file 
						AliasHandlePriv alias,			//I/O alias record handle 
						FSSpec *target, 				//O resolved alias FSSpec
						Boolean *wasChanged)			//O did any thing change?
{
	OSErr err; 				// result code
	short cnt = 1;			// alias list length
	unsigned long rulesMask;// rules to be passed to MatchAlias
	
	/* This is a fast resolve.  Do a fast search for the alias and find the only
		one.  Not interested in multiple ones.  If the alias is found but 
		something has changed in the alias record, update the record. */
		
	rulesMask = kARMMountVol | kARMSearch; // always do fast direct search and auto mount volumes

	if (fromFile != NULL)	// If requested, set priority for relative search
		rulesMask |= kARMSearchRelFirst;	

	err = MatchAlias (fromFile, rulesMask, (AliasHandle)alias, &cnt, target, wasChanged, NULL, NULL);

	if ((err == noErr) && *wasChanged) // found the alias. if changed, update the record
		(void) UpdateAlias (fromFile, target, (AliasHandle)alias, wasChanged);

	return (err);
	
} // end of ResolveAlias routine

/*********************************************************************
 ***************	Do alias kind and macthed entry kind match?  *****
 *********************************************************************/
 
static Boolean KindMatches(CInfoPBRec *cpbPtr, 		//I
					const AliasPtrPriv aptr)		//I
{
	if (cpbPtr->hFileInfo.ioFlAttrib & kDirMask) {

		if (aptr->thisAliasKind == kDirAlias)

			return (TRUE);

	} else if (aptr->thisAliasKind == kFileAlias)

		return (TRUE);

	return (FALSE);	
	
} // end of KindMatches routine

/*********************************************************************
 ***************	Check if alias record attributes match CPB data	**
 *********************************************************************/

static Boolean AttributesMatch (register const AliasPtrPriv aptr,	//I alias record pointer
							register const CInfoPBRec *cpb,	//I result of a CatInfo call
							Boolean matchFileNum) 	//I should routine give priority to file num matching?
{
	short vref;						// volume reference number
	Boolean needsUpdate;			// dummy flag for this usage
	
	// Certain attributes of an alias record are matched against the data in a
	// cpb.  If either the file number matches(on the same volume) or 
	// (creation date, type, creator)
	// matches identically, a TRUE answer is returned.  Otherwise, return FALSE.
	
	if ( (!(cpb->hFileInfo.ioFlAttrib & kDirMask)) && // cpb says it is a file
	  (aptr->thisAliasKind == kFileAlias)) { 		// alias record says it is a file

		if (matchFileNum && cpb->hFileInfo.ioDirID == aptr->fileNum) { // Does file number match?

			if (AL_findVolume (aptr, 0 /* no autoMount */, &vref, &needsUpdate, NULL) == noErr
					&& cpb->hFileInfo.ioVRefNum == vref) // Is it on the same volume

				return (TRUE);

		} else {

	  		if ((aptr->fileCrDate == AL_fixedCreationDate(nil, cpb->hFileInfo.ioVRefNum, cpb->hFileInfo.ioFlCrDat)) && // How about creation date and

	  		(cpb->hFileInfo.ioFlFndrInfo.fdType == aptr->fileType) && // file type and
	
			(cpb->hFileInfo.ioFlFndrInfo.fdCreator == aptr->fdCreator)) // creator name?

				return (TRUE);
		}

	} else {

		if ((cpb->hFileInfo.ioFlAttrib & kDirMask) && // found a directory

			(aptr->thisAliasKind == kDirAlias) &&			// alias also says it is a dir

		 	(aptr->fileCrDate == AL_fixedCreationDate(nil, cpb->hFileInfo.ioVRefNum, cpb->dirInfo.ioDrCrDat))) // only creation date meaningful

		return (TRUE);
		
	}

	return (FALSE);
}

/*********************************************************************
 ***************		Filter a file for client's acceptance  *******
 *********************************************************************/

 static OSErr CallClientFilter(register MatchAliasParamBlk *apb, //I MatchAlias parameter block pointer
					CInfoPBPtr thisCpbPtr,			//I
					Boolean foundTarget,			//I
					Boolean *filterIt)				//O
{
	OSErr err = noErr;				// return code
	CInfoPBRec cpb;					// cpb to be passed to client fileFilter proc
	Boolean answerFromClient;		// did client say filter it or not?

	*filterIt = FALSE;				// init return params
	apb->quitFlag = FALSE;

	if (apb->aliasFilter != NULL) { // only if there is a filter to call

		if (foundTarget && (thisCpbPtr == NULL)) { // need to construct a CPB to be passed to filter
	
			// prepare a CPBInfoRec to be passed to the client filter as a param
			cpb.hFileInfo.ioFDirIndex = 0; // find the resolved match by name mode
			if ((err = AL_getCatInfo (apb->vref, apb->newParentDir, apb->newTargetName, &cpb)) != noErr)
				goto EXIT;
	
			thisCpbPtr = &cpb;
	
		}
	
		answerFromClient = (*(apb->aliasFilter)) (thisCpbPtr, &apb->quitFlag, apb->yourDataPtr);
	
		if (foundTarget) // do not destroy filterIt parameter when calling filter to get quitFlag
			*filterIt = answerFromClient;
	}

EXIT:

	return (err);

} // end of routine CallClientFilter

/*********************************************************************
 ***************		Check for entry in  Alias List  **************
 *********************************************************************/

static Boolean NewCandidate(short vref, 		//I
 					long dir,					//I
					const StringPtr nam,		//I
					short cnt,					//I
					FSSpec *list)				//I

{
	register short i = 0;			// do loop index
	register FSSpec *fptr = list; 	// pointer into input list

	// given a FSSpec data (vref, dirID and filename) and an alias list buffer,
	// The current count of the number of entries in the buffer is given as
	// cnt.  If a similar entry exists in the buffer, return FALSE.  
	// Otherwise, return TRUE.

	while (++i <= cnt) {			// is there a similar entry in the buffer?

		if (fptr->vRefNum == vref && fptr->parID == dir &&
			FSEqualString(fptr->name, nam)) {
			return(FALSE);
		}

		++fptr;

	}

	return (TRUE);

} // end of routine NewCandidate

/*********************************************************************
 ***************	Filter a found match during fast search  *********
 *********************************************************************/
 
static Boolean FilterThisMatch(register MatchAliasParamBlk *apb, //I MatchAlias parameter block pointer
							Boolean foundTarget)
{
	OSErr err;						// result code
	CInfoPBRec cpb;					// cpb
	CInfoPBPtr cpbPtr = NULL; 		// Ptr to cpb
	Boolean filterIt = TRUE;		// filter this entry or not?	
	register AliasPtrPriv aptr;		// pointer to locked AliasRecord

	/* Alias to a volume is OK even if fuzzy because the definition of fuzziness means that
	the creation date of the volume is different.  So, if we enforce the fuzziness check for
	volume aliases, they won't be found.  But we still may want to do this in case of
	fast search on multiple volumes is being done (FileShare case).  We want even volume
	aliases to be forces into extra checks of creation date.
	*/

	/*
	If the alias being resolved is a minimal alias, do not test fuzzyness.  Since for
	minimal aliases, no creation date, type and creator are stored, there is no point
	in checking those values for found matches.
	*/

	aptr = apb->aptr;

	if (foundTarget  && (aptr->parDirID != kVolRootDirID || apb->volumeAliasFuzzy)) { 	

		cpb.hFileInfo.ioFDirIndex = 0;	// find the resolved match by name mode
		err = AL_getCatInfo(apb->vref, apb->newParentDir, apb->newTargetName, &cpb);

		// if the kind (file or directory) does not match, it is wrong
		if (err != noErr || (!KindMatches(&cpb, aptr)))
			return (TRUE);
			
		// for fuzzy volumes, make sure creation date and type and creator match for files
		// and only creation date for folders. (test not valid for minimal aliases)
		if (aptr->fileNum != kInvalidValue && apb->isVolumeFuzzy) { 

			if (!AttributesMatch(aptr, &cpb, (!kMatchFileNumber)))
				return(TRUE);

		}
		cpbPtr = &cpb;
		
	} // kind matching and fuzzy volume checking done									

	/* If the found entry is a new candidate for the returned alias list, proceed.  
	Otherwise, this entry has already been filtered.
	*/
	if (!foundTarget || NewCandidate(apb->vref, apb->newParentDir, apb->newTargetName, apb->curCnt, apb->aliasList))
		(void) CallClientFilter (apb, cpbPtr, foundTarget, &filterIt);	// filter it by calling client
	
	return (filterIt);

} // end of FilterThisMatch routine

/*********************************************************************
 ***************	update file number for a given new file	**********
 *********************************************************************/

static void UpdateFileNumber (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{			
	(void) AL_findByName (apb->vref, apb->newParentDir, apb->newTargetName,
		&apb->newFileNum, &apb->newParentDir); // update the fileNumber

} // end of UpdateFileNumber routine

/*********************************************************************
 ***************		Add Candidate to Alias List  *****************
 *********************************************************************/

static void AddCandidate(short vref,			//I
 					long dir,					//I
					const StringPtr nam,		//I
					short *cnt,					//I/O
					short lim,					//I
					FSSpec *list)				//I/O

{
	register FSSpec *fptr = list; 	// pointer into input list
	register short lcnt = *cnt;		// local count

	// given a FSSpec data (vref, dirID and filename) and an alias list buffer,
	// add the FSSpec data to the alias list buffer.  The buffer bound is given as
	// lim and the current count of the number of entries in the buffer is given as
	// cnt.  If the buffer is not full, enter the entry into the alias list buffer.

	if (lcnt < lim)	{			// buffer is not full

		fptr = &list[lcnt];
	
		fptr->vRefNum = vref;
		fptr->parID = dir;
		AL_copyPString (nam, fptr->name);
	
		*cnt = (++lcnt);
	}

} // end of routine AddCandidate

/*********************************************************************
 ***************	update alias list and needsUpdate flag	**********
 *********************************************************************/

static void UpdateAliasListAndFlag (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{			
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord

	aptr = apb->aptr;

	// a match is found and filtered, now update the return list

	AddCandidate (apb->vref, apb->newParentDir, apb->newTargetName, 
				&apb->curCnt, apb->aliasCount, apb->aliasList);

	if (apb->needsUpdate == FALSE) { // update "needsUpdate" only if not already updated

		if (apb->newParentDir != aptr->parDirID || // parent directory ID changed

			// file number (or directory ID) changed 
			(apb->newFileNum != aptr->fileNum) || 

			(! FSEqualString(apb->newTargetName, aptr->fileName))) // name changed

		apb->needsUpdate = TRUE;

	}

} // end of UpdateAliasListAndFlag routine

/*********************************************************************
 ***************	Find the target and update alias param blk  ******
 *********************************************************************/
 
static OSErr FindTarget(MatchAliasSearchKind searchKind, //I type of search
					MatchAliasParamBlk *apb)			 //I MatchAlias parameter block pointer
{
	OSErr err;					// result code
	Boolean shouldFilter=TRUE;	// should filter the match ot not? 
	CInfoPBRec cpb;				// catalog parameter block
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord

	aptr = apb->aptr;
	apb->newParentDir = aptr->parDirID; // assume that it will be found at the old place
	apb->newFileNum = aptr->fileNum;
	AL_copyPString (aptr->fileName, apb->newTargetName);
	
	switch (searchKind) {

	case kFindByID:
		// redundant check to speed up and not call FilterThisMatch
		shouldFilter = (apb->isVolumeFuzzy || apb->aliasFilter != NULL);

		err = AL_findByID (apb->vref, aptr, &apb->newParentDir, apb->newTargetName);
		if (err != noErr) return (err);

		break;
		
	case kFindByName:
		cpb.hFileInfo.ioFDirIndex = 0;	// find the resolved match by name mode
		err = aptr->parDirID != kInvalidValue ? // force an absolute search with dirNFErr
				AL_getCatInfo(apb->vref, aptr->parDirID, aptr->fileName, &cpb) : dirNFErr;
		if (err != noErr) return (err);

		apb->newFileNum = cpb.hFileInfo.ioDirID;

		// redundant check to speed up and not call FilterThisMatch
		shouldFilter = (apb->isVolumeFuzzy || apb->aliasFilter != NULL || (!KindMatches(&cpb, aptr)));

		// Did file number change? (backup and restore at same place)	
		apb->foundByName = TRUE;

		break;
		
	case kFindByNum:
		err = AL_findFileByNum (apb->vref, aptr->parDirID, aptr->fileNum, apb->newTargetName);
		if (err != noErr) return (err);

		if (apb->foundByName && apb->aliasCount == 1)
			--apb->curCnt;	// overwrite the one that was found by name

		break;
		
	case kFindByAbsPath:
		// search by absolute path
		err = AL_findByAbsPath (apb->vref, aptr, &apb->newParentDir, apb->newTargetName);
		if (err != noErr)  { // trace dirs upto root. may be restored at a different place

			if (aptr->volumeType != kInvalidValue) 
				err = AL_findByAbsPathToRoot (apb->vref, aptr, &apb->newParentDir, apb->newTargetName);

			else // any other volume by same name has the same abs path file?
				err = AL_findByAbsPathOnOtherVolume (&apb->vref, aptr, &apb->newParentDir, apb->newTargetName);

		}

		/* All these routines return a hint in "apb->newParentDir" so that we can set
		the "hintParentDirAbs" variable correctly.
		*/

		if (err == fnfErr || err == noErr)
			apb->hintParentDirAbs = apb->newParentDir;

		if (err != noErr) return (err);

		UpdateFileNumber (apb);

		break;
		
	case kFindByRelPath:
		if (apb->fromFile == NULL || aptr->nlvlFrom == kNoRelativePath)
			return (fnfErr);

		err = AL_findByRelPath (apb->fromFile, aptr, &apb->newParentDir, &apb->newFileNum);

		if (err == noErr || err == fnfErr) // found it by relative path OR a hint was returned
			apb->hintParentDirRel = apb->newParentDir;

		if (err == noErr) { 	// found it by relative path

			apb->isVolumeFuzzy = FALSE; // make volume info irrelevant
			apb->vref = apb->fromFile->vRefNum;

		} else

			return (err);

		break;
		
	default:
		return (fnfErr);
		
	} // end of switch (searchKind)

	if ((!shouldFilter && NewCandidate(apb->vref, apb->newParentDir, apb->newTargetName, apb->curCnt, apb->aliasList))
		|| (!FilterThisMatch(apb, kFoundTarget)))
		UpdateAliasListAndFlag (apb);
	
	return (noErr);

} // end of FindTarget routine

/*********************************************************************
 *******	Compute and store a hint for target that was not found ***
 *********************************************************************/
 
static OSErr StoreHint (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer
{
	OSErr err = dirNFErr; 	// result code
	short hintVref;			// if not found, return a VRef hint of where it could have been found
	long hintParentDir = kInvalidValue; // if not found, return a parent dirID  hint of where it could have been found
	long dummyDir;			// dummy place holder

	/* Return the hint of last place where the target used to be.  
	Make sure the directory where target should have been found is a valid directory.
	*/

	if (apb->rulesMask & kARMSearchRelFirst) { // Stored hint should be for relative or absolute path?

		hintVref = apb->fromFile->vRefNum;
		hintParentDir = apb->hintParentDirRel;

	}
	
	if (hintParentDir == kInvalidValue && // If didn't find by relative and direct search is in rule OR no relative in rules
		(apb->rulesMask & (kARMSearch | kARMSearchMore))) {

		hintVref = apb->vref;
		hintParentDir = apb->hintParentDirAbs;

	}

	if (hintParentDir != kInvalidValue) { // either relative or direct search had valid values for hints

		if (hintVref == 0)
			err = nsvErr;
		else {
			if (AL_findDirByID (hintVref, hintParentDir, &dummyDir, NULL) == noErr &&
					(Length(apb->aptr->fileName) > 0)) { // make sure it exists

				err = noErr;
				AddCandidate (hintVref, hintParentDir, apb->aptr->fileName, 
					&apb->curCnt, 1, apb->aliasList);
			}
		}
	}

	return (err);
	
} // end of StoreHint routine


 /*********************************************************************
 ***************		Find file by attributes, recursively **********
 *********************************************************************/

static OSErr FindNextByAttribute (register MatchAliasParamBlk *apb, //I MatchAlias parameter block pointer
						Boolean *foundIt)			//O found a match or not
						
{						
							
	register OSErr err;				// result code
	CInfoPBRec cpb;					// cpb
	Str63 localName;				// ioNamePtr for CPB			

	*foundIt = FALSE;	// assume that won't find the match

	cpb.hFileInfo.ioFDirIndex = apb->curIoFDirIndex;	

	/* Given a directory, search it in order to find a file/directory that match
	certain attributes.  For files, they are file number or (creation date, type and
	creator).  For directories, it is the creation date of the directory.
	*/

	++cpb.hFileInfo.ioFDirIndex;// next file in the directory

	// no more files or a fatal error or found it
	err = AL_getCatInfo (apb->vref, apb->curDir, localName, &cpb);// get next file

	if (err == noErr) {

		if (AttributesMatch(apb->aptr, &cpb, kMatchFileNumber)) { // Is it a good match?

			apb->newParentDir = cpb.hFileInfo.ioFlParID;
			AL_copyPString(localName, apb->newTargetName);
			apb->newFileNum = cpb.hFileInfo.ioDirID;
			*foundIt = TRUE;	// found a match

		}	

		if (cpb.hFileInfo.ioFlAttrib & kDirMask) { // found a directory

			if (apb->curIdx >= (kMaxDirStack-1)) { // ran out of directory stack

				err = memFullErr;		// out of memory
				goto EXIT;

			}else  // add it to directory stack in order to search it later
				(*(apb->dirStack))[apb->curIdx++] = cpb.dirInfo.ioDrDirID;

		}

	} // end of noErr

	else if (apb->curIdx > 0) {	// any more directories remaining to be searched?

		apb->curDir = (*(apb->dirStack))[--apb->curIdx];	// next directory from stack
		cpb.hFileInfo.ioFDirIndex = 0;	// start search at the beginning
		err = noErr;

	} 
	
	apb->curIoFDirIndex = cpb.hFileInfo.ioFDirIndex;

EXIT:
		
	return (err);		
	
} // end of FindNextByAttribute routine

/*********************************************************************
 ***************	Find file(s) or directory(s) by CatSearch	******
 *********************************************************************/

static OSErr FindNextByCatSrch(register MatchAliasParamBlk *apb, //I MatchAlias parameter block pointer
						Boolean *foundIt)			//O found a match or not
						
{						
	OSErr err;						// result code
	CSParam cspb;					// CatSearch parameter block
	CInfoPBRec cpb1, cpb2;			// CatSearch setup catlog param blocks
	FSSpec ans;						// CatSearch return entry
	register AliasPtrPriv aptr; 	// alias record pointer
	unsigned long fixedCrDate;		// fixed creation date for AppleShare

	*foundIt = FALSE;	// assume that won't find the match

	aptr = apb->aptr;

	// set up CatSearch parameter block
	cspb.ioNamePtr = cspb.ioOptBuffer = NULL;
	cspb.ioVRefNum = apb->vref;

	cspb.ioMatchPtr = &ans;
	cspb.ioReqMatchCount = 1;

	cspb.ioSearchBits = fsSBFlAttrib + fsSBFlFndrInfo + fsSBFlCrDat;
	if (aptr->thisAliasKind == kDirAlias)
		// for files, type and creator are important but for folders they are not
		cspb.ioSearchBits -= fsSBFlFndrInfo;

	cspb.ioSearchInfo1 = &cpb1;
	cspb.ioSearchInfo2 = &cpb2;

	cspb.ioSearchTime = KMaxCSrchTimeOut; // length of time to run search
									// later on, may want to refine it depending upon performance

	cspb.ioCatPosition = apb->prec;	// input CatSearch position record
	
	cpb1.hFileInfo.ioNamePtr = NULL; // name was not important
	cpb2.hFileInfo.ioNamePtr = NULL;
	
	cpb1.hFileInfo.ioFlAttrib = (aptr->thisAliasKind == kDirAlias) ? kDirMask : 0;
	cpb2.hFileInfo.ioFlAttrib = kDirMask;// only bit 4 is important
	
	fixedCrDate = AL_fixedCreationDate(nil, apb->vref, aptr->fileCrDate); 
	cpb1.hFileInfo.ioFlCrDat = cpb2.hFileInfo.ioFlCrDat =
			aptr->fileCrDate + (aptr->fileCrDate - fixedCrDate);

	if (cspb.ioSearchBits & fsSBFlFndrInfo) { 

		// for files, type and creator are important
		cpb1.hFileInfo.ioFlFndrInfo.fdType = aptr->fileType;;
		cpb1.hFileInfo.ioFlFndrInfo.fdCreator = aptr->fdCreator;

		// create the mask bits so that only type and creator are important.
		cpb2.hFileInfo.ioFlFndrInfo.fdType = 0xFFFFFFFF;
		cpb2.hFileInfo.ioFlFndrInfo.fdCreator = 0xFFFFFFFF;
		cpb2.hFileInfo.ioFlFndrInfo.fdFlags = 0;
		cpb2.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
		cpb2.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
		cpb2.hFileInfo.ioFlFndrInfo.fdFldr = 0;

	}
		
	err = PBCatSearch (&cspb, false);
	if (err == noErr || err == eofErr) { //if reached end of volume, it is ok

		if (cspb.ioActMatchCount == 1) {

			apb->newParentDir = ans.parID;
			AL_copyPString(ans.name, apb->newTargetName);
			UpdateFileNumber (apb);
			*foundIt = TRUE;	// found a match

		}

		if (err == noErr) // new position not meaningful for eofErr or fnfErr
			apb->prec = cspb.ioCatPosition;	// return the new position

	} else 

		apb->CatSearchAvailable = FALSE;

	return (err);			
	
} // end of FindNextByCatSrch routine

/*********************************************************************
 ***************	search a volume for an alias match	**************
 *********************************************************************/

static void SearchOneVolume (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{	
	OSErr err;						// result code
	Boolean foundIt=FALSE;			// found the next match by slow search or not?
	unsigned long startCount;		// remember ticks when volume search starts
	unsigned long currentCount;		// value of current ticks
		
	if (apb->quitFlag || apb->aptr->fileNum == kInvalidValue) // no valid data to match against
		return;

	apb->prec.initialize = 0; // from beginning of volume 
	apb->CatSearchAvailable = TRUE; // assume it is available
	apb->curDir = kRootDirID; // current directory that we are searching, start at root
	apb->curIdx =  0;		  // current index into directory stack
	apb->curIoFDirIndex =  0; // current index into the directory being searched
	apb->dirStack = NULL; 
	startCount = TickCount();	// remember when volume search started

	do {

		if (apb->aptr->parDirID != kVolRootDirID) { // not a volume alias, file/directory alias

			if (apb->CatSearchAvailable)
				err = FindNextByCatSrch (apb, &foundIt);
	
			// FindNextByCatSrch will set apb->CatSearchAvailable & hence the check and not else
			if (!apb->CatSearchAvailable) { 
	
				if (apb->dirStack == NULL) {
	
					apb->dirStack = (LongsHandle)(NewHandle(sizeof(long)*kMaxDirStack));
					if (apb->dirStack == NULL) // can't do the search
						return;
	
				}
	
				err = FindNextByAttribute (apb, &foundIt);
			}
		} else { // handle volume aliases specially since CatSearch and recursive index search can't handle them
		
			HVolumeParam vpb;		// volume parameter block

			vpb.ioNamePtr =  apb->newTargetName;
			vpb.ioVolIndex = 0;				// find by vRefNum mode
			vpb.ioVRefNum = apb->vref;		// put the vref in
		
			if (PBHGetVInfo ((HParmBlkPtr)&vpb, FALSE) == noErr && 
				apb->aptr->volumeCrDate == AL_fixedCreationDate(&vpb, vpb.ioVRefNum, vpb.ioVCrDate)) { // found a match

				apb->newParentDir = apb->aptr->parDirID;
				apb->newFileNum = apb->aptr->fileNum;
				foundIt = TRUE;
			}
			err = fnfErr;	// force a quit from the while loop
		}

		currentCount = TickCount();

		if (foundIt || (currentCount >= (startCount + kCancelWait))) {

			if (!FilterThisMatch (apb, foundIt) && foundIt) 
				UpdateAliasListAndFlag (apb);

			startCount = TickCount(); // restart the count

		}

	} while (apb->curCnt < apb->aliasCount && (! apb->quitFlag) && err == noErr);

	if (apb->dirStack != NULL)	// dispose directory stack
		DisposHandle ((Handle)apb->dirStack);

} // end of SearchOneVolume routine

/*********************************************************************
 ***************	search all mounted volumes for an alias match	**
 *********************************************************************/

static void SearchAllVolumes(Boolean vrefSrchFlg,	//I passed vref is already searched, ignore it		
						short vref,					//I volume reference number/WD ref
						register MatchAliasParamBlk *apb, //I MatchAlias parameter block pointer
						unsigned long rulesMask,	//I search rules mask , fast or slow or both?
						MatchAliasSearchKind fastSearchKind) //I what kind of fast search?
												
{	
	register VCB *vcbPtr;			// VCB pointer
	
	vcbPtr = (VCB *)GetVCBQHdr()->qHead;

	while (vcbPtr != NULL) {

		if ((vrefSrchFlg != kDontSrchVref) || (vref != vcbPtr->vcbVRefNum)) {
		// ignore the passed vref if the flag says so, it is already searched

			apb->vref = vcbPtr->vcbVRefNum;

			if (rulesMask & kARMSearch) 
				(void) FindTarget (fastSearchKind, apb);

			if (rulesMask & kARMSearchMore) // do slow search by CatSearch
				SearchOneVolume (apb); 

			if (apb->curCnt >= apb->aliasCount || apb->quitFlag) // filled the buffer 
				break;

		}

		vcbPtr = (VCB *) vcbPtr->qLink;

	}
		
} // end of SearchAllVolumes routine

/*********************************************************************
 ***************	Are we trying to mount ourself?	******************
 *********************************************************************/

Boolean AL_isItSelf (register AliasPtrPriv aptr,	// pointer to locked AliasRecord
					Boolean *needsUpdate)	// Does the alias record need updating?

{
   	Str32  		zoneName; // my zone name
   	Str31   	serverName;	// my server name
	Boolean		IsCurrentZoneStar; // Is current zone a '*'?
	Boolean		IsAliasZoneStar; // Is zone in alias record a '*'?
	char 		*zoneNamePtr; // pointer to zone name from alias record

	AL_GetMyZonePhs2(zoneName);
	AL_GetMyServer(serverName);

	/* If alias record or current zone says local zone, assume zone already matches.
	But tell the client to update the record to get real zone if possible. */

	IsCurrentZoneStar = Length(zoneName) == 1 && zoneName[1] == '*';

	zoneNamePtr = AL_getAFPinfo (aptr, asiZoneName);

	IsAliasZoneStar = Length(zoneNamePtr) == 1 && ((*(zoneNamePtr+1)) == '*');

	if (IsAliasZoneStar && (!IsCurrentZoneStar)) // force the update of alias record to get real zone name
		*needsUpdate = TRUE;

	return ((IsCurrentZoneStar || IsAliasZoneStar || FSEqualString(zoneName, zoneNamePtr)) 
		&& FSEqualString(serverName, AL_getAFPinfo (aptr, asiServerName)));
	
} // end of AL_isItSelf routine

/*********************************************************************
 ***************	Get Next Shared Folder ***************************
 *********************************************************************/

static OSErr GetNextSharedFolder (short index, //I index for the folder to be got
							CInfoPBPtr cpbPtr) // catalog parameter block pointer

{
	OSErr err;			// result code
	ParamBlockRec sharedFolderInfo;
	HVolumeParam vpb;	// volume parameter block
	
	sharedFolderInfo.cntrlParam.ioNamePtr = NULL;
	sharedFolderInfo.cntrlParam.csCode = SCGetExpFldr;
	sharedFolderInfo.cntrlParam.csParam[0] = index;
	err = GetExportedFolderInfo(&sharedFolderInfo, FALSE);

	if (err == noErr) {

		cpbPtr->hFileInfo.ioFDirIndex	= -1; // interested in directory info only
		err = AL_getCatInfo (sharedFolderInfo.cntrlParam.ioVRefNum,
				*((long *)&sharedFolderInfo.cntrlParam.csParam[1]),
				cpbPtr->hFileInfo.ioNamePtr, cpbPtr);

		if (err == noErr && cpbPtr->hFileInfo.ioFlParID == kVolRootDirID) {

			/* Return the volume creation date in the cpb and not the root dir's creation date
			for the entire volume itself as the SharePoint.  File System treats them both differently.
			*/
			if (AL_findVolByName (cpbPtr->hFileInfo.ioNamePtr, &vpb) == noErr)
				cpbPtr->hFileInfo.ioFlCrDat = vpb.ioVCrDate;

		}
	}
	
	return (err);
	
} // end of GetNextSharedFolder routine

/*********************************************************************
 ***************	Switch alias record to auxiliary info ************
 *********************************************************************/

static void SwitchToAux (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{
	register AuxAliasInfo *apbAuxPtr;	// pointer to locked AliasRecord
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord
	char 		*aliasAuxPtr;		// auxiliary info pointer in alias record
	short 		aliasAuxLen;		// length of auxiliary alias record

	aptr = apb->aptr;
	apbAuxPtr = &apb->auxAliasRecord;

	AL_copyPString (aptr->volumeName, apbAuxPtr->auxVolName);
	apbAuxPtr->auxVolCrDate = aptr->volumeCrDate;
	apbAuxPtr->auxVolType = aptr->volumeType;
	apbAuxPtr->auxDirIDs.auxParDirID = aptr->parDirID;
	apbAuxPtr->auxDirIDs.auxFileNum = aptr->fileNum;

	aliasAuxPtr = AL_getVarPtr(aptr, kAuxRemoteAlias, &aliasAuxLen);
	AL_changeToAuxInfo (aptr, (AuxAliasInfo *)aliasAuxPtr);
	apb->hintParentDirAbs = aptr->parDirID;

	apb->auxAliasSwitch = TRUE;

} // end of SwitchToAux routine


/*********************************************************************
 ***************	Do fast search on multiple volumes ***************
 *********************************************************************/

static void FastSearchAllVolumes (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord
	short prevCnt;				// remember current count of found candidates

	if (apb->rulesMask & kARMSearch) { // Does rulesMask allow fast search?

		aptr = apb->aptr;

		apb->isVolumeFuzzy = TRUE;  // the found volume would be fuzzy, force extra checks
		apb->volumeAliasFuzzy = TRUE;  // force extra checks for volume aliases themselves
	
		prevCnt = apb->curCnt;

 		/* Fast search on multiple volumes mean looking for it first by ID and then by name
		in parent directory.  Also, caller controls whether extra checks are done or not
		by setting the isVolumeFuzzy and volumeAliasFuzzy flags of MatchAlias parameter block.
		*/

		SearchAllVolumes ((!kDontSrchVref), apb->vref, apb, kARMSearch, kFindByID);

		if (apb->curCnt < apb->aliasCount && (!apb->quitFlag)) 
			SearchAllVolumes ((!kDontSrchVref), apb->vref, apb, kARMSearch, kFindByName);
	
		if (prevCnt != apb->curCnt)
			apb->needsUpdate = TRUE;

		apb->searchedAllVols = TRUE;

	}

} // end of FastSearchAllVolumes routine


/*********************************************************************
 ***************	Handle a shared volume (FileShare like)	**********
 *********************************************************************/

static OSErr HandleSharedVolume (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{
	OSErr resultErr=noErr;		// result code
	OSErr err;					// result code
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord
	CInfoPBRec cpb;				// catalog parameter block
	Str27 localName;			// temporary local volume name
	register short index = 0;	// index through all shared folders	
	short auxLen;				// length of auxiliary real parent dirID
	AuxAliasDirIDs *realDirPtr;	// pointer to real directory ID info

	aptr = apb->aptr;

	if (AL_isItSelf(aptr, &apb->needsUpdate)) { // on the server side

		if (aptr->volumeAttributes & kAuxRealDirID && aptr->volumeType == kVolForeign) { 

			// on server and accessing remote alias
			/* Get info about all currently shared folders and see if any of them is the
			correct target.  If could not identify the target volume, the only thing we can
			do is quick search on multiple volumes for the target.
			*/

			apb->auxAliasRecord.auxDirIDs.auxParDirID = aptr->parDirID;
			apb->auxAliasRecord.auxDirIDs.auxFileNum = aptr->fileNum;

			realDirPtr = (AuxAliasDirIDs *) AL_getVarPtr(aptr, kAuxRealDirID, &auxLen);
			aptr->parDirID = realDirPtr->auxParDirID;
			aptr->fileNum = realDirPtr->auxFileNum;

			apb->auxRealDirSwitch = TRUE;

			do {

				cpb.hFileInfo.ioNamePtr	= localName;
				err = GetNextSharedFolder(++index, &cpb);
				if (err == noErr) {

					if (FSEqualString (localName, aptr->volumeName) && 
						cpb.hFileInfo.ioFlCrDat == aptr->volumeCrDate) {

						apb->vref = cpb.hFileInfo.ioVRefNum;
						apb->needsUpdate = TRUE;

						break;
					}
				}

			} while (err == noErr);
			
			/* FileShare is not running or can't locate the correct volume.  
			Force a search on all mounted volumes as the last recourse.
			*/
			if (err != noErr) {
			
				FastSearchAllVolumes(apb);

				/* Already did fast search on all mounted volumes on the server locally. 
				No more volumes to search or mount, so get out.
				*/

				resultErr = nsvErr;
			}
		}

	} else { // we are on remote machine (at least we think so)

		// on client workstation using alias created on server

		if (aptr->volumeAttributes & kVolAuxRemoteInfo && aptr->volumeType != kVolForeign) {

			SwitchToAux (apb); // Is the shared folder itself mounted?
			if (AL_checkIfMounted(aptr, &apb->vref, &apb->needsUpdate) != noErr) {
			
				AL_changeToAuxInfo (aptr, &apb->auxAliasRecord);
				apb->hintParentDirAbs = aptr->parDirID;

				if (AL_checkIfMounted(aptr, &apb->vref, &apb->needsUpdate) != noErr)  { // Is the entire volume mounted?

					SwitchToAux (apb);	// back to primary search info in this case	
					if ((apb->rulesMask & kARMMountVol) && (aptr->volumeType != kInvalidValue) &&
						(aptr->volumeAttributes & kVolMntExists))
						resultErr = AL_mountForeign(aptr, &apb->vref, &apb->needsUpdate, 
							apb->rulesMask & kARMNoUI ? kNoUserInterface : kUserInterface, &apb->auxAliasRecord);
				}
			}
		}
	}

	return (resultErr);
		
} // end of HandleSharedVolume routine

/*********************************************************************
 ***************	Post processing of returned needsUpdate flag	**
 *********************************************************************/

static void PostProcessNeedsUpdateFlag (register MatchAliasParamBlk *apb) //I MatchAlias parameter block pointer

{
	if (apb->curCnt > 1) // if returned multiple answers, set the flag.
		apb->needsUpdate = TRUE;

	/* If relative has first priority, but found by absolute, set needsUpdate flag. */
	if ((!apb->needsUpdate) && apb->curCnt > 0)
		{
		if ((apb->aptr->nlvlFrom != kNoRelativePath) && 
			(apb->rulesMask & kARMSearchRelFirst) && 
			(apb->rulesMask & kARMSearch) && 
			(!apb->foundByRelativeFirst) &&
			(apb->fromFile->vRefNum == apb->aliasList[0].vRefNum))
				apb->needsUpdate = TRUE;
		}

	if (apb->aptr->fileNum == kInvalidValue) // never force update of minimal aliases
		apb->needsUpdate = FALSE;

} // end of PostProcessNeedsUpdateFlag routine		

/**********************************************************************
 ***************		Match an alias	*******************************
 *********************************************************************/

pascal OSErr AL_matchAlias(const FSSpec *fromFile, //I aliased from file
						unsigned long rulesMask,		//I search rules mask
						const AliasHandlePriv alias,	//I alias record handle 
						short *aliasCount,				//I/O # of entries in alias list
						FSSpecPtr aliasList,			//I/O alias list buffer
						Boolean *needsUpdate,			//O does alias needs updating?
						AliasFilterProcPtr aliasFilter, //I filter proc for alias match
						Ptr yourDataPtr)				//I a callback param passed to filter proc
{
	OSErr err; 					// result code
	HVolumeParam vpb;			// volume parameter block
	char masterFlags;			// flags to remember before locking and unlocking handles
	MatchAliasParamBlk apb;		// parmater block for passing to internal routines	
	register AliasPtrPriv aptr;	// pointer to locked AliasRecord
	short prevCnt;				// remember current count of found candidates
	short prevVref;				// remember vRefNum before multiple volume search
	unsigned long searchBits;	// search rules bits from rulesMask
	short prevSlowSearchedVref = 0; // remember the volume that was already slow searched if any

	apb.needsUpdate = FALSE;	// assume update not needed


	/* Validate the input parameters. */
	
	searchBits = (rulesMask & (kARMSearch | kARMSearchMore | kARMSearchRelFirst));
	if (aliasList == NULL || 	// can't allow null answer buffer
		*aliasCount < 1   || 	// can't ask for 0 or negative matches
		searchBits == 0   ||	// have to specify at least one search strategy
		(fromFile == NULL && searchBits == kARMSearchRelFirst)  ||  // No fromFile but only relative search specified
		(AL_validateHandle(alias) != noErr)) { // Is it a handle to invalid alias record?

		return (paramErr);
	}


	/* Lock the alias handle and remember the alias record pointer. */

	masterFlags = HGetState ((Handle)alias); // remember master flags
	HLock((Handle)alias);		// lock the passed alias record
	aptr = apb.aptr = *alias;	// get alias record pointer
	
	/* set up the MatchAlias parameter block. */

	apb.fromFile = fromFile;
	apb.rulesMask = rulesMask;
	apb.aliasCount = *aliasCount;
	apb.aliasList = aliasList;
	apb.aliasFilter = aliasFilter;
	apb.yourDataPtr = yourDataPtr;
	apb.curCnt = 0;
	apb.quitFlag = FALSE;
	apb.isVolumeFuzzy = FALSE;
	apb.foundByName = FALSE;
	apb.foundByRelativeFirst = FALSE;
	apb.hintParentDirAbs = aptr->parDirID;
	apb.hintParentDirRel = kInvalidValue;
	apb.vref = 0;
	apb.auxAliasSwitch = FALSE;
	apb.auxRealDirSwitch = FALSE;
	apb.searchedAllVols = FALSE;
	apb.volumeAliasFuzzy = FALSE;



	/* if relative search first specified, give it priority over absolute search. */

	if (apb.rulesMask & kARMSearchRelFirst) { 

		err = FindTarget (kFindByRelPath, &apb);
		if (err == noErr && apb.curCnt != 0) {		

			apb.foundByRelativeFirst = TRUE;
			// for return buffer yet not full, continue search by falling through
			mExitIfQuitOrBufFull(apb.quitFlag, apb.curCnt, *aliasCount); // if buffer full or client quit , exit

		}			

		/* Optimization for no other than relative search requested. */
		if (searchBits == kARMSearchRelFirst)
			goto EXIT;


		apb.vref = 0; // force an auto mount and volume search since we are not done yet!
	}



	/* Handle the FileShare cases here.  For such volumes,
	the actual information is stored in the auxiliary area.  Hence, it is crucial to
	set up the alias record data correctly before we start processing the alias record.
	*/

	if (AL_isAFPmedia(aptr) && 
		(aptr->volumeAttributes & kVolAuxRemoteInfo || aptr->volumeAttributes & kAuxRealDirID)) {

		err = HandleSharedVolume(&apb);
		if (err != noErr) goto EXIT;

	}

	

	/* Locate the target volume. */

	if (apb.vref == 0) { 

		/* was not a shared volume or correct volume is not located yet. */

		err = AL_findVolume (aptr, apb.rulesMask, &apb.vref, &apb.needsUpdate, 
					apb.auxAliasSwitch ? (&apb.auxAliasRecord) : NULL);
		if (err != noErr) goto EXIT;

	}



	/* There is a concept of a fuzzy volume.  If the found volume's creation date does
	not match, it is possible that it is the wrong volume.  In this case, we set the
	fuzzy flag.  This allows later on alias matching to be more restrictive.
	Also, AppleShare foreign volumes are not counted as fuzzy evenif the creation date
	is different (since zone and server and volume names matched, it is OK. 
	*/

	if (apb.needsUpdate && (!AL_isAFPmedia(aptr))) { 

		/* means that found the volume but something about the volume has changed */
		err = AL_findVolByVRefNum (apb.vref, &vpb); // make sure volume is still mounted
		if (err != noErr) goto EXIT;
		if (vpb.ioVCrDate != aptr->volumeCrDate)
			apb.isVolumeFuzzy = TRUE;  // the found volume is fuzzy

	}



	/* Now do the search for target itself. */

	if (apb.rulesMask & kARMSearch) {  // if quick search specified
	
		// Find file by fileID or directory by dirID
		err = FindTarget (kFindByID, &apb);
		if (err == noErr) {		
			// for return buffer yet not full, continue search by falling through
			mExitIfQuitOrBufFull(apb.quitFlag, apb.curCnt, *aliasCount); // if buffer full or client quit , exit
		}			
		
		// try finding by name in the parent directory
		err = FindTarget (kFindByName, &apb);
		switch (err) {

		case noErr:
			/* Found by name in the same parent directory and fileNumber is the same.
			No need to look further by absolute path search or fileNumber search 
			evenif multiple matches requested.
			*/

			if (apb.newFileNum == aptr->fileNum || aptr->fileNum == kInvalidValue
					|| aptr->thisAliasKind == kDirAlias)
				break;

			// If found by name but file number does not match, continue
			// search by file number as a renamed file. But remember the fact
			// that a match by name was found.

			if (apb.quitFlag) goto EXIT;

			apb.foundByName = TRUE;
				
		case fnfErr:
			if (aptr->thisAliasKind == kFileAlias) { 

				// how about in parent directory as a renamed file?
				err = FindTarget (kFindByNum, &apb);
				if (err == noErr) {		
					mExitIfQuitOrBufFull(apb.quitFlag, apb.curCnt, *aliasCount); // if buffer full or client quit , exit
				}

			}
			// fall through for files and directories to find them by absolute path
			
		case dirNFErr: // may be backed up and restored. dirIDs will be different.
			// search by absolute path
			err = FindTarget (kFindByAbsPath, &apb);
			if (err == noErr) {	// found it, return the result
				mExitIfQuitOrBufFull(apb.quitFlag, apb.curCnt, *aliasCount); // if buffer full or client quit , exit
			}
			break;				

		default:
			break;
			
		} // end of switch of finding by name
				
	} // end of quick search for a file or a directory



	/* search further using slow search. */
	if (apb.rulesMask & kARMSearchMore) {
		prevSlowSearchedVref = apb.vref;
		SearchOneVolume (&apb);
	}


EXIT:


	/* Do the post processing and set up return parameters correctly.
	No need for relative or multiple volume search if user canceled out of 
	the entire operation.
	*/

	/* if relative search requested, do it now.  Do it only if buffer is not full
	and the previous client filtering did not tell us to quit. Also, if the
	relative search had priority over absolute search, it was already done.
	*/

	if ((apb.curCnt < *aliasCount) && (!apb.quitFlag) && 
			(err != userCanceledErr) && (!(apb.rulesMask & kARMSearchRelFirst))) {

		/* Do not override nsvErr.  If kARMSearchRelFirst bit was set, that was already done.
		But it is not set and absolute search had priority.  In such a case, if we can't
		find by relative search, return whatever error was last set during abs search.
		*/

		prevCnt = apb.curCnt;
		(void) FindTarget (kFindByRelPath, &apb); // no error checking here
		if (prevCnt != apb.curCnt)
			apb.needsUpdate = TRUE;	// did not find absolute, but found relative

	} // end of fast relative search


#ifdef FORCE_MULTIPLEVOL_SEARCH
	/* Also, if we have not located or auto mounted the correct target volume and if a fast search 
	was not already done on all mounted volumes (i.e. FileShare case via HandleSharedVolume) we
	can do it here by pretending that the kARMMultVols bit was set in rulesMask.  This could
	detect aliases in case when aliases were created while FileShare was not running and then
	any of parent folders were shared later on.
	*/
	if (err == nsvErr && (!apb.searchedAllVols) && (apb.rulesMask & kARMMountVol))
		apb.rulesMask |= kARMMultVols;
#endif FORCE_MULTIPLEVOL_SEARCH
	

	/* If the multiple volume search bit is set, now is the time to do it.
	Do it only if the input buffer is not full and quitFlag is not set.
	For minimal alases, no CatSearch for creation date, type
	and creator since these are invalid values.
	*/	
	
	if ((apb.curCnt < *aliasCount) && (!apb.quitFlag) && 
		(err != userCanceledErr) && (aptr->fileNum != kInvalidValue)) { 

		if (apb.rulesMask & kARMMultVols) {
		
			prevCnt = apb.curCnt;
			prevVref = apb.vref; // volume on which the original target was searched
			FastSearchAllVolumes (&apb);
				
			if ((apb.rulesMask & kARMSearchMore) && (apb.curCnt < *aliasCount) && (!apb.quitFlag) ) 
				SearchAllVolumes ((prevSlowSearchedVref != 0) ? kDontSrchVref : (!kDontSrchVref),
						prevSlowSearchedVref, &apb, kARMSearchMore, kFindByNum /* this is ignored. */);
						
			if (prevCnt != apb.curCnt) // found on a different volume
				apb.needsUpdate = TRUE;
			apb.vref = prevVref; // restore original target volume
		}
	}



	/* Set the returned needsUpdate flag correctly. */
	
	PostProcessNeedsUpdateFlag (&apb);


	/* Set the return error . */
	if (apb.curCnt)
		err = noErr;			// if at least one match returned, no error

	else if (err == noErr || err == fnfErr || // target not found, zero match during search
				(apb.vref != 0 && err == afpAccessDenied)) { 

		/* return the hint of last place where the target used to be.  In such a case,
		error returned is still fnfErr.  The returned count is one if the directory
		where target should have been found is a valid directory.
		*/
		err = StoreHint(&apb);
		/* Eventhough count returned is one, returned answer is not valid.  
		It is just a hint as to where the target used to be.
		*/
		if (err == noErr) {
			err = fnfErr; 
			apb.needsUpdate = FALSE;  // for hints, needsUpdate flag has to be false
		}
	}


	/* for shared volumes on client side, we had changed info in the alias record.
	set it back to the original.*/

	if (apb.auxAliasSwitch) 
		AL_changeToAuxInfo(aptr, &apb.auxAliasRecord);

	if (apb.auxRealDirSwitch) {
		aptr->parDirID = apb.auxAliasRecord.auxDirIDs.auxParDirID;
		aptr->fileNum = apb.auxAliasRecord.auxDirIDs.auxFileNum;
	}


	*aliasCount = apb.curCnt;		// return found alias count and needsUpdate flag
	*needsUpdate = apb.needsUpdate;

	HUnlock((Handle)alias);		// unlock the alias record
	HSetState((Handle)alias, masterFlags); // restore original state
	
	return (err);
	
} // end of MatchAlias routine


 /*********************************************************************
 ***************		Canonify a file name	***********************
 *********************************************************************/

pascal OSErr AL_canonifyFile (	short vRefNum,			//I volume ref #/working dir
							long dirID,					//I directory ID
							const Str255 *fileName,		//I file name
							FSSpec *canonicalFile) //O returned canonical form
{
#ifndef FSMakeFSSpec_FOR_MFS_WORKS

	if (canonicalFile == NULL)
		return (paramErr);
	else
		return (FSMakeFSSpec (vRefNum, dirID, (ConstStr255Param)fileName, canonicalFile));

#else

	OSErr		err;				// result code
	long 		tdir;				// temporary directory ID
	long 		fnum;				// file number
	HVolumeParam 	vpb;			// volume parameter block
	Boolean		noFileName;			// input filename is null or empty
	Boolean		isFullPath=FALSE;	// input filename is fullpath or not

	if (canonicalFile == NULL)
		return (paramErr);

	/* Try to use the file system call if it works.  It does not support MFS volumes. */
	if (FSMakeFSSpec (vRefNum, dirID, (ConstStr255Param)fileName, canonicalFile) == noErr)
		return (noErr);

	noFileName = ((fileName == NULL) || (Length(fileName) == 0));

	canonicalFile->parID = dirID;	// use client supplied directory ID

	if (!noFileName) {		// fileName parameter is valid
	
		AL_getFilename ((unsigned char *)fileName+1, (Length(fileName) <= (sizeof(Str63)-1)) ? Length(fileName) : (sizeof(Str63)-1), 
				canonicalFile->name); // get the filename component
		isFullPath = AL_isFullpath((unsigned char *)fileName+1, Length(fileName));
		
	}

	if (isFullPath) {	// fullpath is specified in fileName parameter, decode it.

		Str255		localVolumeName;	// temporary local volume name

		// fullpath is a special case where correct volume ref number needs to be computed

		AL_getVolname ((unsigned char *)fileName+1, Length(fileName), localVolumeName); // get volume name
		if ((err = AL_findVolByName (localVolumeName, &vpb)) != noErr)
			goto EXIT;
		canonicalFile->vRefNum = vpb.ioVRefNum;	// returned vRefNum

		// for full paths, start with root dirID
		canonicalFile->parID = (vpb.ioVSigWord != kFlatVolume) ? kVolRootDirID : kRootDirID;

		// Handle the special case for Canonify called for a volume.  See if the passed
		// filename is indeed a volume name.

		if (FSEqualString ((Str255)fileName, (Str255)localVolumeName)) {

			// special case, canonify called for a volume
			canonicalFile->parID = kVolRootDirID;
			*canonicalFile->name = Length(localVolumeName) -1; // do not copy separator char
			BlockMove (localVolumeName+1, canonicalFile->name+1, (*canonicalFile->name));
			goto EXIT;		// done with special case of volume

		}

	} else { // not a fullpath specification

		WDPBRec		wdpb;		// working directory parameter block

		wdpb.ioVRefNum	= vRefNum;			// this working direcotry
		wdpb.ioWDIndex	= 0;
		wdpb.ioWDProcID	= 0;
		wdpb.ioWDVRefNum = 0;
		wdpb.ioNamePtr = NULL;

		if ((err = PBGetWDInfo(&wdpb, FALSE)) != noErr) // info about this one
			goto EXIT;

		canonicalFile->vRefNum = wdpb.ioWDVRefNum;	// returned vRefNum

		if (dirID == 0)								// override only if not specified
			canonicalFile->parID = wdpb.ioWDDirID;	// returned dirID

		if (noFileName) 
			err = AL_findDirByID (canonicalFile->vRefNum, canonicalFile->parID, 
					&canonicalFile->parID, canonicalFile->name);

	}
	

	/* If our volume is a MFS volume, no need to compute correct parent dirID
	for partial or full path names since the whole structure is flat. */

	if (err == noErr) { // everything was OK so far

		if (!isFullPath) {

			err = AL_findVolByVRefNum(canonicalFile->vRefNum, &vpb);
			if (err != noErr) goto EXIT;

		}

		if (vpb.ioVSigWord != kFlatVolume) {

			Str255		localPartialName;	// temporary local partial pathname

			/*If just a leaf name was specified as the fileName parameter, we are done.
			If a partial or full pathname was specified, we still need to compute the
			correct parent directory ID for the file or directory name. */

			if ((!noFileName) && (! FSEqualString ((Str255)fileName, canonicalFile->name))) {

				AL_stripFilename ((unsigned char *)fileName+1, Length(fileName), localPartialName);
				err = AL_findByName (canonicalFile->vRefNum, canonicalFile->parID, 
							localPartialName, &canonicalFile->parID,
							&fnum);

				if (err == noErr) { // the directory does exist

					if (Length(canonicalFile->name) == 0) // case of name specified ending in a colon
						err = AL_findDirByID (canonicalFile->vRefNum, canonicalFile->parID, 
							&canonicalFile->parID, canonicalFile->name); // compute its name

				}
			}
		}
	}


	if (err != noErr) { // if the directory does not exist, return that error

		if (err == fnfErr) //File system returns fnfErr for directory not existing in this case
			err = dirNFErr;
		goto EXIT;

	} else {

		/* return correct error if file is non-existent.  If file does not exist, the
		canonical form is returned correctly but fnfErr is returned as the function
		result. Factor out volume case for MFS volumes because getting volume info by name
		does not work for them if dirID is kVolRootDirID. */
		
		if (vpb.ioVSigWord != kFlatVolume || canonicalFile->parID != kVolRootDirID) 
			err = AL_findByName (canonicalFile->vRefNum, canonicalFile->parID, 
					canonicalFile->name, &fnum, &tdir);

	}

EXIT:

	return (err);
	
#endif FSMakeFSSpec_FOR_MFS_WORKS

} // end of canonifyFile routine

 /*********************************************************************
 ***************		Update an alias record	***********************
 *********************************************************************/
 pascal OSErr AL_updateAlias (const FSSpec *fromFile, 	//I aliased from file
						const FSSpec *target,			//I aliased to file
						AliasHandlePriv alias,			//I alias record handle
						Boolean *wasChanged)			//O did alias record change?
{

	return (AL_fillAlias (fromFile, target, !kCreateAlias, alias, wasChanged, kCompleteAlias, NULL));

} // end of UpdateAlias routine


 /*********************************************************************
 ***************	get information from an alias record **************
 *********************************************************************/

pascal OSErr AL_getAliasInfo(	const AliasHandlePriv alias, //I alias record handle 
								AliasInfoType index,	//I index specifying requested information
								Str63 theString)		//O returned info as string

{
	register AliasPtrPriv aptr;	// alias record pointer
	short len = 0;				// variable info datalength
	register short i;			// for loop index
	char *infoPtr;				// ptr to data being returned
	short nchInAPath;			// # of chars in absolute path
	short ndirToRoot;			// # of dirs to Root for the alias
	register char *s, *t;		// temporary character pointers

	if (index < asiZoneName || (AL_validateHandle(alias) != noErr))
		return (paramErr);

	aptr = *alias;				// get alias record pointer
		
	if (index == asiAliasName) 			// alias name
		AL_copyPString(aptr->fileName, theString);

	else { // not an alias name

		if (index == asiVolumeName) {	// volume name

			len = Length(aptr->volumeName); 
			infoPtr = aptr->volumeName + 1;

		}

		else { // data comes from variable length info part

			if (index >= asiParentName) { // some other parent folder name

/* Example: vol:lvl1:lvl2:afile will have ndirToRoot = 2, lvl1 and lvl2.  Index of 1
will return lvl2. Index of 2 will return lvl1 and index of >= 3 will return empty string. */

				infoPtr =  AL_getVarPtr (aptr, kDirIDs, &ndirToRoot); 
				ndirToRoot = (ndirToRoot >> 2); // since dirIDs are long, this is equivalent to
												// division by sizeof(long);
				if (index > ndirToRoot) 		// return empty string
					len = 0;					

				else { // return a parent 

					s = t = AL_getVarPtr (aptr, kAbsPath, &nchInAPath); // pointer to absolute path name
					s += nchInAPath - 1;		// point to the last character in abs path

					for (i=index-1; i>=0; --i) { // locate separator char index times
					// locate a separator character index times
						while (*s-- != kChrSeparator && s > t)
							;					// do nothing
					}

					if (s > t) {				// now locate the separator character before found name

						infoPtr = s;			// before the separator character

						while (*infoPtr != kChrSeparator && infoPtr > t)
							--infoPtr;		

						if (infoPtr > t) {		// found the name to be returned

							len = s - infoPtr;
							++infoPtr;			// point to the first cahracter of name

						}
					} // end of (s>t)

				} // end of return a parent

			} // some other parent folder name

			else if (index == asiZoneName || index == asiServerName) { // zone name or server name

				if (AL_isAFPmedia(aptr)) {

					infoPtr =  AL_getAFPinfo (aptr, index);
					len = Length(infoPtr);
					++infoPtr;

				}
			}

		} // data comes from variable length info part
		
		*theString = len;
		if (len > 0)
			BlockMove (infoPtr, theString+1, len);

	} // end of not an alias name
			
	return (noErr);

} // end of routine AL_getAliasInfo

/**********************************************************************
 ***************		Follow a Finder alias	***********************
 *********************************************************************/

pascal OSErr AL_FollowFinderAlias(const FSSpec *fromFile, 	//I aliased from file 
						AliasHandle alias,				//I/O alias record handle 
						Boolean logon,					//I is auto logon allowed?
						FSSpec *target, 				//O resolved alias FSSpec
						Boolean *wasChanged)			//O did any thing change?
{
	OSErr err; 				// result code
	short aliasCount = 1;	// alias list length
	unsigned long logonMask;// logon mask depending upon logon param
	FSSpec fromSpec;
	
	logonMask = logon ? kARMMountVol : 0;
	fromSpec = (*fromFile);

	// This is same as calling ResolveAlias except we do not want auto update of alias record so that we can
	// ignore the same folder case.
	err = MatchAlias(&fromSpec, kARMSearchRelFirst | kARMSearch | logonMask, alias, &aliasCount, target, wasChanged, nil, nil);

	// If found target is in the same folder, ignore it. This leads to wrong targets because
	// all aliases of Finder have same relative path of being in same folder initially.
	if (err == noErr && fromSpec.vRefNum == target->vRefNum && fromSpec.parID == target->parID) {

		aliasCount = 1;
		err = MatchAlias(&fromSpec, kARMSearch | logonMask, alias, &aliasCount, target, wasChanged, nil, nil);

		// If this direct search found the original fromSpec itself, return error
		if (err == noErr && fromSpec.vRefNum == target->vRefNum &&
			fromSpec.parID == target->parID && FSEqualString(fromSpec.name, target->name))
			err = fnfErr;
	}

	if ((err == noErr) && *wasChanged) // MatchAlias tells us that it needs updating
		err = UpdateAlias(&fromSpec, target, alias, wasChanged);

	return (err);
	
} // end of FollowFinderAlias routine

 /*********************************************************************
 ************ Get FSSpec of next resolved Finder alias	***************
 *********************************************************************/

static OSErr GetNextSpec(register FSSpec *theSpec)
{
	register OSErr			err = fnfErr;
	register short			aliasResFile;
	register AliasHandle	alias;
	Boolean					aliasChanged;
	short					resLoadState;
	
	resLoadState = *(short *)ResLoad;

	SetResLoad(FALSE);
	aliasResFile = HOpenResFile(theSpec->vRefNum, theSpec->parID, &theSpec->name, fsCurPerm);

	if( aliasResFile > 0 )
	{
		SetResLoad(TRUE);
		
		UseResFile(aliasResFile);
		alias = (AliasHandle )Get1IndResource(rAliasType, 1);
		
		if (alias != NULL)
		{			
			err = FollowFinderAlias(theSpec, alias, TRUE, theSpec, &aliasChanged);
			
			if(err == noErr && aliasChanged)
			{
				ChangedResource((Handle )alias);				/* This may fail depending on permissions */
				WriteResource((Handle )alias);
			}
		}
		else
			err = resNotFound;
			
		CloseResFile(aliasResFile);
	}

	SetResLoad(FALSE != resLoadState);
	return(err);
}

 /*********************************************************************
 ***************		Resolve a Finder Alias	***********************
 *********************************************************************/

pascal OSErr
AL_resolveAliasFile(register FSSpec	*theSpec,	//I/O FSSpec for which target is returned 
					Boolean	resolveAliasChains, //I Resolve all alias chains or just one step?
					Boolean	*targetIsFolder,	//O Is target a folder?
					Boolean	*wasAliased)		//O Was input FSSpec an alias?
{
	register OSErr			err;
	register short			hopCount;			/* Alias file chains can have loops.  A hop count prevents infinite loops */
	register Boolean		foundChainEnd;		/* True => found a non-alias file target */
	CInfoPBRec				cpb;				/* Parameter block used to walk the alias chain */
	short					savedResFile;		/* I open files while pursuing an alias chain so I restore the original active resource file before returning. */
	register Boolean		specChanged;		/* True => we may changed theSpec so if we get an error, restore theSpec. */ 
	FSSpec					savedSpec;			/* in case of an error while walking an alias chain, I keep a copy of theSpec */
	
			
	if( theSpec == NULL || targetIsFolder == NULL || wasAliased == NULL )		/* param check */
		err = paramErr;

	else	
	{
		foundChainEnd					= FALSE;
		specChanged						= FALSE;
		*wasAliased						= FALSE;
		*targetIsFolder					= FALSE;
		savedSpec						= *theSpec;				/* Save a copy of theSpec in case there is a problem (optimized to have fewest copies for non-error cases) */
		savedResFile					= CurResFile();
		
		cpb.hFileInfo.ioNamePtr		= (StringPtr )&theSpec->name;
		cpb.hFileInfo.ioFDirIndex	= 0;					/* Make GetCatInfo use the name string */
		
		/* -------------------------------
		** Loop looking for the target in a chain
		** of alias files.  I will end the chain if:
		** (1)	chain is too long.  This is to avoid possible loops in alias chains.
		** (2)	encounter an error.
		** (3)	find the target.
		** ------------------------------- */

		hopCount = (resolveAliasChains ? (kResolveAliasMaxHop+1) : 1);
		while( --hopCount >= 0 )
		{
			cpb.hFileInfo.ioVRefNum	= theSpec->vRefNum;
			cpb.hFileInfo.ioDirID	= theSpec->parID;
			cpb.hFileInfo.ioFVersNum	= 0;			/* MFS compatibility */
			
			err = PBGetCatInfo(&cpb,FALSE);		/* The target doesn't exist or is probably a folder or volume */
			if( err == noErr )
			{
				if( cpb.hFileInfo.ioFlAttrib & ioDirMask ) /* cannot check alias bit before checking is folder */
				{
					*targetIsFolder = TRUE;
					foundChainEnd = TRUE;
					break;
				}
				else if( cpb.hFileInfo.ioFlFndrInfo.fdFlags & isAlias )	/* check Finder flags to see if this is an alias file */
				{
					*wasAliased = TRUE;				/* Let caller know there was an alias file */

					err = GetNextSpec(theSpec);
					if( err == noErr )				/* if resolved an alias, then theSpec should have changed (except loops) */
						specChanged = TRUE;
					else
						break;
				}
				else /* it is a file but not an alias */
				{
					foundChainEnd = TRUE;
					break;
				}
			}
			else
				break;
		}
		
		if( err == noErr && (!foundChainEnd) )	/* if we got no error and did not reach the end of an alias chain (hop count was reached) */
			if( resolveAliasChains )				/* If I was supposed to follow the chain to the end, then fnfErr */
				err = fnfErr;

			else
			{
				cpb.hFileInfo.ioVRefNum	= theSpec->vRefNum;
				cpb.hFileInfo.ioDirID	= theSpec->parID;
				cpb.hFileInfo.ioFVersNum	= 0;			/* MFS compatibility */
				
				err = PBGetCatInfo(&cpb,FALSE);		/* The target doesn't exist or is probably a folder or volume */
				if( err == noErr )
					if( cpb.hFileInfo.ioFlAttrib & ioDirMask )
						*targetIsFolder = TRUE;
			}

		if( err != noErr && specChanged )	/* got an error and we resolved at least once then */
			*theSpec = savedSpec;				/* restore theSpec.  This happens when chains of alias files are broken beyond the first link */
		
		UseResFile(savedResFile);				/* restore theResFile */
	}
	return(err);
}

/**********************************************************************
 ***************	Mount a foreign volume for an alias	***************
 *********************************************************************/

pascal OSErr AL_aliasVolumeMount(VolumeLocation *volMntInfo, //I volume mount info 
						Boolean /*interact*/,		//I user interaction allowed or not?
						short *vRefNum, 			//O mounted volume's vRefNum
						Boolean * /*changed*/)		//O did any thing change about the mounted volume's info?
{
	return VolumeMount(volMntInfo, vRefNum);
	
} // end of AliasVolumeMount routine


/**************************** end of alExt.c *********************************/
