4         Contains:       IndexedSearch and the PBCatSearch compatibility function. 
   8         Copyright:      © 1992-2001 by Apple Computer, Inc., all rights reserved. 
  10         You may incorporate this sample code into your applications without 
  11         restriction, though the sample code has been provided "AS IS" and the 
  12         responsibility for its operation is 100% yours.  However, what you are 
  13         not permitted to do is to redistribute the source as "DSC Sample Code" 
  14         after having made changes. If you're going to re-distribute the source, 
  15         we require that you make it clear in the source that the code was 
  16         descended from Apple Sample Code, but that you've made changes. 
  22                 Other Contact:          Apple Macintosh Developer Technical Support 
  23                                                         <http://developer.apple.com/bugreporter/> 
  25                 Technology:                     DTS Sample Code 
  31         Change History (most recent first): 
  33                  <2>      2/7/01        JL              Added standard header. Updated names of includes. Updated 
  34                                                                         various routines to use new calling convention of the 
  35                                                                         MoreFilesExtras accessor functions. Added TARGET_API_MAC_CARBON 
  36                                                                         conditional checks around TimeOutTask. 
  37                 <1>             12/06/99        JL              MoreFiles 1.5. 
  43 #include <MacErrors.h> 
  44 #include <MacMemory.h> 
  46 #include <TextUtils.h> 
  48 #define __COMPILINGMOREFILES 
  50 #include "MoreFiles.h" 
  51 #include "MoreFilesExtras.h" 
  55 /*****************************************************************************/ 
  59         /* Number of LevelRecs to add each time the searchStack is grown */ 
  60         /* 20 levels is probably more than reasonable for most volumes. */ 
  61         /* If more are needed, they are allocated 20 levels at a time. */ 
  62         kAdditionalLevelRecs 
= 20 
  65 /*****************************************************************************/ 
  68 **      LevelRecs are used to store the directory ID and index whenever 
  69 **      IndexedSearch needs to either scan a sub-directory, or return control 
  70 **      to the caller because the call has timed out or the number of 
  71 **      matches requested has been found. LevelRecs are stored in an array 
  76         long    dirModDate
;     /* for detecting most (but not all) catalog changes */ 
  80 typedef struct LevelRec LevelRec
; 
  81 typedef LevelRec 
*LevelRecPtr
, **LevelRecHandle
; 
  85 **      SearchPositionRec is my version of a CatPositionRec. It holds the 
  86 **      information I need to resuming searching. 
  88 #if PRAGMA_STRUCT_ALIGN 
  89 #pragma options align=mac68k 
  91 struct SearchPositionRec
 
  93         long                    initialize
;             /* Goofy checksum of volume information used to make */ 
  94                                                                         /* sure we're resuming a search on the same volume. */ 
  95         unsigned short  stackDepth
;             /* Current depth on searchStack. */ 
  96         short                   priv
[11];               /* For future use... */ 
  98 #if PRAGMA_STRUCT_ALIGN 
  99 #pragma options align=reset 
 101 typedef struct SearchPositionRec SearchPositionRec
; 
 102 typedef SearchPositionRec 
*SearchPositionRecPtr
; 
 106 **      ExtendedTMTask is a TMTask record extended to hold the timer flag. 
 108 #if PRAGMA_STRUCT_ALIGN 
 109 #pragma options align=mac68k 
 111 struct ExtendedTMTask
 
 114         Boolean                 stopSearch
;             /* the Time Mgr task will set stopSearch to */ 
 115                                                                         /* true when the timer expires */ 
 117 #if PRAGMA_STRUCT_ALIGN 
 118 #pragma options align=reset 
 120 typedef struct ExtendedTMTask ExtendedTMTask
; 
 121 typedef ExtendedTMTask 
*ExtendedTMTaskPtr
; 
 123 /*****************************************************************************/ 
 125 static  OSErr   
