/*
**	Apple Macintosh Developer Technical Support
**
**	IndexedSearch and the PBCatSearch compatibility function.
**
**	by Jim Luther, Apple Developer Technical Support Emeritus
**
**	File:		Search.c
**
**	Copyright © 1992-1998 Apple Computer, Inc.
**	All rights reserved.
**
**	You may incorporate this sample code into your applications without
**	restriction, though the sample code has been provided "AS IS" and the
**	responsibility for its operation is 100% yours.  However, what you are
**	not permitted to do is to redistribute the source as "DSC Sample Code"
**	after having made changes. If you're going to re-distribute the source,
**	we require that you make it clear in the source that the code was
**	descended from Apple Sample Code, but that you've made changes.
*/

/* 
 * This code, which was decended from Apple Sample Code, has been modified by 
 * Netscape.
 *
 * To avoid potential conflicts with other files named search renamed to
 * MoreFilesSearch.c
 */


#include <Types.h>
#include <Gestalt.h>
#include <Timer.h>
#include <Errors.h>
#include <Memory.h>
#include <Files.h>
#include <TextUtils.h>

#define	__COMPILINGMOREFILES

#include "MoreFiles.h"
#include "MoreFilesExtras.h"
#include "MoreFilesSearch.h"

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

enum
{
	/* Number of LevelRecs to add each time the searchStack is grown */
	/* 20 levels is probably more than reasonable for most volumes. */
	/* If more are needed, they are allocated 20 levels at a time. */
	kAdditionalLevelRecs = 20
};

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

/*
**	LevelRecs are used to store the directory ID and index whenever
**	IndexedSearch needs to either scan a sub-directory, or return control
**	to the caller because the call has timed out or the number of
**	matches requested has been found. LevelRecs are stored in an array
**	used as a stack.
*/
struct	LevelRec
{
	long	dirModDate;	/* for detecting most (but not all) catalog changes */
	long	dirID;
	short	index;
};
typedef struct LevelRec LevelRec;
typedef LevelRec *LevelRecPtr, **LevelRecHandle;


/*
**	SearchPositionRec is my version of a CatPositionRec. It holds the
**	information I need to resuming searching.
*/
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=mac68k
#endif
struct SearchPositionRec
{
	long			initialize;		/* Goofy checksum of volume information used to make */
									/* sure we're resuming a search on the same volume. */
	unsigned short	stackDepth;		/* Current depth on searchStack. */
	short			priv[11];		/* For future use... */
};
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=reset
#endif
typedef struct SearchPositionRec SearchPositionRec;
typedef SearchPositionRec *SearchPositionRecPtr;


/*
**	ExtendedTMTask is a TMTask record extended to hold the timer flag.
*/
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=mac68k
#endif
struct ExtendedTMTask
{
	TMTask			theTask;
	Boolean			stopSearch;		/* the Time Mgr task will set stopSearch to */
									/* true when the timer expires */
};
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=reset
#endif
typedef struct ExtendedTMTask ExtendedTMTask;
typedef ExtendedTMTask *ExtendedTMTaskPtr;

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

static	OSErr	CheckVol(ConstStr255Param pathname,
						 short vRefNum,
						 short *realVRefNum,
						 long *volID);

static	OSErr	CheckStack(unsigned short stackDepth,
						   LevelRecHandle searchStack,
						   Size *searchStackSize);

static	OSErr	VerifyUserPB(CSParamPtr userPB,
							 Boolean *includeFiles,
							 Boolean *includeDirs,
							 Boolean *includeNames);

static	Boolean	IsSubString(ConstStr255Param aStringPtr,
							ConstStr255Param subStringPtr);

static	Boolean	CompareMasked(const long *data1,
							  const long *data2,
							  const long *mask,
							  short longsToCompare);

static	void	CheckForMatches(CInfoPBPtr cPB,
								CSParamPtr userPB,
								const Str63 matchName,
								Boolean includeFiles,
								Boolean includeDirs);

#if	__WANTPASCALELIMINATION
#undef	pascal
#endif

#if GENERATINGCFM

static	pascal	void	TimeOutTask(TMTaskPtr tmTaskPtr);

#else

static	pascal	TMTaskPtr	GetTMTaskPtr(void);

static	void	TimeOutTask(void);

#endif

#if	__WANTPASCALELIMINATION
#define	pascal	
#endif

static	long	GetDirModDate(short vRefNum,
							  long dirID);

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

