2 **      Apple Macintosh Developer Technical Support 
   4 **      IndexedSearch and the PBCatSearch compatibility function. 
   6 **      by Jim Luther, Apple Developer Technical Support Emeritus 
  10 **      Copyright © 1992-1998 Apple Computer, Inc. 
  11 **      All rights reserved. 
  13 **      You may incorporate this sample code into your applications without 
  14 **      restriction, though the sample code has been provided "AS IS" and the 
  15 **      responsibility for its operation is 100% yours.  However, what you are 
  16 **      not permitted to do is to redistribute the source as "DSC Sample Code" 
  17 **      after having made changes. If you're going to re-distribute the source, 
  18 **      we require that you make it clear in the source that the code was 
  19 **      descended from Apple Sample Code, but that you've made changes. 
  28 #include <TextUtils.h> 
  30 #define __COMPILINGMOREFILES 
  36 /*****************************************************************************/ 
  40         /* Number of LevelRecs to add each time the searchStack is grown */ 
  41         /* 20 levels is probably more than reasonable for most volumes. */ 
  42         /* If more are needed, they are allocated 20 levels at a time. */ 
  43         kAdditionalLevelRecs 
= 20 
  46 /*****************************************************************************/ 
  49 **      LevelRecs are used to store the directory ID and index whenever 
  50 **      IndexedSearch needs to either scan a sub-directory, or return control 
  51 **      to the caller because the call has timed out or the number of 
  52 **      matches requested has been found. LevelRecs are stored in an array 
  57         long    dirModDate
;     /* for detecting most (but not all) catalog changes */ 
  61 typedef struct LevelRec LevelRec
; 
  62 typedef LevelRec 
*LevelRecPtr
, **LevelRecHandle
; 
  66 **      SearchPositionRec is my version of a CatPositionRec. It holds the 
  67 **      information I need to resuming searching. 
  69 #if PRAGMA_ALIGN_SUPPORTED 
  70 #pragma options align=mac68k 
  72 struct SearchPositionRec
 
  74         long                    initialize
;             /* Goofy checksum of volume information used to make */ 
  75                                                                         /* sure we're resuming a search on the same volume. */ 
  76         unsigned short  stackDepth
;             /* Current depth on searchStack. */ 
  77         short                   priv
[11];               /* For future use... */ 
  79 #if PRAGMA_ALIGN_SUPPORTED 
  80 #pragma options align=reset 
  82 typedef struct SearchPositionRec SearchPositionRec
; 
  83 typedef SearchPositionRec 
*SearchPositionRecPtr
; 
  87 **      ExtendedTMTask is a TMTask record extended to hold the timer flag. 
  89 #if PRAGMA_ALIGN_SUPPORTED 
  90 #pragma options align=mac68k 
  95         Boolean                 stopSearch
;             /* the Time Mgr task will set stopSearch to */ 
  96                                                                         /* true when the timer expires */ 
  98 #if PRAGMA_ALIGN_SUPPORTED 
  99 #pragma options align=reset 
 101 typedef struct ExtendedTMTask ExtendedTMTask
; 
 102 typedef ExtendedTMTask 
*ExtendedTMTaskPtr
; 
 104 /*****************************************************************************/ 
 106 static  OSErr   