CheckVol(ConstStr255Param pathname
, 
 130 static  OSErr   
CheckStack(unsigned short stackDepth
, 
 131                                                    LevelRecHandle searchStack
, 
 132                                                    Size 
*searchStackSize
); 
 134 static  OSErr   
VerifyUserPB(CSParamPtr userPB
, 
 135                                                          Boolean 
*includeFiles
, 
 136                                                          Boolean 
*includeDirs
, 
 137                                                          Boolean 
*includeNames
); 
 139 static  Boolean 
IsSubString(ConstStr255Param aStringPtr
, 
 140                                                         ConstStr255Param subStringPtr
); 
 142 static  Boolean 
CompareMasked(const long *data1
, 
 145                                                           short longsToCompare
); 
 147 static  void    CheckForMatches(CInfoPBPtr cPB
, 
 149                                                                 const Str63 matchName
, 
 150                                                                 Boolean includeFiles
, 
 151                                                                 Boolean includeDirs
); 
 153 #if     __WANTPASCALELIMINATION 
 157 #if TARGET_RT_MAC_CFM || TARGET_API_MAC_CARBON 
 159 static  pascal  void    TimeOutTask(TMTaskPtr tmTaskPtr
); 
 163 static  pascal  TMTaskPtr       
GetTMTaskPtr(void); 
 165 static  void    TimeOutTask(void); 
 169 #if     __WANTPASCALELIMINATION 
 173 static  long    GetDirModDate(short vRefNum
, 
 176 /*****************************************************************************/ 
 179 **      CheckVol gets the volume's real vRefNum and builds a volID. The volID 
 180 **      is used to help insure that calls to resume searching with IndexedSearch 
 181 **      are to the same volume as the last call to IndexedSearch. 
 183 static  OSErr   
CheckVol(ConstStr255Param pathname
, 
 191         error 
= GetVolumeInfoNoName(pathname
, vRefNum
, &pb
); 
 192         if ( error 
== noErr 
) 
 194                 /* Return the real vRefNum */ 
 195                 *realVRefNum 
= pb
.volumeParam
.ioVRefNum
; 
 197                 /* Add together a bunch of things that aren't supposed to change on */ 
 198                 /* a mounted volume that's being searched and that should come up with */ 
 199                 /* a fairly unique number */ 
 200                 *volID 
= pb
.volumeParam
.ioVCrDate 
+ 
 201                                  pb
.volumeParam
.ioVRefNum 
+ 
 202                                  pb
.volumeParam
.ioVNmAlBlks 
+ 
 203                                  pb
.volumeParam
.ioVAlBlkSiz 
+ 
 204                                  pb
.volumeParam
.ioVFSID
; 
 209 /*****************************************************************************/ 
 212 **      CheckStack checks the size of the search stack (array) to see if there's 
 213 **      room to push another LevelRec. If not, CheckStack grows the stack by 
 214 **      another kAdditionalLevelRecs elements. 
 216 static  OSErr   
CheckStack(unsigned short stackDepth
, 
 217                                                    LevelRecHandle searchStack
, 
 218                                                    Size 
*searchStackSize
) 
 222         if ( (*searchStackSize 
/ sizeof(LevelRec
)) == (stackDepth 
+ 1) ) 
 224                 /* Time to grow stack */ 
 225                 SetHandleSize((Handle
)searchStack
, *searchStackSize 
+ (kAdditionalLevelRecs 
* sizeof(LevelRec
))); 
 226                 result 
= MemError();    /* should be noErr */ 
 227                 *searchStackSize 
= GetHandleSize((Handle
)searchStack
); 
 237 /*****************************************************************************/ 
 240 **      VerifyUserPB makes sure the parameter block passed to IndexedSearch has 
 241 **      valid parameters. By making this check once, we don't have to worry about 
 242 **      things like NULL pointers, strings being too long, etc. 
 243 **      VerifyUserPB also determines if the search includes files and/or 
 244 **      directories, and determines if a full or partial name search was requested. 
 246 static  OSErr   
VerifyUserPB(CSParamPtr userPB
, 
 247                                                          Boolean 
*includeFiles
, 
 248                                                          Boolean 
*includeDirs
, 
 249                                                          Boolean 
*includeNames
) 
 251         CInfoPBPtr      searchInfo1
; 
 252         CInfoPBPtr      searchInfo2
; 
 254         searchInfo1 
= userPB
->ioSearchInfo1
; 
 255         searchInfo2 
= userPB
->ioSearchInfo2
; 
 257         /* ioMatchPtr cannot be NULL */ 
 258         if ( userPB
->ioMatchPtr 
== NULL 
) 
 263         /* ioSearchInfo1 cannot be NULL */ 
 264         if ( searchInfo1 
== NULL 
) 
 269         /* If any bits except partialName, fullName, or negate are set, then */ 
 270         /* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required  */ 
 271         if ( ((userPB
->ioSearchBits 
& ~(fsSBPartialName 
| fsSBFullName 
| fsSBNegate
)) != 0) && 
 272                  ( searchInfo2 
== NULL 
)) 
 277         *includeFiles 
= false; 
 278         *includeDirs 
= false; 
 279         *includeNames 
= false; 
 281         if ( (userPB
->ioSearchBits 
& (fsSBPartialName 
| fsSBFullName
)) != 0 ) 
 283                 /* If any kind of name matching is requested, then ioNamePtr in */ 
 284                 /* ioSearchInfo1 cannot be NULL or a zero-length string */ 
 285                 if ( (searchInfo1
->hFileInfo
.ioNamePtr 
== NULL
) || 
 286                          (searchInfo1
->hFileInfo
.ioNamePtr
[0] == 0) || 
 287                          (searchInfo1
->hFileInfo
.ioNamePtr
[0] > (sizeof(Str63
) - 1)) ) 
 292                 *includeNames 
= true; 
 295         if ( (userPB
->ioSearchBits 
& fsSBFlAttrib
) != 0 ) 
 297                 /* The only attributes you can search on are the directory flag */ 
 298                 /* and the locked flag. */ 
 299                 if ( (searchInfo2
->hFileInfo
.ioFlAttrib 
& ~(kioFlAttribDirMask 
| kioFlAttribLockedMask
)) != 0 ) 
 304                 /* interested in the directory bit? */ 
 305                 if ( (searchInfo2
->hFileInfo
.ioFlAttrib 
& kioFlAttribDirMask
) != 0 ) 
 307                         /* yes, so do they want just directories or just files? */ 
 308                         if ( (searchInfo1
->hFileInfo
.ioFlAttrib 
& kioFlAttribDirMask
) != 0 ) 
 314                                 *includeFiles 
= true; 
 319                         /* no interest in directory bit - get both files and directories */ 
 321                         *includeFiles 
= true; 
 326                 /* no attribute checking - get both files and directories */ 
 328                 *includeFiles 
= true; 
 331         /* If directories are included in the search, */ 
 332         /* then the locked attribute cannot be requested. */ 
 334                  ((userPB
->ioSearchBits 
& fsSBFlAttrib
) != 0) && 
 335                  ((searchInfo2
->hFileInfo
.ioFlAttrib 
& kioFlAttribLockedMask
) != 0) ) 
 340         /* If files are included in the search, then there cannot be */ 
 341         /* a search on the number of files. */ 
 342         if ( *includeFiles 
&& 
 343                  ((userPB
->ioSearchBits 
& fsSBDrNmFls
) != 0) ) 
 348         /* If directories are included in the search, then there cannot */ 
 349         /* be a search on file lengths. */ 
 351                  ((userPB
->ioSearchBits 
& (fsSBFlLgLen 
| fsSBFlPyLen 
| fsSBFlRLgLen 
| fsSBFlRPyLen
)) != 0) ) 
 362 /*****************************************************************************/ 
 365 **      IsSubString checks to see if a string is a substring of another string. 
 366 **      Both input strings have already been converted to all uppercase using 
 367 **      UprString (the same non-international call the File Manager uses). 
 369 static  Boolean 
IsSubString(ConstStr255Param aStringPtr
, 
 370                                                         ConstStr255Param subStringPtr
) 
 372         short   strLength
;              /* length of string */ 
 373         short   subStrLength
;   /* length of subString */ 
 374         Boolean found
;                  /* result of test */ 
 375         short   index
;                  /* current index into string */ 
 378         strLength 
= aStringPtr
[0]; 
 379         subStrLength 
= subStringPtr
[0]; 
 381         if ( subStrLength 
<= strLength
) 
 383                 register short  count
;                  /* search counter */ 
 384                 register short  strIndex
;               /* running index into string */ 
 385                 register short  subStrIndex
;    /* running index into subString */ 
 387                 /* start looking at first character */ 
 390                 /* continue looking until remaining string is shorter than substring */ 
 391                 count 
= strLength 
- subStrLength 
+ 1; 
 395                         strIndex 
= index
;       /* start string index at index */ 
 396                         subStrIndex 
= 1;        /* start subString index at 1 */ 
 398                         while ( !found 
&& (aStringPtr
[strIndex
] == subStringPtr
[subStrIndex
]) ) 
 400                                 if ( subStrIndex 
== subStrLength 
) 
 402                                         /* all characters in subString were found */ 
 407                                         /* check next character of substring against next character of string */ 
 415                                 /* start substring search again at next string character */ 
 419                 } while ( count 
!= 0 && (!found
) ); 
 425 /*****************************************************************************/ 
 428 **      CompareMasked does a bitwise comparison with mask on 1 or more longs. 
 429 **      data1 and data2 are first exclusive-ORed together resulting with bits set 
 430 **      where they are different. That value is then ANDed with the mask resulting 
 431 **      with bits set if the test fails. true is returned if the tests pass. 
 433 static  Boolean 