/*
**	CheckVol gets the volume's real vRefNum and builds a volID. The volID
**	is used to help insure that calls to resume searching with IndexedSearch
**	are to the same volume as the last call to IndexedSearch.
*/
static	OSErr	CheckVol(ConstStr255Param pathname,
						 short vRefNum,
						 short *realVRefNum,
						 long *volID)
{
	HParamBlockRec pb;
	OSErr error;

	error = GetVolumeInfoNoName(pathname, vRefNum, &pb);
	if ( error == noErr )
	{
		/* Return the real vRefNum */
		*realVRefNum = pb.volumeParam.ioVRefNum;

		/* Add together a bunch of things that aren't supposed to change on */
		/* a mounted volume that's being searched and that should come up with */
		/* a fairly unique number */
		*volID = pb.volumeParam.ioVCrDate +
				 pb.volumeParam.ioVRefNum +
				 pb.volumeParam.ioVNmAlBlks +
				 pb.volumeParam.ioVAlBlkSiz +
				 pb.volumeParam.ioVFSID;
	}
	return ( error );
}

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

/*
**	CheckStack checks the size of the search stack (array) to see if there's
**	room to push another LevelRec. If not, CheckStack grows the stack by
**	another kAdditionalLevelRecs elements.
*/
static	OSErr	CheckStack(unsigned short stackDepth,
						   LevelRecHandle searchStack,
						   Size *searchStackSize)
{
	OSErr	result;
	
	if ( (*searchStackSize / sizeof(LevelRec)) == (stackDepth + 1) )
	{
		/* Time to grow stack */
		SetHandleSize((Handle)searchStack, *searchStackSize + (kAdditionalLevelRecs * sizeof(LevelRec)));
		result = MemError();	/* should be noErr */
		*searchStackSize = InlineGetHandleSize((Handle)searchStack);
	}
	else
	{
		result = noErr;
	}
	
	return ( result );
}

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