CheckVol(ConstStr255Param pathname
, 
 111 static  OSErr   
CheckStack(unsigned short stackDepth
, 
 112                                                    LevelRecHandle searchStack
, 
 113                                                    Size 
*searchStackSize
); 
 115 static  OSErr   
VerifyUserPB(CSParamPtr userPB
, 
 116                                                          Boolean 
*includeFiles
, 
 117                                                          Boolean 
*includeDirs
, 
 118                                                          Boolean 
*includeNames
); 
 120 static  Boolean 
IsSubString(ConstStr255Param aStringPtr
, 
 121                                                         ConstStr255Param subStringPtr
); 
 123 static  Boolean 
CompareMasked(const long *data1
, 
 126                                                           short longsToCompare
); 
 128 static  void    CheckForMatches(CInfoPBPtr cPB
, 
 130                                                                 const Str63 matchName
, 
 131                                                                 Boolean includeFiles
, 
 132                                                                 Boolean includeDirs
); 
 134 #if     __WANTPASCALELIMINATION 
 140 static  pascal  void    TimeOutTask(TMTaskPtr tmTaskPtr
); 
 144 static  pascal  TMTaskPtr       
GetTMTaskPtr(void); 
 146 static  void    TimeOutTask(void); 
 150 #if     __WANTPASCALELIMINATION 
 154 static  long    GetDirModDate(short vRefNum
, 
 157 /*****************************************************************************/ 
 160 **      CheckVol gets the volume's real vRefNum and builds a volID. The volID 
 161 **      is used to help insure that calls to resume searching with IndexedSearch 
 162 **      are to the same volume as the last call to IndexedSearch. 
 164 static  OSErr   
CheckVol(ConstStr255Param pathname
, 
 172         error 
= GetVolumeInfoNoName(pathname
, vRefNum
, &pb
); 
 173         if ( error 
== noErr 
) 
 175                 /* Return the real vRefNum */ 
 176                 *realVRefNum 
= pb
.volumeParam
.ioVRefNum
; 
 178                 /* Add together a bunch of things that aren't supposed to change on */ 
 179                 /* a mounted volume that's being searched and that should come up with */ 
 180                 /* a fairly unique number */ 
 181                 *volID 
= pb
.volumeParam
.ioVCrDate 
+ 
 182                                  pb
.volumeParam
.ioVRefNum 
+ 
 183                                  pb
.volumeParam
.ioVNmAlBlks 
+ 
 184                                  pb
.volumeParam
.ioVAlBlkSiz 
+ 
 185                                  pb
.volumeParam
.ioVFSID
; 
 190 /*****************************************************************************/ 
 193 **      CheckStack checks the size of the search stack (array) to see if there's 
 194 **      room to push another LevelRec. If not, CheckStack grows the stack by 
 195 **      another kAdditionalLevelRecs elements. 
 197 static  OSErr   
CheckStack(unsigned short stackDepth
, 
 198                                                    LevelRecHandle searchStack
, 
 199                                                    Size 
*searchStackSize
) 
 203         if ( (*searchStackSize 
/ sizeof(LevelRec
)) == (stackDepth 
+ 1) ) 
 205                 /* Time to grow stack */ 
 206                 SetHandleSize((Handle
)searchStack
, *searchStackSize 
+ (kAdditionalLevelRecs 
* sizeof(LevelRec
))); 
 207                 result 
= MemError();    /* should be noErr */ 
 208                 *searchStackSize 
= InlineGetHandleSize((Handle
)searchStack
); 
 218 /*****************************************************************************/ 
 221 **      VerifyUserPB makes sure the parameter block passed to IndexedSearch has 
 222 **      valid parameters. By making this check once, we don't have to worry about 
 223 **      things like NULL pointers, strings being too long, etc. 
 224 **      VerifyUserPB also determines if the search includes files and/or 
 225 **      directories, and determines if a full or partial name search was requested. 
 227 static  OSErr   
VerifyUserPB(CSParamPtr userPB
, 
 228                                                          Boolean 
*includeFiles
, 
 229                                                          Boolean 
*includeDirs
, 
 230                                                          Boolean 
*includeNames
) 
 232         CInfoPBPtr      searchInfo1
; 
 233         CInfoPBPtr      searchInfo2
; 
 235         searchInfo1 
= userPB
->ioSearchInfo1
; 
 236         searchInfo2 
= userPB
->ioSearchInfo2
; 
 238         /* ioMatchPtr cannot be NULL */ 
 239         if ( userPB
->ioMatchPtr 
== NULL 
) 
 244         /* ioSearchInfo1 cannot be NULL */ 
 245         if ( searchInfo1 
== NULL 
) 
 250         /* If any bits except partialName, fullName, or negate are set, then */ 
 251         /* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required  */ 
 252         if ( ((userPB
->ioSearchBits 
& ~(fsSBPartialName 
| fsSBFullName 
| fsSBNegate
)) != 0) && 
 253                  ( searchInfo2 
== NULL 
)) 
 258         *includeFiles 
= false; 
 259         *includeDirs 
= false; 
 260         *includeNames 
= false; 
 262         if ( (userPB
->ioSearchBits 
& (fsSBPartialName 
| fsSBFullName
)) != 0 ) 
 264                 /* If any kind of name matching is requested, then ioNamePtr in */ 
 265                 /* ioSearchInfo1 cannot be NULL or a zero-length string */ 
 266                 if ( (searchInfo1
->hFileInfo
.ioNamePtr 
== NULL
) || 
 267                          (searchInfo1
->hFileInfo
.ioNamePtr
[0] == 0) || 
 268                          (searchInfo1
->hFileInfo
.ioNamePtr
[0] > (sizeof(Str63
) - 1)) ) 
 273                 *includeNames 
= true; 
 276         if ( (userPB
->ioSearchBits 
& fsSBFlAttrib
) != 0 ) 
 278                 /* The only attributes you can search on are the directory flag */ 
 279                 /* and the locked flag. */ 
 280                 if ( (searchInfo2
->hFileInfo
.ioFlAttrib 
& ~(ioDirMask 
| 0x01)) != 0 ) 
 285                 /* interested in the directory bit? */ 
 286                 if ( (searchInfo2
->hFileInfo
.ioFlAttrib 
& ioDirMask
) != 0 ) 
 288                         /* yes, so do they want just directories or just files? */ 
 289                         if ( (searchInfo1
->hFileInfo
.ioFlAttrib 
& ioDirMask
) != 0 ) 
 295                                 *includeFiles 
= true; 
 300                         /* no interest in directory bit - get both files and directories */ 
 302                         *includeFiles 
= true; 
 307                 /* no attribute checking - get both files and directories */ 
 309                 *includeFiles 
= true; 
 312         /* If directories are included in the search, */ 
 313         /* then the locked attribute cannot be requested. */ 
 315                  ((userPB
->ioSearchBits 
& fsSBFlAttrib
) != 0) && 
 316                  ((searchInfo2
->hFileInfo
.ioFlAttrib 
& 0x01) != 0) ) 
 321         /* If files are included in the search, then there cannot be */ 
 322         /* a search on the number of files. */ 
 323         if ( *includeFiles 
&& 
 324                  ((userPB
->ioSearchBits 
& fsSBDrNmFls
) != 0) ) 
 329         /* If directories are included in the search, then there cannot */ 
 330         /* be a search on file lengths. */ 
 332                  ((userPB
->ioSearchBits 
& (fsSBFlLgLen 
| fsSBFlPyLen 
| fsSBFlRLgLen 
| fsSBFlRPyLen
)) != 0) ) 
 343 /*****************************************************************************/ 
 346 **      IsSubString checks to see if a string is a substring of another string. 
 347 **      Both input strings have already been converted to all uppercase using 
 348 **      UprString (the same non-international call the File Manager uses). 
 350 static  Boolean 
IsSubString(ConstStr255Param aStringPtr
, 
 351                                                         ConstStr255Param subStringPtr
) 
 353         short   strLength
;              /* length of string */ 
 354         short   subStrLength
;   /* length of subString */ 
 355         Boolean found
;                  /* result of test */ 
 356         short   index
;                  /* current index into string */ 
 359         strLength 
= aStringPtr
[0]; 
 360         subStrLength 
= subStringPtr
[0]; 
 362         if ( subStrLength 
<= strLength
) 
 364                 register short  count
;                  /* search counter */ 
 365                 register short  strIndex
;               /* running index into string */ 
 366                 register short  subStrIndex
;    /* running index into subString */ 
 368                 /* start looking at first character */ 
 371                 /* continue looking until remaining string is shorter than substring */ 
 372                 count 
= strLength 
- subStrLength 
+ 1; 
 376                         strIndex 
= index
;       /* start string index at index */ 
 377                         subStrIndex 
= 1;        /* start subString index at 1 */ 
 379                         while ( !found 
&& (aStringPtr
[strIndex
] == subStringPtr
[subStrIndex
]) ) 
 381                                 if ( subStrIndex 
== subStrLength 
) 
 383                                         /* all characters in subString were found */ 
 388                                         /* check next character of substring against next character of string */ 
 396                                 /* start substring search again at next string character */ 
 400                 } while ( count 
!= 0 && (!found
) ); 
 406 /*****************************************************************************/ 
 409 **      CompareMasked does a bitwise comparison with mask on 1 or more longs. 
 410 **      data1 and data2 are first exclusive-ORed together resulting with bits set 
 411 **      where they are different. That value is then ANDed with the mask resulting 
 412 **      with bits set if the test fails. true is returned if the tests pass. 
 414 static  Boolean 