CompareMasked(const long *data1
, 
 436                                                           short longsToCompare
) 
 438         Boolean result 
= true; 
 440         while ( (longsToCompare 
!= 0) && (result 
== true) ) 
 442                 /* (*data1 ^ *data2) = bits that are different, so... */ 
 443                 /* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */ 
 445                 if ( ((*data1 
^ *data2
) & *mask
) != 0 ) 
 457 /*****************************************************************************/ 
 460 **      Check for matches compares the search criteria in userPB to the file 
 461 **      system object in cPB. If there's a match, then the information in cPB is 
 462 **      is added to the match array and the actual match count is incremented. 
 464 static  void    CheckForMatches(CInfoPBPtr cPB
, 
 466                                                                 const Str63 matchName
, 
 467                                                                 Boolean includeFiles
, 
 471         CInfoPBPtr      searchInfo1
; 
 472         CInfoPBPtr      searchInfo2
; 
 473         Str63           itemName
;               /* copy of object's name for partial name matching */ 
 476         foundMatch 
= false;                     /* default to no match */ 
 478         searchBits 
= userPB
->ioSearchBits
; 
 479         searchInfo1 
= userPB
->ioSearchInfo1
; 
 480         searchInfo2 
= userPB
->ioSearchInfo2
; 
 482         /* Into the if statements that go on forever... */ 
 484         if ( (cPB
->hFileInfo
.ioFlAttrib 
& kioFlAttribDirMask
) == 0 ) 
 499         if ( (searchBits 
& fsSBPartialName
) != 0 ) 
 501                 if ( (cPB
->hFileInfo
.ioNamePtr
[0] > 0) && 
 502                          (cPB
->hFileInfo
.ioNamePtr
[0] <= (sizeof(Str63
) - 1)) ) 
 504                         /* Make uppercase copy of object name */ 
 505                         BlockMoveData(cPB
->hFileInfo
.ioNamePtr
, 
 507                                                         cPB
->hFileInfo
.ioNamePtr
[0] + 1); 
 508                         /* Use the same non-international call the File Manager uses */ 
 509                         UpperString(itemName
, true); 
 517                         if ( !IsSubString(itemName
, matchName
) ) 
 521                         else if ( searchBits 
== fsSBPartialName 
) 
 523                                 /* optimize for name matching only since it is most common way to search */ 
 529         if ( (searchBits 
& fsSBFullName
) != 0 ) 
 531                 /* Use the same non-international call the File Manager uses */ 
 532                 if ( !EqualString(cPB
->hFileInfo
.ioNamePtr
, matchName
, false, true) ) 
 536                 else if ( searchBits 
== fsSBFullName 
) 
 538                         /* optimize for name matching only since it is most common way to search */ 
 543         if ( (searchBits 
& fsSBFlParID
) != 0 ) 
 545                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlParID
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlParID
)) || 
 546                          ((unsigned long)(cPB
->hFileInfo
.ioFlParID
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlParID
)) ) 
 552         if ( (searchBits 
& fsSBFlAttrib
) != 0 ) 
 554                 if ( ((cPB
->hFileInfo
.ioFlAttrib 
^ searchInfo1
->hFileInfo
.ioFlAttrib
) & 
 555                           searchInfo2
->hFileInfo
.ioFlAttrib
) != 0 ) 
 561         if ( (searchBits 
& fsSBDrNmFls
) != 0 ) 
 563                 if ( ((unsigned long)(cPB
->dirInfo
.ioDrNmFls
) < (unsigned long)(searchInfo1
->dirInfo
.ioDrNmFls
)) || 
 564                          ((unsigned long)(cPB
->dirInfo
.ioDrNmFls
) > (unsigned long)(searchInfo2
->dirInfo
.ioDrNmFls
)) ) 
 570         if ( (searchBits 
& fsSBFlFndrInfo
) != 0 )       /* fsSBFlFndrInfo is same as fsSBDrUsrWds */ 
 572                 if ( !CompareMasked((long *)&(cPB
->hFileInfo
.ioFlFndrInfo
), 
 573                                                         (long *)&(searchInfo1
->hFileInfo
.ioFlFndrInfo
), 
 574                                                         (long *)&(searchInfo2
->hFileInfo
.ioFlFndrInfo
), 
 575                                                         sizeof(FInfo
) / sizeof(long)) ) 
 581         if ( (searchBits 
& fsSBFlXFndrInfo
) != 0 )      /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */ 
 583                 if ( !CompareMasked((long *)&(cPB
->hFileInfo
.ioFlXFndrInfo
), 
 584                                                         (long *)&(searchInfo1
->hFileInfo
.ioFlXFndrInfo
), 
 585                                                         (long *)&(searchInfo2
->hFileInfo
.ioFlXFndrInfo
), 
 586                                                         sizeof(FXInfo
) / sizeof(long)) ) 
 592         if ( (searchBits 
& fsSBFlLgLen
) != 0 ) 
 594                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlLgLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlLgLen
)) || 
 595                          ((unsigned long)(cPB
->hFileInfo
.ioFlLgLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlLgLen
)) ) 
 601         if ( (searchBits 
& fsSBFlPyLen
) != 0 ) 
 603                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlPyLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlPyLen
)) || 
 604                          ((unsigned long)(cPB
->hFileInfo
.ioFlPyLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlPyLen
)) ) 
 610         if ( (searchBits 
& fsSBFlRLgLen
) != 0 ) 
 612                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlRLgLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlRLgLen
)) || 
 613                          ((unsigned long)(cPB
->hFileInfo
.ioFlRLgLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlRLgLen
)) ) 
 619         if ( (searchBits 
& fsSBFlRPyLen
) != 0 ) 
 621                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlRPyLen
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlRPyLen
)) || 
 622                          ((unsigned long)(cPB
->hFileInfo
.ioFlRPyLen
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlRPyLen
)) ) 
 628         if ( (searchBits 
& fsSBFlCrDat
) != 0 )  /* fsSBFlCrDat is same as fsSBDrCrDat */ 
 630                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlCrDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlCrDat
)) || 
 631                          ((unsigned long)(cPB
->hFileInfo
.ioFlCrDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlCrDat
)) ) 
 637         if ( (searchBits 
& fsSBFlMdDat
) != 0 )  /* fsSBFlMdDat is same as fsSBDrMdDat */ 
 639                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlMdDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlMdDat
)) || 
 640                          ((unsigned long)(cPB
->hFileInfo
.ioFlMdDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlMdDat
)) ) 
 646         if ( (searchBits 
& fsSBFlBkDat
) != 0 )  /* fsSBFlBkDat is same as fsSBDrBkDat */ 
 648                 if ( ((unsigned long)(cPB
->hFileInfo
.ioFlBkDat
) < (unsigned long)(searchInfo1
->hFileInfo
.ioFlBkDat
)) || 
 649                          ((unsigned long)(cPB
->hFileInfo
.ioFlBkDat
) > (unsigned long)(searchInfo2
->hFileInfo
.ioFlBkDat
)) ) 
 655         /* Hey, we passed all of the tests! */ 
 660 /* foundMatch is false if code jumps to Failed */ 
 662         /* Do we reverse our findings? */ 
 663         if ( (searchBits 
& fsSBNegate
) != 0 ) 
 665                 foundMatch 