/*
**	VerifyUserPB makes sure the parameter block passed to IndexedSearch has
**	valid parameters. By making this check once, we don't have to worry about
**	things like NULL pointers, strings being too long, etc.
**	VerifyUserPB also determines if the search includes files and/or
**	directories, and determines if a full or partial name search was requested.
*/
static	OSErr	VerifyUserPB(CSParamPtr userPB,
							 Boolean *includeFiles,
							 Boolean *includeDirs,
							 Boolean *includeNames)
{
	CInfoPBPtr	searchInfo1;
	CInfoPBPtr	searchInfo2;
	
	searchInfo1 = userPB->ioSearchInfo1;
	searchInfo2 = userPB->ioSearchInfo2;
	
	/* ioMatchPtr cannot be NULL */
	if ( userPB->ioMatchPtr == NULL )
	{
		goto ParamErrExit;
	}
	
	/* ioSearchInfo1 cannot be NULL */
	if ( searchInfo1 == NULL )
	{
		goto ParamErrExit;
	}
	
	/* If any bits except partialName, fullName, or negate are set, then */
	/* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required  */
	if ( ((userPB->ioSearchBits & ~(fsSBPartialName | fsSBFullName | fsSBNegate)) != 0) &&
		 ( searchInfo2 == NULL ))
	{
		goto ParamErrExit;
	}
	
	*includeFiles = false;
	*includeDirs = false;
	*includeNames = false;
	
	if ( (userPB->ioSearchBits & (fsSBPartialName | fsSBFullName)) != 0 )
	{
		/* If any kind of name matching is requested, then ioNamePtr in */
		/* ioSearchInfo1 cannot be NULL or a zero-length string */
		if ( (searchInfo1->hFileInfo.ioNamePtr == NULL) ||
			 (searchInfo1->hFileInfo.ioNamePtr[0] == 0) ||
			 (searchInfo1->hFileInfo.ioNamePtr[0] > (sizeof(Str63) - 1)) )
		{
			goto ParamErrExit;
		}
		
		*includeNames = true;
	}
	
	if ( (userPB->ioSearchBits & fsSBFlAttrib) != 0 )
	{
		/* The only attributes you can search on are the directory flag */
		/* and the locked flag. */
		if ( (searchInfo2->hFileInfo.ioFlAttrib & ~(ioDirMask | 0x01)) != 0 )
		{
			goto ParamErrExit;
		}
		
		/* interested in the directory bit? */
		if ( (searchInfo2->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
		{
			/* yes, so do they want just directories or just files? */
			if ( (searchInfo1->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
			{
				*includeDirs = true;
			}
			else
			{
				*includeFiles = true;
			}
		}
		else
		{
			/* no interest in directory bit - get both files and directories */
			*includeDirs = true;
			*includeFiles = true;
		}
	}
	else
	{
		/* no attribute checking - get both files and directories */
		*includeDirs = true;
		*includeFiles = true;
	}
	
	/* If directories are included in the search, */
	/* then the locked attribute cannot be requested. */
	if ( *includeDirs &&
		 ((userPB->ioSearchBits & fsSBFlAttrib) != 0) &&
		 ((searchInfo2->hFileInfo.ioFlAttrib & 0x01) != 0) )
	{
		goto ParamErrExit;
	}
	
	/* If files are included in the search, then there cannot be */
	/* a search on the number of files. */
	if ( *includeFiles &&
		 ((userPB->ioSearchBits & fsSBDrNmFls) != 0) )
	{
		goto ParamErrExit;
	}
	
	/* If directories are included in the search, then there cannot */
	/* be a search on file lengths. */
	if ( *includeDirs &&
		 ((userPB->ioSearchBits & (fsSBFlLgLen | fsSBFlPyLen | fsSBFlRLgLen | fsSBFlRPyLen)) != 0) )
	{
		goto ParamErrExit;
	}
	
	return ( noErr );
		 
ParamErrExit:
	return ( paramErr );
}

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

/*
**	IsSubString checks to see if a string is a substring of another string.
**	Both input strings have already been converted to all uppercase using
**	UprString (the same non-international call the File Manager uses).
*/
static	Boolean	IsSubString(ConstStr255Param aStringPtr,
							ConstStr255Param subStringPtr)
{
	short	strLength;		/* length of string */
	short	subStrLength;	/* length of subString */
	Boolean	found;			/* result of test */
	short	index;			/* current index into string */
	
	found = false;
	strLength = aStringPtr[0];
	subStrLength = subStringPtr[0];
		
	if ( subStrLength <= strLength)
	{
		register short	count;			/* search counter */
		register short	strIndex;		/* running index into string */
		register short	subStrIndex;	/* running index into subString */
		
		/* start looking at first character */
		index = 1;
		
		/* continue looking until remaining string is shorter than substring */
		count = strLength - subStrLength + 1;
		
		do
		{
			strIndex = index;	/* start string index at index */
			subStrIndex = 1;	/* start subString index at 1 */
			
			while ( !found && (aStringPtr[strIndex] == subStringPtr[subStrIndex]) )
			{
				if ( subStrIndex == subStrLength )
				{
					/* all characters in subString were found */
					found = true;
				}
				else
				{
					/* check next character of substring against next character of string */
					++subStrIndex;
					++strIndex;
				}
			}
			
			if ( !found )
			{
				/* start substring search again at next string character */
				++index;
				--count;
			}
		} while ( count != 0 && (!found) );
	}
	
	return ( found );
}

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

/*
**	CompareMasked does a bitwise comparison with mask on 1 or more longs.
**	data1 and data2 are first exclusive-ORed together resulting with bits set
**	where they are different. That value is then ANDed with the mask resulting
**	with bits set if the test fails. true is returned if the tests pass.
*/
static	Boolean	CompareMasked(const long *data1,
							  const long *data2,
							  const long *mask,
							  short longsToCompare)
{
	Boolean	result = true;
	
	while ( (longsToCompare != 0) && (result == true) )
	{
		/* (*data1 ^ *data2) = bits that are different, so... */
		/* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */
		
		if ( ((*data1 ^ *data2) & *mask) != 0 )
			result = false;
		
		++data1;
		++data2;
		++mask;
		--longsToCompare;
	}
	
	return ( result );
}

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

/*
**	Check for matches compares the search criteria in userPB to the file
**	system object in cPB. If there's a match, then the information in cPB is
**	is added to the match array and the actual match count is incremented.
*/
static	void	CheckForMatches(CInfoPBPtr cPB,
								CSParamPtr userPB,
								const Str63 matchName,
								Boolean includeFiles,
								Boolean includeDirs)
{
	long		searchBits;
	CInfoPBPtr	searchInfo1;
	CInfoPBPtr	searchInfo2;
	Str63		itemName;		/* copy of object's name for partial name matching */
	Boolean		foundMatch;
	
	foundMatch = false;			/* default to no match */
	
	searchBits = userPB->ioSearchBits;
	searchInfo1 = userPB->ioSearchInfo1;
	searchInfo2 = userPB->ioSearchInfo2;
	
	/* Into the if statements that go on forever... */
	
	if ( (cPB->hFileInfo.ioFlAttrib & ioDirMask) == 0 )
	{
		if (!includeFiles)
		{
			goto Failed;
		}
	}
	else
	{
		if (!includeDirs)
		{
			goto Failed;
		}
	}
	
	if ( (searchBits & fsSBPartialName) != 0 )
	{
		if ( (cPB->hFileInfo.ioNamePtr[0] > 0) &&
			 (cPB->hFileInfo.ioNamePtr[0] <= (sizeof(Str63) - 1)) )
		{
			/* Make uppercase copy of object name */
			BlockMoveData(cPB->hFileInfo.ioNamePtr,
							itemName,
							cPB->hFileInfo.ioNamePtr[0] + 1);
			/* Use the same non-international call the File Manager uses */
			UpperString(itemName, true);
		}
		else
		{
			goto Failed;
		}
		
		{
			if ( !IsSubString(itemName, matchName) )
			{
				goto Failed;
			}
			else if ( searchBits == fsSBPartialName )
			{
				/* optimize for name matching only since it is most common way to search */
				goto Hit;
			}
		}
	}
	
	if ( (searchBits & fsSBFullName) != 0 )
	{
		/* Use the same non-international call the File Manager uses */
		if ( !EqualString(cPB->hFileInfo.ioNamePtr, matchName, false, true) )
		{
			goto Failed;
		}
		else if ( searchBits == fsSBFullName )
		{
			/* optimize for name matching only since it is most common way to search */
			goto Hit;
		}
	}
	
	if ( (searchBits & fsSBFlParID) != 0 )
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlParID) < (unsigned long)(searchInfo1->hFileInfo.ioFlParID)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlParID) > (unsigned long)(searchInfo2->hFileInfo.ioFlParID)) )
		{
			goto Failed;
		}
	}
	
	if ( (searchBits & fsSBFlAttrib) != 0 )
	{
		if ( ((cPB->hFileInfo.ioFlAttrib ^ searchInfo1->hFileInfo.ioFlAttrib) &
			  searchInfo2->hFileInfo.ioFlAttrib) != 0 )
		{
			goto Failed;
		}
	}
	
	if ( (searchBits & fsSBDrNmFls) != 0 )
	{
		if ( ((unsigned long)(cPB->dirInfo.ioDrNmFls) < (unsigned long)(searchInfo1->dirInfo.ioDrNmFls)) ||
			 ((unsigned long)(cPB->dirInfo.ioDrNmFls) > (unsigned long)(searchInfo2->dirInfo.ioDrNmFls)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlFndrInfo) != 0 )	/* fsSBFlFndrInfo is same as fsSBDrUsrWds */
	{
		if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlFndrInfo),
							(long *)&(searchInfo1->hFileInfo.ioFlFndrInfo),
							(long *)&(searchInfo2->hFileInfo.ioFlFndrInfo),
							sizeof(FInfo) / sizeof(long)) )
		{
			goto Failed;
		}
	}
	
	if ( (searchBits & fsSBFlXFndrInfo) != 0 )	/* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */
	{
		if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlXFndrInfo),
							(long *)&(searchInfo1->hFileInfo.ioFlXFndrInfo),
							(long *)&(searchInfo2->hFileInfo.ioFlXFndrInfo),
							sizeof(FXInfo) / sizeof(long)) )
		{
			goto Failed;
		}
	}
	
	if ( (searchBits & fsSBFlLgLen) != 0 )
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlLgLen)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlLgLen)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlPyLen) != 0 )
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlPyLen)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlPyLen)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlRLgLen) != 0 )
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRLgLen)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRLgLen)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlRPyLen) != 0 )
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRPyLen)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRPyLen)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlCrDat) != 0 )	/* fsSBFlCrDat is same as fsSBDrCrDat */
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlCrDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlCrDat)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlCrDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlCrDat)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlMdDat) != 0 )	/* fsSBFlMdDat is same as fsSBDrMdDat */
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlMdDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlMdDat)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlMdDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlMdDat)) )
		{
			goto Failed;
		}
	}

	if ( (searchBits & fsSBFlBkDat) != 0 )	/* fsSBFlBkDat is same as fsSBDrBkDat */
	{
		if ( ((unsigned long)(cPB->hFileInfo.ioFlBkDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlBkDat)) ||
			 ((unsigned long)(cPB->hFileInfo.ioFlBkDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlBkDat)) )
		{
			goto Failed;
		}
	}

	/* Hey, we passed all of the tests! */
	