CompareMasked(const long *data1
, 
 417                                                           short longsToCompare
) 
 419         Boolean result 
= true; 
 421         while ( (longsToCompare 
!= 0) && (result 
== true) ) 
 423                 /* (*data1 ^ *data2) = bits that are different, so... */ 
 424                 /* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */ 
 426                 if ( ((*data1 
^ *data2
) & *mask
) != 0 ) 
 438 /*****************************************************************************/ 
 441 **      Check for matches compares the search criteria in userPB to the file 
 442 **      system object in cPB. If there's a match, then the information in cPB is 
 443 **      is added to the match array and the actual match count is incremented. 
 445 static  void    CheckForMatches(CInfoPBPtr cPB
, 
 447                                                                 const Str63 matchName
, 
 448                                                                 Boolean includeFiles
, 
 452         CInfoPBPtr      searchInfo1
; 
 453         CInfoPBPtr      searchInfo2
; 
 454         Str63           itemName
;               /* copy of object's name for partial name matching */ 
 457         foundMatch 
= false;                     /* default to no match */ 
 459         searchBits 
= userPB
->ioSearchBits
; 
 460         searchInfo1 
= userPB
->ioSearchInfo1
; 
 461         searchInfo2 
= userPB
->ioSearchInfo2
; 
 463         /* Into the if statements that go on forever... */ 
 465         if ( (cPB
->hFileInfo
.ioFlAttrib 
& ioDirMask
) == 0 ) 
 480         if ( (searchBits 
& fsSBPartialName
) != 0 ) 
 482                 if ( (cPB
->hFileInfo
.ioNamePtr
[0] > 0) && 
 483                          (cPB
->hFileInfo
.ioNamePtr
[0] <= (sizeof(Str63
) - 1)) ) 
 485                         /* Make uppercase copy of object name */ 
 486                         BlockMoveData(cPB
->hFileInfo
.ioNamePtr
, 
 488                                                         cPB
->hFileInfo
.ioNamePtr
[0] + 1); 
 489                         /* Use the same non-international call the File Manager uses */ 
 490                         UpperString(itemName
, true); 
 498                         if ( !IsSubString(itemName
, matchName
) ) 
 502                         else if ( searchBits 
== fsSBPartialName 
) 
 504                                 /* optimize for name matching only since it is most common way to search */ 
 510         if ( (searchBits 
& fsSBFullName
) != 0 ) 
 512                 /* Use the same non-international call the File Manager uses */ 
 513                 if ( !EqualString(cPB
->hFileInfo
.ioNamePtr
, matchName
, false, true) ) 
 517                 else if ( searchBits 
== fsSBFullName 
) 
 519                         /* optimize for name matching only since it is most common way to search */ 
 524         if ( (searchBits 
& fsSBFlParID
) != 0 ) 
 526                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlParID
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlParID
)) || 
 527                          ((unsigned long)(cPB
->hFileInfo
.ioFlParID
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlParID
)) ) 
 533         if ( (searchBits 
& fsSBFlAttrib
) != 0 ) 
 535                 if ( ((cPB
->hFileInfo
.ioFlAttrib 
^ searchInfo1
->hFileInfo
.ioFlAttrib
) & 
 536                           searchInfo2
->hFileInfo
.ioFlAttrib
) != 0 ) 
 542         if ( (searchBits 
& fsSBDrNmFls
) != 0 ) 
 544                 if ( ((unsigned long)(cPB
->dirInfo
.ioDrNmFls
) < (unsigned long)(searchInfo1
->dirInfo
.ioDrNmFls
)) || 
 545                          ((unsigned long)(cPB
->dirInfo
.ioDrNmFls
) > (unsigned long)(searchInfo2
->dirInfo
.ioDrNmFls
)) ) 
 551         if ( (searchBits 
& fsSBFlFndrInfo
) != 0 )       /* fsSBFlFndrInfo is same as fsSBDrUsrWds */ 
 553                 if ( !CompareMasked((long *)&(cPB
->hFileInfo
.ioFlFndrInfo
), 
 554                                                         (long *)&(searchInfo1
->hFileInfo
.ioFlFndrInfo
), 
 555                                                         (long *)&(searchInfo2
->hFileInfo
.ioFlFndrInfo
), 
 556                                                         sizeof(FInfo
) / sizeof(long)) ) 
 562         if ( (searchBits 
& fsSBFlXFndrInfo
) != 0 )      /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */ 
 564                 if ( !CompareMasked((long *)&(cPB
->hFileInfo
.ioFlXFndrInfo
), 
 565                                                         (long *)&(searchInfo1
->hFileInfo
.ioFlXFndrInfo
), 
 566                                                         (long *)&(searchInfo2
->hFileInfo
.ioFlXFndrInfo
), 
 567                                                         sizeof(FXInfo
) / sizeof(long)) ) 
 573         if ( (searchBits 
& fsSBFlLgLen
) != 0 ) 
 575                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlLgLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlLgLen
)) || 
 576                          ((unsigned long)(cPB
->hFileInfo
.ioFlLgLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlLgLen
)) ) 
 582         if ( (searchBits 
& fsSBFlPyLen
) != 0 ) 
 584                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlPyLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlPyLen
)) || 
 585                          ((unsigned long)(cPB
->hFileInfo
.ioFlPyLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlPyLen
)) ) 
 591         if ( (searchBits 
& fsSBFlRLgLen
) != 0 ) 
 593                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlRLgLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlRLgLen
)) || 
 594                          ((unsigned long)(cPB
->hFileInfo
.ioFlRLgLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlRLgLen
)) ) 
 600         if ( (searchBits 
& fsSBFlRPyLen
) != 0 ) 
 602                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlRPyLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlRPyLen
)) || 
 603                          ((unsigned long)(cPB
->hFileInfo
.ioFlRPyLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlRPyLen
)) ) 
 609         if ( (searchBits 
& fsSBFlCrDat
) != 0 )  /* fsSBFlCrDat is same as fsSBDrCrDat */ 
 611                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlCrDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlCrDat
)) || 
 612                          ((unsigned long)(cPB
->hFileInfo
.ioFlCrDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlCrDat
)) ) 
 618         if ( (searchBits 
& fsSBFlMdDat
) != 0 )  /* fsSBFlMdDat is same as fsSBDrMdDat */ 
 620                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlMdDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlMdDat
)) || 
 621                          ((unsigned long)(cPB
->hFileInfo
.ioFlMdDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlMdDat
)) ) 
 627         if ( (searchBits 
& fsSBFlBkDat
) != 0 )  /* fsSBFlBkDat is same as fsSBDrBkDat */ 
 629                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlBkDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlBkDat
)) || 
 630                          ((unsigned long)(cPB
->hFileInfo
.ioFlBkDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlBkDat
)) ) 
 636         /* Hey, we passed all of the tests! */ 
 641 /* foundMatch is false if code jumps to Failed */ 
 643         /* Do we reverse our findings? */ 
 644         if ( (searchBits 
& fsSBNegate
) != 0 ) 
 646                 foundMatch 