= !foundMatch
;       /* matches are not, not matches are */ 
 671                 /* Move the match into the match buffer */ 
 672                 userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].vRefNum 
= cPB
->hFileInfo
.ioVRefNum
; 
 673                 userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].parID 
= cPB
->hFileInfo
.ioFlParID
; 
 674                 if ( cPB
->hFileInfo
.ioNamePtr
[0] > 63 ) 
 676                         cPB
->hFileInfo
.ioNamePtr
[0] = 63; 
 678                 BlockMoveData(cPB
->hFileInfo
.ioNamePtr
, 
 679                                           userPB
->ioMatchPtr
[userPB
->ioActMatchCount
].name
, 
 680                                           cPB
->hFileInfo
.ioNamePtr
[0] + 1); 
 682                 /* increment the actual count */ 
 683                 ++(userPB
->ioActMatchCount
); 
 687 /*****************************************************************************/ 
 690 **      TimeOutTask is executed when the timer goes off. It simply sets the 
 691 **      stopSearch field to true. After each object is found and possibly added 
 692 **      to the matches buffer, stopSearch is checked to see if the search should 
 696 #if     __WANTPASCALELIMINATION 
 700 #if TARGET_RT_MAC_CFM || TARGET_API_MAC_CARBON 
 702 static  pascal  void    TimeOutTask(TMTaskPtr tmTaskPtr
) 
 704         ((ExtendedTMTaskPtr
)tmTaskPtr
)->stopSearch 
= true; 
 709 static  pascal  TMTaskPtr       