Hit:
	foundMatch = true;

/* foundMatch is false if code jumps to Failed */
Failed:
	/* Do we reverse our findings? */
	if ( (searchBits & fsSBNegate) != 0 )
	{
		foundMatch = !foundMatch;	/* matches are not, not matches are */
	}
	
	if ( foundMatch )
	{

		/* Move the match into the match buffer */
		userPB->ioMatchPtr[userPB->ioActMatchCount].vRefNum = cPB->hFileInfo.ioVRefNum;
		userPB->ioMatchPtr[userPB->ioActMatchCount].parID = cPB->hFileInfo.ioFlParID;
		if ( cPB->hFileInfo.ioNamePtr[0] > 63 )
		{
			cPB->hFileInfo.ioNamePtr[0] = 63;
		}
		BlockMoveData(cPB->hFileInfo.ioNamePtr,
					  userPB->ioMatchPtr[userPB->ioActMatchCount].name,
					  cPB->hFileInfo.ioNamePtr[0] + 1);
		
		/* increment the actual count */
		++(userPB->ioActMatchCount);
	}
}

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

/*
**	TimeOutTask is executed when the timer goes off. It simply sets the
**	stopSearch field to true. After each object is found and possibly added
**	to the matches buffer, stopSearch is checked to see if the search should
**	continue.
*/