= !foundMatch
;       /* matches are not, not matches are */ 
 652                 /* Move the match into the match buffer */ 
 653                 userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].vRefNum 
= cPB
->hFileInfo
.ioVRefNum
; 
 654                 userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].parID 
= cPB
->hFileInfo
.ioFlParID
; 
 655                 if ( cPB
->hFileInfo
.ioNamePtr
[0] > 63 ) 
 657                         cPB
->hFileInfo
.ioNamePtr
[0] = 63; 
 659                 BlockMoveData(cPB
->hFileInfo
.ioNamePtr
, 
 660                                           userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].name
, 
 661                                           cPB
->hFileInfo
.ioNamePtr
[0] + 1); 
 663                 /* increment the actual count */ 
 664                 ++(userPB
->ioActMatchCount
); 
 668 /*****************************************************************************/ 
 671 **      TimeOutTask is executed when the timer goes off. It simply sets the 
 672 **      stopSearch field to true. After each object is found and possibly added 
 673 **      to the matches buffer, stopSearch is checked to see if the search should 
 677 #if     __WANTPASCALELIMINATION 
 683 static  pascal  void    TimeOutTask(TMTaskPtr tmTaskPtr
) 
 685         ((ExtendedTMTaskPtr
)tmTaskPtr
)->stopSearch 
= true; 
 690 static  pascal  TMTaskPtr       