GetTMTaskPtr(void) 
 710         ONEWORDINLINE(0x2e89);  /* MOVE.L A1,(SP) */ 
 712 static  void    TimeOutTask(void) 
 714         ((ExtendedTMTaskPtr
)GetTMTaskPtr())->stopSearch 
= true; 
 719 #if     __WANTPASCALELIMINATION 
 723 /*****************************************************************************/ 
 726 **      GetDirModDate returns the modification date of a directory. If there is 
 727 **      an error getting the modification date, -1 is returned to indicate 
 728 **      something went wrong. 
 730 static  long    GetDirModDate(short vRefNum
, 
 737         /* Protection against File Sharing problem */ 
 739         pb
.dirInfo
.ioNamePtr 
= tempName
; 
 740         pb
.dirInfo
.ioVRefNum 
= vRefNum
; 
 741         pb
.dirInfo
.ioDrDirID 
= dirID
; 
 742         pb
.dirInfo
.ioFDirIndex 
= -1;    /* use ioDrDirID */ 
 744         if ( PBGetCatInfoSync(&pb
) == noErr 
) 
 746                 modDate 
= pb
.dirInfo
.ioDrMdDat
; 
 756 /*****************************************************************************/ 
 758 pascal  OSErr   
IndexedSearch(CSParamPtr pb
, 
 761         static LevelRecHandle   searchStack 
= NULL
;             /* static handle to LevelRec stack */ 
 762         static Size                             searchStackSize 
= 0;    /* size of static handle */ 
 763         SearchPositionRecPtr    catPosition
; 
 766         ExtendedTMTask                  timerTask
; 
 772         Boolean                                 includeFiles
; 
 774         Boolean                                 includeNames
; 
 777         timerTask
.stopSearch 
= false;   /* don't stop yet! */ 
 779         /* If request has a timeout, install a Time Manager task. */ 
 780         if ( pb
->ioSearchTime 
!= 0 ) 
 783                 timerTask
.theTask
.tmAddr 
= NewTimerUPP(TimeOutTask
); 
 784                 InsTime((QElemPtr
)&(timerTask
.theTask
)); 
 785                 PrimeTime((QElemPtr
)&(timerTask
.theTask
), pb
->ioSearchTime
); 
 788         /* Check the parameter block passed for things that we don't want to assume */ 
 789         /* are OK later in the code. For example, make sure pointers to data structures */ 
 790         /* and buffers are not NULL.  And while we're in there, see if the request */ 
 791         /* specified searching for files, directories, or both, and see if the search */ 
 792         /* was by full or partial name. */ 
 793         result 
= VerifyUserPB(pb
, &includeFiles
, &includeDirs
, &includeNames
); 
 794         if ( result 
== noErr 
) 
 796                 pb
->ioActMatchCount 
= 0;        /* no matches yet */ 
 800                         /* The search includes seach by full or partial name. */ 
 801                         /* Make an upper case copy of the match string to pass to */ 
 802                         /* CheckForMatches. */ 
 803                         BlockMoveData(pb
->ioSearchInfo1
->hFileInfo
.ioNamePtr
, 
 805                                                         pb
->ioSearchInfo1
->hFileInfo
.ioNamePtr
[0] + 1); 
 806                         /* Use the same non-international call the File Manager uses */ 
 807                         UpperString(upperName
, true); 
 810                 /* Prevent casting to my type throughout code */ 
 811                 catPosition 
= (SearchPositionRecPtr
)&pb
->ioCatPosition
; 
 813                 /* Create searchStack first time called */ 
 814                 if ( searchStack 
== NULL 
) 
 816                         searchStack 
= (LevelRecHandle
)NewHandle(kAdditionalLevelRecs 
* sizeof(LevelRec
)); 
 819                 /* Make sure searchStack really exists */ 
 820                 if ( searchStack 
!= NULL 
) 
 822                         searchStackSize 
= GetHandleSize((Handle
)searchStack
); 
 824                         /* See if the search is a new search or a resumed search. */ 
 825                         if ( catPosition
->initialize 
== 0 ) 
 829                                 /* Get the real vRefNum and fill in catPosition->initialize. */  
 830                                 result 
= CheckVol(pb
->ioNamePtr
, pb
->ioVRefNum
, &realVRefNum
, &catPosition
->initialize
); 
 831                                 if ( result 
== noErr 
) 
 833                                         /* clear searchStack */ 
 834                                         catPosition
->stackDepth 
= 0; 
 836                                         /* use dirID parameter passed and... */ 
 837                                         index 
= -1;     /* start with the passed directory itself! */ 
 842                                 /* We're resuming a search. */ 
 844                                 /* Get the real vRefNum and make sure catPosition->initialize is valid. */  
 845                                 result 
= CheckVol(pb
->ioNamePtr
, pb
->ioVRefNum
, &realVRefNum
, &tempLong
); 
 846                                 if ( result 
== noErr 
) 
 848                                         /* Make sure the resumed search is to the same volume! */ 
 849                                         if ( catPosition
->initialize 
== tempLong 
) 
 851                                                 /* For resume, catPosition->stackDepth > 0 */ 
 852                                                 if ( catPosition
->stackDepth 
> 0 ) 
 854                                                         /* Position catPosition->stackDepth to access last saved level */ 
 855                                                         --(catPosition
->stackDepth
); 
 857                                                         /* Get the dirID and index for the next item */ 
 858                                                         dirID 
= (*searchStack
)[catPosition
->stackDepth
].dirID
; 
 859                                                         index 
= (*searchStack
)[catPosition
->stackDepth
].index
; 
 861                                                         /* Check the dir's mod date against the saved mode date on our "stack" */ 
 862                                                         modDate 
= GetDirModDate(realVRefNum
, dirID
); 
 863                                                         if ( modDate 
!= (*searchStack
)[catPosition
->stackDepth
].dirModDate 
) 
 865                                                                 result 
= catChangedErr
; 
 870                                                         /* Invalid catPosition record was passed */ 
 876                                                 /* The volume is not the same */ 
 877                                                 result 
= catChangedErr
; 
 882                         if ( result 
== noErr 
) 
 884                                 /* ioNamePtr and ioVRefNum only need to be set up once. */ 
 885                                 cPB
.hFileInfo
.ioNamePtr 
= itemName
; 
 886                                 cPB
.hFileInfo
.ioVRefNum 
= realVRefNum
; 
 889                                 **      Here's the loop that: 
 890                                 **              Finds the next item on the volume. 
 891                                 **              If noErr, calls the code to check for matches and add matches 
 892                                 **                      to the match buffer. 
 893                                 **              Sets up dirID and index for to find the next item on the volume. 
 895                                 **      The looping ends when: 
 896                                 **              (a) an unexpected error is returned by PBGetCatInfo. All that 
 897                                 **                      is expected is noErr and fnfErr (after the last item in a 
 898                                 **                      directory is found). 
 899                                 **              (b) the caller specified a timeout and our Time Manager task 
 901                                 **              (c) the number of matches requested by the caller has been found. 
 902                                 **              (d) the last item on the volume was found. 
 906                                         /* get the next item */ 
 907                                         cPB
.hFileInfo
.ioFDirIndex 
= index
; 
 908                                         cPB
.hFileInfo
.ioDirID 
= dirID
; 
 909                                         result 
= PBGetCatInfoSync(&cPB
); 
 912                                                 if ( result 
== noErr 
) 
 914                                                         /* We found something */ 
 916                                                         CheckForMatches(&cPB
, pb
, upperName
, includeFiles
, includeDirs
); 
 919                                                         if ( (cPB
.dirInfo
.ioFlAttrib 
& kioFlAttribDirMask
) != 0 ) 
 921                                                                 /* It's a directory */ 
 923                                                                 result 
= CheckStack(catPosition
->stackDepth
, searchStack
, &searchStackSize
); 
 924                                                                 if ( result 
== noErr 
) 
 926                                                                         /* Save the current state on the searchStack */ 
 927                                                                         /* when we come back, this is where we'll start */ 
 928                                                                         (*searchStack
)[catPosition
->stackDepth
].dirID 
= dirID
; 
 929                                                                         (*searchStack
)[catPosition
->stackDepth
].index 
= index
; 
 930                                                                         (*searchStack
)[catPosition
->stackDepth
].dirModDate 
= GetDirModDate(realVRefNum
, dirID
); 
 932                                                                         /* position catPosition->stackDepth for next saved level */ 
 933                                                                         ++(catPosition
->stackDepth
); 
 935                                                                         /* The next item to get is the 1st item in the child directory */ 
 936                                                                         dirID 
= cPB
.dirInfo
.ioDrDirID
; 
 940                                                         /* else do nothing for files */ 
 944                                                         /* End of directory found (or we had some error and that */ 
 945                                                         /* means we have to drop out of this directory). */ 
 946                                                         /* Restore last thing put on stack and */ 
 947                                                         /* see if we need to continue or quit. */ 
 948                                                         if ( catPosition
->stackDepth 
> 0 ) 
 950                                                                 /* position catPosition->stackDepth to access last saved level */ 
 951                                                                 --(catPosition
->stackDepth
); 
 953                                                                 dirID 
= (*searchStack
)[catPosition
->stackDepth
].dirID
; 
 954                                                                 index 
= (*searchStack
)[catPosition
->stackDepth
].index
; 
 956                                                                 /* Check the dir's mod date against the saved mode date on our "stack" */ 
 957                                                                 modDate 
= GetDirModDate(realVRefNum
, dirID
); 
 958                                                                 if ( modDate 
!= (*searchStack
)[catPosition
->stackDepth
].dirModDate 
) 
 960                                                                         result 
= catChangedErr
; 
 964                                                                         /* Going back to ancestor directory. */ 
 965                                                                         /* Clear error so we can continue. */ 
 971                                                                 /* We hit the bottom of the stack, so we'll let the */ 
 972                                                                 /* the eofErr drop us out of the loop. */ 
 979                                                 /* Special case for index == -1; that means that we're starting */ 
 980                                                 /* a new search and so the first item to check is the directory */ 
 982                                                 if ( result 
== noErr 
) 
 984                                                         /* We found something */ 
 986                                                         CheckForMatches(&cPB
, pb
, upperName
, includeFiles
, includeDirs
); 
 988                                                         /* Now, set the index to 1 and then we're ready to look inside */ 
 989                                                         /* the passed directory. */ 
 993                                 } while ( (!timerTask
.stopSearch
) &&    /* timer hasn't fired */ 
 994                                                   (result 
== noErr
) &&                  /* no unexpected errors */ 
 995                                                   (pb
->ioReqMatchCount 
> pb
->ioActMatchCount
) ); /* we haven't found our limit */ 
 997                                 /* Did we drop out of the loop because of timeout or */ 
 998                                 /* ioReqMatchCount was found? */ 
 999                                 if ( result 
== noErr 
) 
1001                                         result 
= CheckStack(catPosition
->stackDepth
, searchStack
, &searchStackSize
); 
1002                                         if ( result 
== noErr 
) 
1004                                                 /* Either there was a timeout or ioReqMatchCount was reached. */ 
1005                                                 /* Save the dirID and index for the next time we're called. */ 
1007                                                 (*searchStack
)[catPosition
->stackDepth
].dirID 
= dirID
; 
1008                                                 (*searchStack
)[catPosition
->stackDepth
].index 
= index
; 
1009                                                 (*searchStack
)[catPosition
->stackDepth
].dirModDate 
= GetDirModDate(realVRefNum
, dirID
); 
1011                                                 /* position catPosition->stackDepth for next saved level */ 
1013                                                 ++(catPosition
->stackDepth
); 
1020                         /* searchStack Handle could not be allocated */ 
1021                         result 
= memFullErr
; 
1025         if ( pb
->ioSearchTime 
!= 0 ) 
1027                 /* Stop Time Manager task here if it was installed */ 
1028                 RmvTime((QElemPtr
)&(timerTask
.theTask
)); 
1029                 DisposeTimerUPP(timerTask
.theTask
.tmAddr
); 
1035 /*****************************************************************************/ 
1037 pascal OSErr 
PBCatSearchSyncCompat(CSParamPtr paramBlock
) 
1040         Boolean                                 supportsCatSearch
; 
1041         GetVolParmsInfoBuffer   volParmsInfo
; 
1043 #if !__MACOSSEVENORLATER 
1044         static Boolean                  fullExtFSDispatchingtested 
= false; 
1045         static Boolean                  hasFullExtFSDispatching 
= false; 
1051 #if !__MACOSSEVENORLATER 
1052         /* See if File Manager will pass CatSearch requests to external file systems */ 
1053         /* we'll store the results in a static variable so we don't have to call Gestalt */ 
1054         /* everytime we're called. (System 7.0 and later always do this) */ 
1055         if ( !fullExtFSDispatchingtested 
) 
1057                 fullExtFSDispatchingtested 
= true; 
1058                 if ( Gestalt(gestaltFSAttr
, &response
) == noErr 
) 
1060                         hasFullExtFSDispatching 
= ((response 
& (1L << gestaltFullExtFSDispatching
)) != 0); 
1065         /* CatSearch is a per volume attribute, so we have to check each time we're */ 
1066         /* called to see if it is available on the volume specified. */ 
1067         supportsCatSearch 
= false; 
1068 #if !__MACOSSEVENORLATER 
1069         if ( hasFullExtFSDispatching 
) 
1072                 infoSize 
= sizeof(GetVolParmsInfoBuffer
); 
1073                 result 
= HGetVolParms(paramBlock
->ioNamePtr
, paramBlock
->ioVRefNum
, 
1074                                                         &volParmsInfo
, &infoSize
); 
1075                 if ( result 
== noErr 
) 
1077                         supportsCatSearch 
= hasCatSearch(&volParmsInfo
); 
1081         /* noErr or paramErr is OK here. */ 
1082         /* paramErr just means that GetVolParms isn't supported by this volume */ 
1083         if ( (result 
== noErr
) || (result 
== paramErr
) ) 
1085                 if ( supportsCatSearch 
) 
1087                         /* Volume supports CatSearch so use it. */ 
1088                         /* CatSearch is faster than an indexed search. */ 
1089                         result 
= PBCatSearchSync(paramBlock
); 
1093                         /* Volume doesn't support CatSearch so */ 
1094                         /* search using IndexedSearch from root directory. */ 
1095                         result 
= IndexedSearch(paramBlock
, fsRtDirID
); 
1102 /*****************************************************************************/ 
1104 pascal  OSErr   
NameFileSearch(ConstStr255Param volName
, 
1106                                                            ConstStr255Param fileName
, 
1109                                                            long *actMatchCount
, 
1113         CInfoPBRec              searchInfo1
, searchInfo2
; 
1116         static CatPositionRec catPosition
; 
1117         static short    lastVRefNum 
= 0; 
1119         /* get the real volume reference number */ 
1120         error 
= DetermineVRefNum(volName
, vRefNum
, &vRefNum
); 
1121         if ( error 
!= noErr 
) 
1124         pb
.csParam
.ioNamePtr 
= NULL
; 
1125         pb
.csParam
.ioVRefNum 
= vRefNum
; 
1126         pb
.csParam
.ioMatchPtr 
= matches
; 
1127         pb
.csParam
.ioReqMatchCount 
= reqMatchCount
; 
1128         if ( partial 
)  /* tell CatSearch what we're looking for: */ 
1130                 pb
.csParam
.ioSearchBits 
= fsSBPartialName 
+ fsSBFlAttrib
;       /* partial name file matches or */ 
1134                 pb
.csParam
.ioSearchBits 
=  fsSBFullName 
+ fsSBFlAttrib
;         /* full name file matches */ 
1136         pb
.csParam
.ioSearchInfo1 
= &searchInfo1
; 
1137         pb
.csParam
.ioSearchInfo2 
= &searchInfo2
; 
1138         pb
.csParam
.ioSearchTime 
= 0; 
1139         if ( (newSearch
) ||                             /* If caller specified new search */ 
1140                  (lastVRefNum 
!= vRefNum
) )     /* or if last search was to another volume, */ 
1142                 catPosition
.initialize 
= 0;     /* then search from beginning of catalog */ 
1144         pb
.csParam
.ioCatPosition 
= catPosition
; 
1145         pb
.csParam
.ioOptBuffer 
= GetTempBuffer(0x00004000, &pb
.csParam
.ioOptBufSize
); 
1147         /* search for fileName */ 
1148         searchInfo1
.hFileInfo
.ioNamePtr 
= (StringPtr
)fileName
; 
1149         searchInfo2
.hFileInfo
.ioNamePtr 
= NULL
; 
1151         /* only match files (not directories) */ 
1152         searchInfo1
.hFileInfo
.ioFlAttrib 
= 0x00; 
1153         searchInfo2
.hFileInfo
.ioFlAttrib 
= kioFlAttribDirMask
; 
1155         error 
= PBCatSearchSyncCompat((CSParamPtr
)&pb
); 
1157         if ( (error 
== noErr
) ||                                                        /* If no errors or the end of catalog was */ 
1158                  (error 
== eofErr
) )                                                    /* found, then the call was successful so */ 
1160                 *actMatchCount 
= pb
.csParam
.ioActMatchCount
;    /* return the match count */ 
1164                 *actMatchCount 
= 0;                                                     /* else no matches found */ 
1167         if ( (error 
== noErr
) ||                                                /* If no errors */ 
1168                  (error 
== catChangedErr
) )                                     /* or there was a change in the catalog */ 
1170                 catPosition 
= pb
.csParam
.ioCatPosition
; 
1171                 lastVRefNum 
= vRefNum
; 
1172                         /* we can probably start the next search where we stopped this time */ 
1176                 catPosition
.initialize 
= 0; 
1177                         /* start the next search from beginning of catalog */ 
1180         if ( pb
.csParam
.ioOptBuffer 
!= NULL 
) 
1182                 DisposePtr(pb
.csParam
.ioOptBuffer
); 
1188 /*****************************************************************************/ 
1190 pascal  OSErr   
CreatorTypeFileSearch(ConstStr255Param volName
, 
1196                                                                           long *actMatchCount
, 
1199         CInfoPBRec              searchInfo1
, searchInfo2
; 
1202         static CatPositionRec catPosition
; 
1203         static short    lastVRefNum 
= 0; 
1205         /* get the real volume reference number */ 
1206         error 
= DetermineVRefNum(volName
, vRefNum
, &vRefNum
); 
1207         if ( error 
!= noErr 
) 
1210         pb
.csParam
.ioNamePtr 
= NULL
; 
1211         pb
.csParam
.ioVRefNum 
= vRefNum
; 
1212         pb
.csParam
.ioMatchPtr 
= matches
; 
1213         pb
.csParam
.ioReqMatchCount 
= reqMatchCount
; 
1214         pb
.csParam
.ioSearchBits 
= fsSBFlAttrib 
+ fsSBFlFndrInfo
;        /* Looking for finder info file matches */ 
1215         pb
.csParam
.ioSearchInfo1 
= &searchInfo1
; 
1216         pb
.csParam
.ioSearchInfo2 
= &searchInfo2
; 
1217         pb
.csParam
.ioSearchTime 
= 0; 
1218         if ( (newSearch
) ||                             /* If caller specified new search */ 
1219                  (lastVRefNum 
!= vRefNum
) )     /* or if last search was to another volume, */ 
1221                 catPosition
.initialize 
= 0;     /* then search from beginning of catalog */ 
1223         pb
.csParam
.ioCatPosition 
= catPosition
; 
1224         pb
.csParam
.ioOptBuffer 
= GetTempBuffer(0x00004000, &pb
.csParam
.ioOptBufSize
); 
1227         searchInfo1
.hFileInfo
.ioNamePtr 
= NULL
; 
1228         searchInfo2
.hFileInfo
.ioNamePtr 
= NULL
; 
1230         /* only match files (not directories) */ 
1231         searchInfo1
.hFileInfo
.ioFlAttrib 
= 0x00; 
1232         searchInfo2
.hFileInfo
.ioFlAttrib 
= kioFlAttribDirMask
; 
1234         /* search for creator; if creator = 0x00000000, ignore creator */ 
1235         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= creator
; 
1236         if ( creator 
== (OSType
)0x00000000 ) 
1238                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= (OSType
)0x00000000; 
1242                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdCreator 
= (OSType
)0xffffffff; 
1245         /* search for fileType; if fileType = 0x00000000, ignore fileType */ 
1246         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdType 
= fileType
; 
1247         if ( fileType 
== (OSType
)0x00000000 ) 
1249                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdType 
= (OSType
)0x00000000; 
1253                 searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdType 
= (OSType
)0xffffffff; 
1256         /* zero all other FInfo fields */ 
1257         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdFlags 
= 0; 
1258         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdLocation
.v 
= 0; 
1259         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdLocation
.h 
= 0; 
1260         searchInfo1
.hFileInfo
.ioFlFndrInfo
.fdFldr 
= 0; 
1262         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdFlags 
= 0; 
1263         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdLocation
.v 
= 0; 
1264         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdLocation
.h 
= 0; 
1265         searchInfo2
.hFileInfo
.ioFlFndrInfo
.fdFldr 
= 0; 
1267         error 
= PBCatSearchSyncCompat((CSParamPtr
)&pb
); 
1269         if ( (error 
== noErr
) ||                                                        /* If no errors or the end of catalog was */ 
1270                  (error 
== eofErr
) )                                                    /* found, then the call was successful so */ 
1272                 *actMatchCount 
= pb
.csParam
.ioActMatchCount
;    /* return the match count */ 
1276                 *actMatchCount 
= 0;                                                     /* else no matches found */ 
1279         if ( (error 
== noErr
) ||                                                /* If no errors */ 
1280                  (error 
== catChangedErr
) )                                     /* or there was a change in the catalog */ 
1282                 catPosition 
= pb
.csParam
.ioCatPosition
; 
1283                 lastVRefNum 
= vRefNum
; 
1284                         /* we can probably start the next search where we stopped this time */ 
1288                 catPosition
.initialize 
= 0; 
1289                         /* start the next search from beginning of catalog */ 
1292         if ( pb
.csParam
.ioOptBuffer 
!= NULL 
) 
1294                 DisposePtr(pb
.csParam
.ioOptBuffer
); 
1300 /*****************************************************************************/