#if	__WANTPASCALELIMINATION
#undef	pascal
#endif

#if GENERATINGCFM

static	pascal	void	TimeOutTask(TMTaskPtr tmTaskPtr)
{
	((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true;
}

#else

static	pascal	TMTaskPtr	GetTMTaskPtr(void)
	ONEWORDINLINE(0x2e89);	/* MOVE.L A1,(SP) */
	
static	void	TimeOutTask(void)
{
	((ExtendedTMTaskPtr)GetTMTaskPtr())->stopSearch = true;
}

#endif

#if	__WANTPASCALELIMINATION
#define	pascal	
#endif

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

/*
**	GetDirModDate returns the modification date of a directory. If there is
**	an error getting the modification date, -1 is returned to indicate
**	something went wrong.
*/
static	long	GetDirModDate(short vRefNum,
							  long dirID)
{
	CInfoPBRec pb;
	Str31 tempName;
	long modDate;

	/* Protection against File Sharing problem */
	tempName[0] = 0;
	pb.dirInfo.ioNamePtr = tempName;
	pb.dirInfo.ioVRefNum = vRefNum;
	pb.dirInfo.ioDrDirID = dirID;
	pb.dirInfo.ioFDirIndex = -1;	/* use ioDrDirID */
	
	if ( PBGetCatInfoSync(&pb) == noErr )
	{
		modDate = pb.dirInfo.ioDrMdDat;
	}
	else
	{
		modDate = -1;
	}
	
	return ( modDate );
}

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

#if !TARGET_CARBON
/* NSCP - pinkerton
 * some of the timer stuff is mismatched for carbon. We don't use it anyway.
 */
pascal	OSErr	IndexedSearch(CSParamPtr pb,
							  long dirID)
{
	static LevelRecHandle	searchStack = NULL;		/* static handle to LevelRec stack */
	static Size				searchStackSize = 0;	/* size of static handle */
	SearchPositionRecPtr	catPosition;
	long					modDate;
	short					index;
	ExtendedTMTask			timerTask;
	OSErr					result;
	short					realVRefNum;
	Str63					itemName;
	CInfoPBRec				cPB;
	long					tempLong;
	Boolean					includeFiles;
	Boolean					includeDirs;
	Boolean					includeNames;
	Str63					upperName;
	
	timerTask.stopSearch = false;	/* don't stop yet! */
	
	/* If request has a timeout, install a Time Manager task. */
	if ( pb->ioSearchTime != 0 )
	{
		/* Start timer */
		timerTask.theTask.tmAddr = NewTimerProc(TimeOutTask);
		InsTime((QElemPtr)&(timerTask.theTask));
		PrimeTime((QElemPtr)&(timerTask.theTask), pb->ioSearchTime);
	}
	
	/* Check the parameter block passed for things that we don't want to assume */
	/* are OK later in the code. For example, make sure pointers to data structures */
	/* and buffers are not NULL.  And while we're in there, see if the request */
	/* specified searching for files, directories, or both, and see if the search */
	/* was by full or partial name. */
	result = VerifyUserPB(pb, &includeFiles, &includeDirs, &includeNames);
	if ( result == noErr )
	{
		pb->ioActMatchCount = 0;	/* no matches yet */
	
		if ( includeNames )
		{
			/* The search includes seach by full or partial name. */
			/* Make an upper case copy of the match string to pass to */
			/* CheckForMatches. */
			BlockMoveData(pb->ioSearchInfo1->hFileInfo.ioNamePtr,
							upperName,
							pb->ioSearchInfo1->hFileInfo.ioNamePtr[0] + 1);
			/* Use the same non-international call the File Manager uses */
			UpperString(upperName, true);
		}
		
		/* Prevent casting to my type throughout code */
		catPosition = (SearchPositionRecPtr)&pb->ioCatPosition;
		
		/* Create searchStack first time called */
		if ( searchStack == NULL )
		{
			searchStack = (LevelRecHandle)NewHandle(kAdditionalLevelRecs * sizeof(LevelRec));
		}
		
		/* Make sure searchStack really exists */
		if ( searchStack != NULL )
		{
			searchStackSize = InlineGetHandleSize((Handle)searchStack);
			
			/* See if the search is a new search or a resumed search. */
			if ( catPosition->initialize == 0 )
			{
				/* New search. */
				
				/* Get the real vRefNum and fill in catPosition->initialize. */ 
				result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &catPosition->initialize);
				if ( result == noErr )
				{
					/* clear searchStack */
					catPosition->stackDepth = 0;
					
					/* use dirID parameter passed and... */
					index = -1;	/* start with the passed directory itself! */
				}
			}
			else
			{
				/* We're resuming a search. */
	
				/* Get the real vRefNum and make sure catPosition->initialize is valid. */ 
				result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &tempLong);
				if ( result == noErr )
				{
					/* Make sure the resumed search is to the same volume! */
					if ( catPosition->initialize == tempLong )
					{
						/* For resume, catPosition->stackDepth > 0 */
						if ( catPosition->stackDepth > 0 )
						{
							/* Position catPosition->stackDepth to access last saved level */
							--(catPosition->stackDepth);
			
							/* Get the dirID and index for the next item */
							dirID = (*searchStack)[catPosition->stackDepth].dirID;
							index = (*searchStack)[catPosition->stackDepth].index;
							
							/* Check the dir's mod date against the saved mode date on our "stack" */
							modDate = GetDirModDate(realVRefNum, dirID);
							if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
							{
								result = catChangedErr;
							}
						}
						else
						{
							/* Invalid catPosition record was passed */
							result = paramErr;
						}
					}
					else
					{
						/* The volume is not the same */
						result = catChangedErr;
					}
				}
			}
			
			if ( result == noErr )
			{
				/* ioNamePtr and ioVRefNum only need to be set up once. */
				cPB.hFileInfo.ioNamePtr = itemName;
				cPB.hFileInfo.ioVRefNum = realVRefNum;
				
				/*
				**	Here's the loop that:
				**		Finds the next item on the volume.
				**		If noErr, calls the code to check for matches and add matches
				**			to the match buffer.
				**		Sets up dirID and index for to find the next item on the volume.
				**
				**	The looping ends when:
				**		(a) an unexpected error is returned by PBGetCatInfo. All that
				**			is expected is noErr and fnfErr (after the last item in a
				**			directory is found).
				**		(b) the caller specified a timeout and our Time Manager task
				**			has fired.
				**		(c) the number of matches requested by the caller has been found.
				**		(d) the last item on the volume was found.
				*/
				do
				{
					/* get the next item */
					cPB.hFileInfo.ioFDirIndex = index;
					cPB.hFileInfo.ioDirID = dirID;
					result = PBGetCatInfoSync(&cPB);
					if ( index != -1 )
					{
						if ( result == noErr )
						{
							/* We found something */
		
							CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
							
							++index;
							if ( (cPB.dirInfo.ioFlAttrib & ioDirMask) != 0 )
							{
								/* It's a directory */
								
								result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
								if ( result == noErr )
								{
									/* Save the current state on the searchStack */
									/* when we come back, this is where we'll start */
									(*searchStack)[catPosition->stackDepth].dirID = dirID;
									(*searchStack)[catPosition->stackDepth].index = index;
									(*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
									
									/* position catPosition->stackDepth for next saved level */
									++(catPosition->stackDepth);
									
									/* The next item to get is the 1st item in the child directory */
									dirID = cPB.dirInfo.ioDrDirID;
									index = 1;
								}
							}
							/* else do nothing for files */
						}
						else
						{
							/* End of directory found (or we had some error and that */
							/* means we have to drop out of this directory). */
							/* Restore last thing put on stack and */
							/* see if we need to continue or quit. */
							if ( catPosition->stackDepth > 0 )
							{
								/* position catPosition->stackDepth to access last saved level */
								--(catPosition->stackDepth);
								
								dirID = (*searchStack)[catPosition->stackDepth].dirID;
								index = (*searchStack)[catPosition->stackDepth].index;
								
								/* Check the dir's mod date against the saved mode date on our "stack" */
								modDate = GetDirModDate(realVRefNum, dirID);
								if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
								{
									result = catChangedErr;
								}
								else
								{
									/* Going back to ancestor directory. */
									/* Clear error so we can continue. */
									result = noErr;
								}
							}
							else
							{
								/* We hit the bottom of the stack, so we'll let the */
								/* the eofErr drop us out of the loop. */
								result = eofErr;
							}
						}
					}
					else
					{
						/* Special case for index == -1; that means that we're starting */
						/* a new search and so the first item to check is the directory */
						/* passed to us. */
						if ( result == noErr )
						{
							/* We found something */
		
							CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
							
							/* Now, set the index to 1 and then we're ready to look inside */
							/* the passed directory. */
							index = 1;
						}
					}
				} while ( (!timerTask.stopSearch) &&	/* timer hasn't fired */
						  (result == noErr) &&			/* no unexpected errors */
						  (pb->ioReqMatchCount > pb->ioActMatchCount) ); /* we haven't found our limit */
				
				/* Did we drop out of the loop because of timeout or */
				/* ioReqMatchCount was found? */
				if ( result == noErr )
				{
					result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
					if ( result == noErr )
					{
						/* Either there was a timeout or ioReqMatchCount was reached. */
						/* Save the dirID and index for the next time we're called. */
						
						(*searchStack)[catPosition->stackDepth].dirID = dirID;
						(*searchStack)[catPosition->stackDepth].index = index;
						(*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
						
						/* position catPosition->stackDepth for next saved level */
						
						++(catPosition->stackDepth);
					}
				}
			}
		}
		else
		{
			/* searchStack Handle could not be allocated */
			result = memFullErr;
		}
	}
	
	if ( pb->ioSearchTime != 0 )
	{
		/* Stop Time Manager task here if it was installed */
		RmvTime((QElemPtr)&(timerTask.theTask));
		DisposeRoutineDescriptor(timerTask.theTask.tmAddr);
	}
	
	return ( result );
}
#endif

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

pascal OSErr PBCatSearchSyncCompat(CSParamPtr paramBlock)
{
	static Boolean			fullExtFSDispatchingtested = false;
	static Boolean			hasFullExtFSDispatching = false;
	OSErr 					result;
	Boolean					supportsCatSearch;
	long					response;
	GetVolParmsInfoBuffer	volParmsInfo;
	long					infoSize;
	
	result = noErr;

	/* See if File Manager will pass CatSearch requests to external file systems */
	/* we'll store the results in a static variable so we don't have to call Gestalt */
	/* everytime we're called. */
	if ( !fullExtFSDispatchingtested )
	{
		fullExtFSDispatchingtested = true;
		if ( Gestalt(gestaltFSAttr, &response) == noErr )
		{
			hasFullExtFSDispatching = ((response & (1L << gestaltFullExtFSDispatching)) != 0);
		}
	}
	
	/* CatSearch is a per volume attribute, so we have to check each time we're */
	/* called to see if it is available on the volume specified. */
	supportsCatSearch = false;
	if ( hasFullExtFSDispatching )
	{
		infoSize = sizeof(GetVolParmsInfoBuffer);
		result = HGetVolParms(paramBlock->ioNamePtr, paramBlock->ioVRefNum,
							&volParmsInfo, &infoSize);
		if ( result == noErr )
		{
			supportsCatSearch = hasCatSearch(volParmsInfo);
		}
	}
	
	/* noErr or paramErr is OK here. */
	/* paramErr just means that GetVolParms isn't supported by this volume */
	if ( (result == noErr) || (result == paramErr) )
	{
		if ( supportsCatSearch )
		{
			/* Volume supports CatSearch so use it. */
			/* CatSearch is faster than an indexed search. */
			result = PBCatSearchSync(paramBlock);
		}
		else
		{
			/* Volume doesn't support CatSearch so */
			/* search using IndexedSearch from root directory. */
			result = IndexedSearch(paramBlock, fsRtDirID);
		}
	}
	
	return ( result );
}

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

pascal	OSErr	NameFileSearch(ConstStr255Param volName,
							   short vRefNum,
							   ConstStr255Param fileName,
							   FSSpecPtr matches,
							   long reqMatchCount,
							   long *actMatchCount,
							   Boolean newSearch,
							   Boolean partial)
{
	CInfoPBRec		searchInfo1, searchInfo2;
	HParamBlockRec	pb;
	OSErr			error;
	static CatPositionRec catPosition;
	static short	lastVRefNum = 0;
	
	/* get the real volume reference number */
	error = DetermineVRefNum(volName, vRefNum, &vRefNum);
	if ( error != noErr )
		return ( error );
	
	pb.csParam.ioNamePtr = NULL;
	pb.csParam.ioVRefNum = vRefNum;
	pb.csParam.ioMatchPtr = matches;
	pb.csParam.ioReqMatchCount = reqMatchCount;
	if ( partial )	/* tell CatSearch what we're looking for: */
	{
		pb.csParam.ioSearchBits = fsSBPartialName + fsSBFlAttrib;	/* partial name file matches or */
	}
	else
	{
		pb.csParam.ioSearchBits =  fsSBFullName + fsSBFlAttrib;		/* full name file matches */
	}
	pb.csParam.ioSearchInfo1 = &searchInfo1;
	pb.csParam.ioSearchInfo2 = &searchInfo2;
	pb.csParam.ioSearchTime = 0;
	if ( (newSearch) ||				/* If caller specified new search */
		 (lastVRefNum != vRefNum) )	/* or if last search was to another volume, */
	{
		catPosition.initialize = 0;	/* then search from beginning of catalog */
	}
	pb.csParam.ioCatPosition = catPosition;
	pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);

	/* search for fileName */
	searchInfo1.hFileInfo.ioNamePtr = (StringPtr)fileName;
	searchInfo2.hFileInfo.ioNamePtr = NULL;
	
	/* only match files (not directories) */
	searchInfo1.hFileInfo.ioFlAttrib = 0x00;
	searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;

	error = PBCatSearchSyncCompat((CSParamPtr)&pb);
	
	if ( (error == noErr) ||							/* If no errors or the end of catalog was */
		 (error == eofErr) )							/* found, then the call was successful so */
	{
		*actMatchCount = pb.csParam.ioActMatchCount;	/* return the match count */
	}
	else
	{
		*actMatchCount = 0;							/* else no matches found */
	}
	
	if ( (error == noErr) ||						/* If no errors */
		 (error == catChangedErr) )					/* or there was a change in the catalog */
	{
		catPosition = pb.csParam.ioCatPosition;
		lastVRefNum = vRefNum;
			/* we can probably start the next search where we stopped this time */
	}
	else
	{
		catPosition.initialize = 0;
			/* start the next search from beginning of catalog */
	}
	
	if ( pb.csParam.ioOptBuffer != NULL )
	{
		DisposePtr(pb.csParam.ioOptBuffer);
	}
		
	return ( error );
}

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

pascal	OSErr	CreatorTypeFileSearch(ConstStr255Param volName,
									  short vRefNum,
									  OSType creator,
									  OSType fileType,
									  FSSpecPtr matches,
									  long reqMatchCount,
									  long *actMatchCount,
									  Boolean newSearch)
{
	CInfoPBRec		searchInfo1, searchInfo2;
	HParamBlockRec	pb;
	OSErr			error;
	static CatPositionRec catPosition;
	static short	lastVRefNum = 0;
	
	/* get the real volume reference number */
	error = DetermineVRefNum(volName, vRefNum, &vRefNum);
	if ( error != noErr )
		return ( error );
	
	pb.csParam.ioNamePtr = NULL;
	pb.csParam.ioVRefNum = vRefNum;
	pb.csParam.ioMatchPtr = matches;
	pb.csParam.ioReqMatchCount = reqMatchCount;
	pb.csParam.ioSearchBits = fsSBFlAttrib + fsSBFlFndrInfo;	/* Looking for finder info file matches */
	pb.csParam.ioSearchInfo1 = &searchInfo1;
	pb.csParam.ioSearchInfo2 = &searchInfo2;
	pb.csParam.ioSearchTime = 0;
	if ( (newSearch) ||				/* If caller specified new search */
		 (lastVRefNum != vRefNum) )	/* or if last search was to another volume, */
	{
		catPosition.initialize = 0;	/* then search from beginning of catalog */
	}
	pb.csParam.ioCatPosition = catPosition;
	pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);

	/* no fileName */
	searchInfo1.hFileInfo.ioNamePtr = NULL;
	searchInfo2.hFileInfo.ioNamePtr = NULL;
	
	/* only match files (not directories) */
	searchInfo1.hFileInfo.ioFlAttrib = 0x00;
	searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;
	
	/* search for creator; if creator = 0x00000000, ignore creator */
	searchInfo1.hFileInfo.ioFlFndrInfo.fdCreator = creator;
	if ( creator == (OSType)0x00000000 )
	{
		searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0x00000000;
	}
	else
	{
		searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0xffffffff;
	}
	
	/* search for fileType; if fileType = 0x00000000, ignore fileType */
	searchInfo1.hFileInfo.ioFlFndrInfo.fdType = fileType;
	if ( fileType == (OSType)0x00000000 )
	{
		searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0x00000000;
	}
	else
	{
		searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0xffffffff;
	}
	
	/* zero all other FInfo fields */
	searchInfo1.hFileInfo.ioFlFndrInfo.fdFlags = 0;
	searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
	searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
	searchInfo1.hFileInfo.ioFlFndrInfo.fdFldr = 0;
	
	searchInfo2.hFileInfo.ioFlFndrInfo.fdFlags = 0;
	searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
	searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
	searchInfo2.hFileInfo.ioFlFndrInfo.fdFldr = 0;

	error = PBCatSearchSyncCompat((CSParamPtr)&pb);
	
	if ( (error == noErr) ||							/* If no errors or the end of catalog was */
		 (error == eofErr) )							/* found, then the call was successful so */
	{
		*actMatchCount = pb.csParam.ioActMatchCount;	/* return the match count */
	}
	else
	{
		*actMatchCount = 0;							/* else no matches found */
	}
	
	if ( (error == noErr) ||						/* If no errors */
		 (error == catChangedErr) )					/* or there was a change in the catalog */
	{
		catPosition = pb.csParam.ioCatPosition;
		lastVRefNum = vRefNum;
			/* we can probably start the next search where we stopped this time */
	}
	else
	{
		catPosition.initialize = 0;
			/* start the next search from beginning of catalog */
	}
	
	if ( pb.csParam.ioOptBuffer != NULL )
	{
		DisposePtr(pb.csParam.ioOptBuffer);
	}
		
	return ( error );
}

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