GetTMTaskPtr(void) 
 691         ONEWORDINLINE(0x2e89);  /* MOVE.L A1,(SP) */ 
 693 static  void    TimeOutTask(void) 
 695         ((ExtendedTMTaskPtr
)GetTMTaskPtr())->stopSearch 
= true; 
 700 #if     __WANTPASCALELIMINATION 
 704 /*****************************************************************************/ 
 707 **      GetDirModDate returns the modification date of a directory. If there is 
 708 **      an error getting the modification date, -1 is returned to indicate 
 709 **      something went wrong. 
 711 static  long    GetDirModDate(short vRefNum
, 
 718         /* Protection against File Sharing problem */ 
 720         pb
.dirInfo
.ioNamePtr 
= tempName
; 
 721         pb
.dirInfo
.ioVRefNum 
= vRefNum
; 
 722         pb
.dirInfo
.ioDrDirID 
= dirID
; 
 723         pb
.dirInfo
.ioFDirIndex 
= -1;    /* use ioDrDirID */ 
 725         if ( PBGetCatInfoSync(&pb
) == noErr 
) 
 727                 modDate 
= pb
.dirInfo
.ioDrMdDat
; 
 737 /*****************************************************************************/ 
 739 pascal  OSErr   
IndexedSearch(CSParamPtr pb
, 
 742         static LevelRecHandle   searchStack 
= NULL
;             /* static handle to LevelRec stack */ 
 743         static Size                             searchStackSize 
= 0;    /* size of static handle */ 
 744         SearchPositionRecPtr    catPosition
; 
 747         ExtendedTMTask                  timerTask
; 
 753         Boolean                                 includeFiles
; 
 755         Boolean                                 includeNames
; 
 758         timerTask
.stopSearch 
= false;   /* don't stop yet! */ 
 760         /* If request has a timeout, install a Time Manager task. */ 
 761         if ( pb
->ioSearchTime 
!= 0 ) 
 764                 timerTask
.theTask
.tmAddr 
= NewTimerProc(TimeOutTask
); 
 765                 InsTime((QElemPtr
)&(timerTask
.theTask
)); 
 766                 PrimeTime((QElemPtr
)&(timerTask
.theTask
), pb
->ioSearchTime
); 
 769         /* Check the parameter block passed for things that we don't want to assume */ 
 770         /* are OK later in the code. For example, make sure pointers to data structures */ 
 771         /* and buffers are not NULL.  And while we're in there, see if the request */ 
 772         /* specified searching for files, directories, or both, and see if the search */ 
 773         /* was by full or partial name. */ 
 774         result 
= VerifyUserPB(pb
, &includeFiles
, &includeDirs
, &includeNames
); 
 775         if ( result 
== noErr 
) 
 777                 pb
->ioActMatchCount 
= 0;        /* no matches yet */ 
 781                         /* The search includes seach by full or partial name. */ 
 782                         /* Make an upper case copy of the match string to pass to */ 
 783                         /* CheckForMatches. */ 
 784                         BlockMoveData(pb
->ioSearchInfo1
->hFileInfo
.ioNamePtr
, 
 786                                                         pb
->ioSearchInfo1
->hFileInfo
.ioNamePtr
[0] + 1); 
 787                         /* Use the same non-international call the File Manager uses */ 
 788                         UpperString(upperName
, true); 
 791                 /* Prevent casting to my type throughout code */ 
 792                 catPosition 
= (SearchPositionRecPtr
)&pb
->ioCatPosition
; 
 794                 /* Create searchStack first time called */ 
 795                 if ( searchStack 
== NULL 
) 
 797                         searchStack 
= (LevelRecHandle
)NewHandle(kAdditionalLevelRecs 
* sizeof(LevelRec
)); 
 800                 /* Make sure searchStack really exists */ 
 801                 if ( searchStack 
!= NULL 
) 
 803                         searchStackSize 
= InlineGetHandleSize((Handle
)searchStack
); 
 805                         /* See if the search is a new search or a resumed search. */ 
 806                         if ( catPosition
->initialize 
== 0 ) 
 810                                 /* Get the real vRefNum and fill in catPosition->initialize. */  
 811                                 result 
= CheckVol(pb
->ioNamePtr
, pb
->ioVRefNum
, &realVRefNum
, &catPosition
->initialize
); 
 812                                 if ( result 
== noErr 
) 
 814                                         /* clear searchStack */ 
 815                                         catPosition
->stackDepth 
= 0; 
 817                                         /* use dirID parameter passed and... */ 
 818                                         index 
= -1;     /* start with the passed directory itself! */ 
 823                                 /* We're resuming a search. */ 
 825                                 /* Get the real vRefNum and make sure catPosition->initialize is valid. */  
 826                                 result 
= CheckVol(pb
->ioNamePtr
, pb
->ioVRefNum
, &realVRefNum
, &tempLong
); 
 827                                 if ( result 
== noErr 
) 
 829                                         /* Make sure the resumed search is to the same volume! */ 
 830                                         if ( catPosition
->initialize 
== tempLong 
) 
 832                                                 /* For resume, catPosition->stackDepth > 0 */ 
 833                                                 if ( catPosition
->stackDepth 
> 0 ) 
 835                                                         /* Position catPosition->stackDepth to access last saved level */ 
 836                                                         --(catPosition
->stackDepth
); 
 838                                                         /* Get the dirID and index for the next item */ 
 839                                                         dirID 
= (*searchStack
)[catPosition
->stackDepth
].dirID
; 
 840                                                         index 
= (*searchStack
)[catPosition
->stackDepth
].index
; 
 842                                                         /* Check the dir's mod date against the saved mode date on our "stack" */ 
 843                                                         modDate 
= GetDirModDate(realVRefNum
, dirID
); 
 844                                                         if ( modDate 
!= (*searchStack
)[catPosition
->stackDepth
].dirModDate 
) 
 846                                                                 result 
= catChangedErr
; 
 851                                                         /* Invalid catPosition record was passed */ 
 857                                                 /* The volume is not the same */ 
 858                                                 result 
= catChangedErr
; 
 863                         if ( result 
== noErr 
) 
 865                                 /* ioNamePtr and ioVRefNum only need to be set up once. */ 
 866                                 cPB
.hFileInfo
.ioNamePtr 
= itemName
; 
 867                                 cPB
.hFileInfo
.ioVRefNum 
= realVRefNum
; 
 870                                 **      Here's the loop that: 
 871                                 **              Finds the next item on the volume. 
 872                                 **              If noErr, calls the code to check for matches and add matches 
 873                                 **                      to the match buffer. 
 874                                 **              Sets up dirID and index for to find the next item on the volume. 
 876                                 **      The looping ends when: 
 877                                 **              (a) an unexpected error is returned by PBGetCatInfo. All that 
 878                                 **                      is expected is noErr and fnfErr (after the last item in a 
 879                                 **                      directory is found). 
 880                                 **              (b) the caller specified a timeout and our Time Manager task 
 882                                 **              (c) the number of matches requested by the caller has been found. 
 883                                 **              (d) the last item on the volume was found. 
 887                                         /* get the next item */ 
 888                                         cPB
.hFileInfo
.ioFDirIndex 
= index
; 
 889                                         cPB
.hFileInfo
.ioDirID 
= dirID
; 
 890                                         result 
= PBGetCatInfoSync(&cPB
); 
 893                                                 if ( result 
== noErr 
) 
 895                                                         /* We found something */ 
 897                                                         CheckForMatches(&cPB
, pb
, upperName
, includeFiles
, includeDirs
); 
 900                                                         if ( (cPB
.dirInfo
.ioFlAttrib 
& ioDirMask
) != 0 ) 
 902                                                                 /* It's a directory */ 
 904                                                                 result 
= CheckStack(catPosition
->stackDepth
, searchStack
, &searchStackSize
); 
 905                                                                 if ( result 
== noErr 
) 
 907                                                                         /* Save the current state on the searchStack */ 
 908                                                                         /* when we come back, this is where we'll start */ 
 909                                                                         (*searchStack
)[catPosition
->stackDepth
].dirID 
= dirID
; 
 910                                                                         (*searchStack
)[catPosition
->stackDepth
].index 
= index
; 
 911                                                                         (*searchStack
)[catPosition
->stackDepth
].dirModDate 
= GetDirModDate(realVRefNum
, dirID
); 
 913                                                                         /* position catPosition->stackDepth for next saved level */ 
 914                                                                         ++(catPosition
->stackDepth
); 
 916                                                                         /* The next item to get is the 1st item in the child directory */ 
 917                                                                         dirID 
= cPB
.dirInfo
.ioDrDirID
; 
 921                                                         /* else do nothing for files */ 
 925                                                         /* End of directory found (or we had some error and that */ 
 926                                                         /* means we have to drop out of this directory). */ 
 927                                                         /* Restore last thing put on stack and */ 
 928                                                         /* see if we need to continue or quit. */ 
 929                                                         if ( catPosition
->stackDepth 
> 0 ) 
 931                                                                 /* position catPosition->stackDepth to access last saved level */ 
 932                                                                 --(catPosition
->stackDepth
); 
 934                                                                 dirID 
= (*searchStack
)[catPosition
->stackDepth
].dirID
; 
 935                                                                 index 
= (*searchStack
)[catPosition
->stackDepth
].index
; 
 937                                                                 /* Check the dir's mod date against the saved mode date on our "stack" */ 
 938                                                                 modDate 
= GetDirModDate(realVRefNum
, dirID
); 
 939                                                                 if ( modDate 
!= (*searchStack
)[catPosition
->stackDepth
].dirModDate 
) 
 941                                                                         result 
= catChangedErr
; 
 945                                                                         /* Going back to ancestor directory. */ 
 946                                                                         /* Clear error so we can continue. */ 
 952                                                                 /* We hit the bottom of the stack, so we'll let the */ 
 953                                                                 /* the eofErr drop us out of the loop. */ 
 960                                                 /* Special case for index == -1; that means that we're starting */ 
 961                                                 /* a new search and so the first item to check is the directory */ 
 963                                                 if ( result 
== noErr 
) 
 965                                                         /* We found something */ 
 967                                                         CheckForMatches(&cPB
, pb
, upperName
, includeFiles
, includeDirs
); 
 969                                                         /* Now, set the index to 1 and then we're ready to look inside */ 
 970                                                         /* the passed directory. */ 
 974                                 } while ( (!timerTask
.stopSearch
) &&    /* timer hasn't fired */ 
 975                                                   (result 
== noErr
) &&                  /* no unexpected errors */ 
 976                                                   (pb
->ioReqMatchCount 
> pb
->ioActMatchCount
) ); /* we haven't found our limit */ 
 978                                 /* Did we drop out of the loop because of timeout or */ 
 979                                 /* ioReqMatchCount was found? */ 
 980                                 if ( result 
== noErr 
) 
 982                                         result 
= CheckStack(catPosition
->stackDepth
, searchStack
, &searchStackSize
); 
 983                                         if ( result 
== noErr 
) 
 985                                                 /* Either there was a timeout or ioReqMatchCount was reached. */ 
 986                                                 /* Save the dirID and index for the next time we're called. */ 
 988                                                 (*searchStack
)[catPosition
->stackDepth
].dirID 
= dirID
; 
 989                                                 (*searchStack
)[catPosition
->stackDepth
].index 
= index
; 
 990                                                 (*searchStack
)[catPosition
->stackDepth
].dirModDate 
= GetDirModDate(realVRefNum
, dirID
); 
 992                                                 /* position catPosition->stackDepth for next saved level */ 
 994                                                 ++(catPosition
->stackDepth
); 
1001                         /* searchStack Handle could not be allocated */ 
1002                         result 
= memFullErr
; 
1006         if ( pb
->ioSearchTime 
!= 0 ) 
1008                 /* Stop Time Manager task here if it was installed */ 
1009                 RmvTime((QElemPtr
)&(timerTask
.theTask
)); 
1010                 DisposeRoutineDescriptor(timerTask
.theTask
.tmAddr
); 
1016 /*****************************************************************************/ 
1018 pascal OSErr 
PBCatSearchSyncCompat(CSParamPtr paramBlock
) 
1020         static Boolean                  fullExtFSDispatchingtested 
= false; 
1021         static Boolean                  hasFullExtFSDispatching 
= false; 
1023         Boolean                                 supportsCatSearch
; 
1025         GetVolParmsInfoBuffer   volParmsInfo
; 
1030         /* See if File Manager will pass CatSearch requests to external file systems */ 
1031         /* we'll store the results in a static variable so we don't have to call Gestalt */ 
1032         /* everytime we're called. */ 
1033         if ( !fullExtFSDispatchingtested 
) 
1035                 fullExtFSDispatchingtested 
= true; 
1036                 if ( Gestalt(gestaltFSAttr
, &response
) == noErr 
) 
1038                         hasFullExtFSDispatching 
= ((response 
& (1L << gestaltFullExtFSDispatching
)) != 0); 
1042         /* CatSearch is a per volume attribute, so we have to check each time we're */ 
1043         /* called to see if it is available on the volume specified. */ 
1044         supportsCatSearch 
= false; 
1045         if ( hasFullExtFSDispatching 
) 
1047                 infoSize 
= sizeof(GetVolParmsInfoBuffer
); 
1048                 result 
= HGetVolParms(paramBlock
->ioNamePtr
, paramBlock
->ioVRefNum
, 
1049                                                         &volParmsInfo
, &infoSize
); 
1050                 if ( result 
== noErr 
) 
1052                         supportsCatSearch 
= hasCatSearch(volParmsInfo
); 
1056         /* noErr or paramErr is OK here. */ 
1057         /* paramErr just means that GetVolParms isn't supported by this volume */ 
1058         if ( (result 
== noErr
) || (result 
== paramErr
) ) 
1060                 if ( supportsCatSearch 
) 
1062                         /* Volume supports CatSearch so use it. */ 
1063                         /* CatSearch is faster than an indexed search. */ 
1064                         result 
= PBCatSearchSync(paramBlock
); 
1068                         /* Volume doesn't support CatSearch so */ 
1069                         /* search using IndexedSearch from root directory. */ 
1070                         result 
= IndexedSearch(paramBlock
, fsRtDirID
); 
1077 /*****************************************************************************/ 
1079 pascal  OSErr   
NameFileSearch(ConstStr255Param volName
, 
1081                                                            ConstStr255Param fileName
, 
1084                                                            long *actMatchCount
, 
1088         CInfoPBRec              searchInfo1
, searchInfo2
; 
1091         static CatPositionRec catPosition
; 
1092         static short    lastVRefNum 
= 0; 
1094         /* get the real volume reference number */ 
1095         error 
= DetermineVRefNum(volName
, vRefNum
, &vRefNum
); 
1096         if ( error 
!= noErr 
) 
1099         pb
.csParam
.ioNamePtr 
= NULL
; 
1100         pb
.csParam
.ioVRefNum 
= vRefNum
; 
1101         pb
.csParam
.ioMatchPtr 
= matches
; 
1102         pb
.csParam
.ioReqMatchCount 
= reqMatchCount
; 
1103         if ( partial 
)  /* tell CatSearch what we're looking for: */ 
1105                 pb
.csParam
.ioSearchBits 
= fsSBPartialName 
+ fsSBFlAttrib
;       /* partial name file matches or */ 
1109                 pb
.csParam
.ioSearchBits 
=  fsSBFullName 
+ fsSBFlAttrib
;         /* full name file matches */ 
1111         pb
.csParam
.ioSearchInfo1 
= &searchInfo1
; 
1112         pb
.csParam
.ioSearchInfo2 
= &searchInfo2
; 
1113         pb
.csParam
.ioSearchTime 
= 0; 
1114         if ( (newSearch
) ||                             /* If caller specified new search */ 
1115                  (lastVRefNum 
!= vRefNum
) )     /* or if last search was to another volume, */ 
1117                 catPosition
.initialize 
= 0;     /* then search from beginning of catalog */ 
1119         pb
.csParam
.ioCatPosition 
= catPosition
; 
1120         pb
.csParam
.ioOptBuffer 
= GetTempBuffer(0x00004000, &pb
.csParam
.ioOptBufSize
); 
1122         /* search for fileName */ 
1123         searchInfo1
.hFileInfo
.ioNamePtr 
= (StringPtr
)fileName
; 
1124         searchInfo2
.hFileInfo
.ioNamePtr 
= NULL
; 
1126         /* only match files (not directories) */ 
1127         searchInfo1
.hFileInfo
.ioFlAttrib 
= 0x00; 
1128         searchInfo2
.hFileInfo
.ioFlAttrib 
= ioDirMask
; 
1130         error 
= PBCatSearchSyncCompat((CSParamPtr
)&pb
); 
1132         if ( (error 
== noErr
) ||                                                        /* If no errors or the end of catalog was */ 
1133                  (error 
== eofErr
) )                                                    /* found, then the call was successful so */ 
1135                 *actMatchCount 
= pb
.csParam
.ioActMatchCount
;    /* return the match count */ 
1139                 *actMatchCount 
= 0;                                                     /* else no matches found */ 
1142         if ( (error 
== noErr
) ||                                                /* If no errors */ 
1143                  (error 
== catChangedErr
) )                                     /* or there was a change in the catalog */ 
1145                 catPosition 
= pb
.csParam
.ioCatPosition
; 
1146                 lastVRefNum 
= vRefNum
; 
1147                         /* we can probably start the next search where we stopped this time */ 
1151                 catPosition
.initialize 
= 0; 
1152                         /* start the next search from beginning of catalog */ 
1155         if ( pb
.csParam
.ioOptBuffer 
!= NULL 
) 
1157                 DisposePtr(pb
.csParam
.ioOptBuffer
); 
1163 /*****************************************************************************/ 
1165 pascal  OSErr   
CreatorTypeFileSearch(ConstStr255Param volName
, 
1171                                                                           long *actMatchCount
, 
1174         CInfoPBRec              searchInfo1
, searchInfo2
; 
1177         static CatPositionRec catPosition
; 
1178         static short    lastVRefNum 
= 0; 
1180         /* get the real volume reference number */ 
1181         error 
= DetermineVRefNum(volName
, vRefNum
, &vRefNum
); 
1182         if ( error 
!= noErr 
) 
1185         pb
.csParam
.ioNamePtr 
= NULL
; 
1186         pb
.csParam
.ioVRefNum 
= vRefNum
; 
1187         pb
.csParam
.ioMatchPtr 
= matches
; 
1188         pb
.csParam
.ioReqMatchCount 
= reqMatchCount
; 
1189         pb
.csParam
.ioSearchBits 
= fsSBFlAttrib 
+ fsSBFlFndrInfo
;        /* Looking for finder info file matches */ 
1190         pb
.csParam
.ioSearchInfo1 
= &searchInfo1
; 
1191         pb
.csParam
.ioSearchInfo2 
= &searchInfo2
; 
1192         pb
.csParam
.ioSearchTime 
= 0; 
1193         if ( (newSearch
) ||                             /* If caller specified new search */ 
1194                  (lastVRefNum 
!= vRefNum
) )     /* or if last search was to another volume, */ 
1196                 catPosition
.initialize 
= 0;     /* then search from beginning of catalog */ 
1198         pb
.csParam
.ioCatPosition 
= catPosition
; 
1199         pb
.csParam
.ioOptBuffer 
= GetTempBuffer(0x00004000, &pb
.csParam
.ioOptBufSize
); 
1202         searchInfo1
.hFileInfo
.ioNamePtr 
= NULL
; 
1203         searchInfo2
.hFileInfo
.ioNamePtr 
= NULL
; 
1205         /* only match files (not directories) */ 
1206         searchInfo1
.hFileInfo
.ioFlAttrib 
= 0x00; 
1207         searchInfo2
.hFileInfo
.ioFlAttrib 
= ioDirMask
; 
1209         /* search for creator; if creator = 0x00000000, ignore creator */ 
1210         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= creator
; 
1211         if ( creator 
== (OSType
)0x00000000 ) 
1213                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= (OSType
)0x00000000; 
1217                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= (OSType
)0xffffffff; 
1220         /* search for fileType; if fileType = 0x00000000, ignore fileType */ 
1221         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdType 
= fileType
; 
1222         if ( fileType 
== (OSType
)0x00000000 ) 
1224                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdType 
= (OSType
)0x00000000; 
1228                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdType 
= (OSType
)0xffffffff; 
1231         /* zero all other FInfo fields */ 
1232         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdFlags 
= 0; 
1233         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdLocation
.v 
= 0; 
1234         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdLocation
.h 
= 0; 
1235         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdFldr 
= 0; 
1237         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdFlags 
= 0; 
1238         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdLocation
.v 
= 0; 
1239         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdLocation
.h 
= 0; 
1240         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdFldr 
= 0; 
1242         error 
= PBCatSearchSyncCompat((CSParamPtr
)&pb
); 
1244         if ( (error 
== noErr
) ||                                                        /* If no errors or the end of catalog was */ 
1245                  (error 
== eofErr
) )                                                    /* found, then the call was successful so */ 
1247                 *actMatchCount 
= pb
.csParam
.ioActMatchCount
;    /* return the match count */ 
1251                 *actMatchCount 
= 0;                                                     /* else no matches found */ 
1254         if ( (error 
== noErr
) ||                                                /* If no errors */ 
1255                  (error 
== catChangedErr
) )                                     /* or there was a change in the catalog */ 
1257                 catPosition 
= pb
.csParam
.ioCatPosition
; 
1258                 lastVRefNum 
= vRefNum
; 
1259                         /* we can probably start the next search where we stopped this time */ 
1263                 catPosition
.initialize 
= 0; 
1264                         /* start the next search from beginning of catalog */ 
1267         if ( pb
.csParam
.ioOptBuffer 
!= NULL 
) 
1269                 DisposePtr(pb
.csParam
.ioOptBuffer
); 
1275 /*****************************************************************************/