1/* 2 File: Search.c 3 4 Contains: IndexedSearch and the PBCatSearch compatibility function. 5 6 Version: MoreFiles 7 8 Copyright: � 1992-2001 by Apple Computer, Inc., all rights reserved. 9 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. 17 18 File Ownership: 19 20 DRI: Jim Luther 21 22 Other Contact: Apple Macintosh Developer Technical Support 23 <http://developer.apple.com/bugreporter/> 24 25 Technology: DTS Sample Code 26 27 Writers: 28 29 (JL) Jim Luther 30 31 Change History (most recent first): 32 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. 38*/ 39 40#include <MacTypes.h> 41#include <Gestalt.h> 42#include <Timer.h> 43#include <MacErrors.h> 44#include <MacMemory.h> 45#include <Files.h> 46#include <TextUtils.h> 47 48#define __COMPILINGMOREFILES 49 50#include "MoreFiles.h" 51#include "MoreFilesExtras.h" 52 53#include "Search.h" 54 55/*****************************************************************************/ 56 57enum 58{ 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 63}; 64 65/*****************************************************************************/ 66 67/* 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 72** used as a stack. 73*/ 74struct LevelRec 75{ 76 long dirModDate; /* for detecting most (but not all) catalog changes */ 77 long dirID; 78 short index; 79}; 80typedef struct LevelRec LevelRec; 81typedef LevelRec *LevelRecPtr, **LevelRecHandle; 82 83 84/* 85** SearchPositionRec is my version of a CatPositionRec. It holds the 86** information I need to resuming searching. 87*/ 88#if PRAGMA_STRUCT_ALIGN 89#pragma options align=mac68k 90#endif 91struct SearchPositionRec 92{ 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... */ 97}; 98#if PRAGMA_STRUCT_ALIGN 99#pragma options align=reset 100#endif 101typedef struct SearchPositionRec SearchPositionRec; 102typedef SearchPositionRec *SearchPositionRecPtr; 103 104 105/* 106** ExtendedTMTask is a TMTask record extended to hold the timer flag. 107*/ 108#if PRAGMA_STRUCT_ALIGN 109#pragma options align=mac68k 110#endif 111struct ExtendedTMTask 112{ 113 TMTask theTask; 114 Boolean stopSearch; /* the Time Mgr task will set stopSearch to */ 115 /* true when the timer expires */ 116}; 117#if PRAGMA_STRUCT_ALIGN 118#pragma options align=reset 119#endif 120typedef struct ExtendedTMTask ExtendedTMTask; 121typedef ExtendedTMTask *ExtendedTMTaskPtr; 122 123/*****************************************************************************/ 124 125static OSErr CheckVol(ConstStr255Param pathname, 126 short vRefNum, 127 short *realVRefNum, 128 long *volID); 129 130static OSErr CheckStack(unsigned short stackDepth, 131 LevelRecHandle searchStack, 132 Size *searchStackSize); 133 134static OSErr VerifyUserPB(CSParamPtr userPB, 135 Boolean *includeFiles, 136 Boolean *includeDirs, 137 Boolean *includeNames); 138 139static Boolean IsSubString(ConstStr255Param aStringPtr, 140 ConstStr255Param subStringPtr); 141 142static Boolean CompareMasked(const long *data1, 143 const long *data2, 144 const long *mask, 145 short longsToCompare); 146 147static void CheckForMatches(CInfoPBPtr cPB, 148 CSParamPtr userPB, 149 const Str63 matchName, 150 Boolean includeFiles, 151 Boolean includeDirs); 152 153#if __WANTPASCALELIMINATION 154#undef pascal 155#endif 156 157#if TARGET_RT_MAC_CFM || TARGET_API_MAC_CARBON 158 159static pascal void TimeOutTask(TMTaskPtr tmTaskPtr); 160 161#else 162 163static pascal TMTaskPtr GetTMTaskPtr(void); 164 165static void TimeOutTask(void); 166 167#endif 168 169#if __WANTPASCALELIMINATION 170#define pascal 171#endif 172 173static long GetDirModDate(short vRefNum, 174 long dirID); 175 176/*****************************************************************************/ 177 178/* 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. 182*/ 183static OSErr CheckVol(ConstStr255Param pathname, 184 short vRefNum, 185 short *realVRefNum, 186 long *volID) 187{ 188 HParamBlockRec pb; 189 OSErr error; 190 191 error = GetVolumeInfoNoName(pathname, vRefNum, &pb); 192 if ( error == noErr ) 193 { 194 /* Return the real vRefNum */ 195 *realVRefNum = pb.volumeParam.ioVRefNum; 196 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; 205 } 206 return ( error ); 207} 208 209/*****************************************************************************/ 210 211/* 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. 215*/ 216static OSErr CheckStack(unsigned short stackDepth, 217 LevelRecHandle searchStack, 218 Size *searchStackSize) 219{ 220 OSErr result; 221 222 if ( (*searchStackSize / sizeof(LevelRec)) == (stackDepth + 1) ) 223 { 224 /* Time to grow stack */ 225 SetHandleSize((Handle)searchStack, *searchStackSize + (kAdditionalLevelRecs * sizeof(LevelRec))); 226 result = MemError(); /* should be noErr */ 227 *searchStackSize = GetHandleSize((Handle)searchStack); 228 } 229 else 230 { 231 result = noErr; 232 } 233 234 return ( result ); 235} 236 237/*****************************************************************************/ 238 239/* 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. 245*/ 246static OSErr VerifyUserPB(CSParamPtr userPB, 247 Boolean *includeFiles, 248 Boolean *includeDirs, 249 Boolean *includeNames) 250{ 251 CInfoPBPtr searchInfo1; 252 CInfoPBPtr searchInfo2; 253 254 searchInfo1 = userPB->ioSearchInfo1; 255 searchInfo2 = userPB->ioSearchInfo2; 256 257 /* ioMatchPtr cannot be NULL */ 258 if ( userPB->ioMatchPtr == NULL ) 259 { 260 goto ParamErrExit; 261 } 262 263 /* ioSearchInfo1 cannot be NULL */ 264 if ( searchInfo1 == NULL ) 265 { 266 goto ParamErrExit; 267 } 268 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 )) 273 { 274 goto ParamErrExit; 275 } 276 277 *includeFiles = false; 278 *includeDirs = false; 279 *includeNames = false; 280 281 if ( (userPB->ioSearchBits & (fsSBPartialName | fsSBFullName)) != 0 ) 282 { 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)) ) 288 { 289 goto ParamErrExit; 290 } 291 292 *includeNames = true; 293 } 294 295 if ( (userPB->ioSearchBits & fsSBFlAttrib) != 0 ) 296 { 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 ) 300 { 301 goto ParamErrExit; 302 } 303 304 /* interested in the directory bit? */ 305 if ( (searchInfo2->hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 ) 306 { 307 /* yes, so do they want just directories or just files? */ 308 if ( (searchInfo1->hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 ) 309 { 310 *includeDirs = true; 311 } 312 else 313 { 314 *includeFiles = true; 315 } 316 } 317 else 318 { 319 /* no interest in directory bit - get both files and directories */ 320 *includeDirs = true; 321 *includeFiles = true; 322 } 323 } 324 else 325 { 326 /* no attribute checking - get both files and directories */ 327 *includeDirs = true; 328 *includeFiles = true; 329 } 330 331 /* If directories are included in the search, */ 332 /* then the locked attribute cannot be requested. */ 333 if ( *includeDirs && 334 ((userPB->ioSearchBits & fsSBFlAttrib) != 0) && 335 ((searchInfo2->hFileInfo.ioFlAttrib & kioFlAttribLockedMask) != 0) ) 336 { 337 goto ParamErrExit; 338 } 339 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) ) 344 { 345 goto ParamErrExit; 346 } 347 348 /* If directories are included in the search, then there cannot */ 349 /* be a search on file lengths. */ 350 if ( *includeDirs && 351 ((userPB->ioSearchBits & (fsSBFlLgLen | fsSBFlPyLen | fsSBFlRLgLen | fsSBFlRPyLen)) != 0) ) 352 { 353 goto ParamErrExit; 354 } 355 356 return ( noErr ); 357 358ParamErrExit: 359 return ( paramErr ); 360} 361 362/*****************************************************************************/ 363 364/* 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). 368*/ 369static Boolean IsSubString(ConstStr255Param aStringPtr, 370 ConstStr255Param subStringPtr) 371{ 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 */ 376 377 found = false; 378 strLength = aStringPtr[0]; 379 subStrLength = subStringPtr[0]; 380 381 if ( subStrLength <= strLength) 382 { 383 register short count; /* search counter */ 384 register short strIndex; /* running index into string */ 385 register short subStrIndex; /* running index into subString */ 386 387 /* start looking at first character */ 388 index = 1; 389 390 /* continue looking until remaining string is shorter than substring */ 391 count = strLength - subStrLength + 1; 392 393 do 394 { 395 strIndex = index; /* start string index at index */ 396 subStrIndex = 1; /* start subString index at 1 */ 397 398 while ( !found && (aStringPtr[strIndex] == subStringPtr[subStrIndex]) ) 399 { 400 if ( subStrIndex == subStrLength ) 401 { 402 /* all characters in subString were found */ 403 found = true; 404 } 405 else 406 { 407 /* check next character of substring against next character of string */ 408 ++subStrIndex; 409 ++strIndex; 410 } 411 } 412 413 if ( !found ) 414 { 415 /* start substring search again at next string character */ 416 ++index; 417 --count; 418 } 419 } while ( count != 0 && (!found) ); 420 } 421 422 return ( found ); 423} 424 425/*****************************************************************************/ 426 427/* 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. 432*/ 433static Boolean CompareMasked(const long *data1, 434 const long *data2, 435 const long *mask, 436 short longsToCompare) 437{ 438 Boolean result = true; 439 440 while ( (longsToCompare != 0) && (result == true) ) 441 { 442 /* (*data1 ^ *data2) = bits that are different, so... */ 443 /* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */ 444 445 if ( ((*data1 ^ *data2) & *mask) != 0 ) 446 result = false; 447 448 ++data1; 449 ++data2; 450 ++mask; 451 --longsToCompare; 452 } 453 454 return ( result ); 455} 456 457/*****************************************************************************/ 458 459/* 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. 463*/ 464static void CheckForMatches(CInfoPBPtr cPB, 465 CSParamPtr userPB, 466 const Str63 matchName, 467 Boolean includeFiles, 468 Boolean includeDirs) 469{ 470 long searchBits; 471 CInfoPBPtr searchInfo1; 472 CInfoPBPtr searchInfo2; 473 Str63 itemName; /* copy of object's name for partial name matching */ 474 Boolean foundMatch; 475 476 foundMatch = false; /* default to no match */ 477 478 searchBits = userPB->ioSearchBits; 479 searchInfo1 = userPB->ioSearchInfo1; 480 searchInfo2 = userPB->ioSearchInfo2; 481 482 /* Into the if statements that go on forever... */ 483 484 if ( (cPB->hFileInfo.ioFlAttrib & kioFlAttribDirMask) == 0 ) 485 { 486 if (!includeFiles) 487 { 488 goto Failed; 489 } 490 } 491 else 492 { 493 if (!includeDirs) 494 { 495 goto Failed; 496 } 497 } 498 499 if ( (searchBits & fsSBPartialName) != 0 ) 500 { 501 if ( (cPB->hFileInfo.ioNamePtr[0] > 0) && 502 (cPB->hFileInfo.ioNamePtr[0] <= (sizeof(Str63) - 1)) ) 503 { 504 /* Make uppercase copy of object name */ 505 BlockMoveData(cPB->hFileInfo.ioNamePtr, 506 itemName, 507 cPB->hFileInfo.ioNamePtr[0] + 1); 508 /* Use the same non-international call the File Manager uses */ 509 UpperString(itemName, true); 510 } 511 else 512 { 513 goto Failed; 514 } 515 516 { 517 if ( !IsSubString(itemName, matchName) ) 518 { 519 goto Failed; 520 } 521 else if ( searchBits == fsSBPartialName ) 522 { 523 /* optimize for name matching only since it is most common way to search */ 524 goto Hit; 525 } 526 } 527 } 528 529 if ( (searchBits & fsSBFullName) != 0 ) 530 { 531 /* Use the same non-international call the File Manager uses */ 532 if ( !EqualString(cPB->hFileInfo.ioNamePtr, matchName, false, true) ) 533 { 534 goto Failed; 535 } 536 else if ( searchBits == fsSBFullName ) 537 { 538 /* optimize for name matching only since it is most common way to search */ 539 goto Hit; 540 } 541 } 542 543 if ( (searchBits & fsSBFlParID) != 0 ) 544 { 545 if ( ((unsigned long)(cPB->hFileInfo.ioFlParID) < (unsigned long)(searchInfo1->hFileInfo.ioFlParID)) || 546 ((unsigned long)(cPB->hFileInfo.ioFlParID) > (unsigned long)(searchInfo2->hFileInfo.ioFlParID)) ) 547 { 548 goto Failed; 549 } 550 } 551 552 if ( (searchBits & fsSBFlAttrib) != 0 ) 553 { 554 if ( ((cPB->hFileInfo.ioFlAttrib ^ searchInfo1->hFileInfo.ioFlAttrib) & 555 searchInfo2->hFileInfo.ioFlAttrib) != 0 ) 556 { 557 goto Failed; 558 } 559 } 560 561 if ( (searchBits & fsSBDrNmFls) != 0 ) 562 { 563 if ( ((unsigned long)(cPB->dirInfo.ioDrNmFls) < (unsigned long)(searchInfo1->dirInfo.ioDrNmFls)) || 564 ((unsigned long)(cPB->dirInfo.ioDrNmFls) > (unsigned long)(searchInfo2->dirInfo.ioDrNmFls)) ) 565 { 566 goto Failed; 567 } 568 } 569 570 if ( (searchBits & fsSBFlFndrInfo) != 0 ) /* fsSBFlFndrInfo is same as fsSBDrUsrWds */ 571 { 572 if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlFndrInfo), 573 (long *)&(searchInfo1->hFileInfo.ioFlFndrInfo), 574 (long *)&(searchInfo2->hFileInfo.ioFlFndrInfo), 575 sizeof(FInfo) / sizeof(long)) ) 576 { 577 goto Failed; 578 } 579 } 580 581 if ( (searchBits & fsSBFlXFndrInfo) != 0 ) /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */ 582 { 583 if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlXFndrInfo), 584 (long *)&(searchInfo1->hFileInfo.ioFlXFndrInfo), 585 (long *)&(searchInfo2->hFileInfo.ioFlXFndrInfo), 586 sizeof(FXInfo) / sizeof(long)) ) 587 { 588 goto Failed; 589 } 590 } 591 592 if ( (searchBits & fsSBFlLgLen) != 0 ) 593 { 594 if ( ((unsigned long)(cPB->hFileInfo.ioFlLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlLgLen)) || 595 ((unsigned long)(cPB->hFileInfo.ioFlLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlLgLen)) ) 596 { 597 goto Failed; 598 } 599 } 600 601 if ( (searchBits & fsSBFlPyLen) != 0 ) 602 { 603 if ( ((unsigned long)(cPB->hFileInfo.ioFlPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlPyLen)) || 604 ((unsigned long)(cPB->hFileInfo.ioFlPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlPyLen)) ) 605 { 606 goto Failed; 607 } 608 } 609 610 if ( (searchBits & fsSBFlRLgLen) != 0 ) 611 { 612 if ( ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRLgLen)) || 613 ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRLgLen)) ) 614 { 615 goto Failed; 616 } 617 } 618 619 if ( (searchBits & fsSBFlRPyLen) != 0 ) 620 { 621 if ( ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRPyLen)) || 622 ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRPyLen)) ) 623 { 624 goto Failed; 625 } 626 } 627 628 if ( (searchBits & fsSBFlCrDat) != 0 ) /* fsSBFlCrDat is same as fsSBDrCrDat */ 629 { 630 if ( ((unsigned long)(cPB->hFileInfo.ioFlCrDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlCrDat)) || 631 ((unsigned long)(cPB->hFileInfo.ioFlCrDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlCrDat)) ) 632 { 633 goto Failed; 634 } 635 } 636 637 if ( (searchBits & fsSBFlMdDat) != 0 ) /* fsSBFlMdDat is same as fsSBDrMdDat */ 638 { 639 if ( ((unsigned long)(cPB->hFileInfo.ioFlMdDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlMdDat)) || 640 ((unsigned long)(cPB->hFileInfo.ioFlMdDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlMdDat)) ) 641 { 642 goto Failed; 643 } 644 } 645 646 if ( (searchBits & fsSBFlBkDat) != 0 ) /* fsSBFlBkDat is same as fsSBDrBkDat */ 647 { 648 if ( ((unsigned long)(cPB->hFileInfo.ioFlBkDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlBkDat)) || 649 ((unsigned long)(cPB->hFileInfo.ioFlBkDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlBkDat)) ) 650 { 651 goto Failed; 652 } 653 } 654 655 /* Hey, we passed all of the tests! */ 656 657Hit: 658 foundMatch = true; 659 660/* foundMatch is false if code jumps to Failed */ 661Failed: 662 /* Do we reverse our findings? */ 663 if ( (searchBits & fsSBNegate) != 0 ) 664 { 665 foundMatch = !foundMatch; /* matches are not, not matches are */ 666 } 667 668 if ( foundMatch ) 669 { 670 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 ) 675 { 676 cPB->hFileInfo.ioNamePtr[0] = 63; 677 } 678 BlockMoveData(cPB->hFileInfo.ioNamePtr, 679 userPB->ioMatchPtr[userPB->ioActMatchCount].name, 680 cPB->hFileInfo.ioNamePtr[0] + 1); 681 682 /* increment the actual count */ 683 ++(userPB->ioActMatchCount); 684 } 685} 686 687/*****************************************************************************/ 688 689/* 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 693** continue. 694*/ 695 696#if __WANTPASCALELIMINATION 697#undef pascal 698#endif 699 700#if TARGET_RT_MAC_CFM || TARGET_API_MAC_CARBON 701 702static pascal void TimeOutTask(TMTaskPtr tmTaskPtr) 703{ 704 ((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true; 705} 706 707#else 708 709static pascal TMTaskPtr GetTMTaskPtr(void) 710 ONEWORDINLINE(0x2e89); /* MOVE.L A1,(SP) */ 711 712static void TimeOutTask(void) 713{ 714 ((ExtendedTMTaskPtr)GetTMTaskPtr())->stopSearch = true; 715} 716 717#endif 718 719#if __WANTPASCALELIMINATION 720#define pascal 721#endif 722 723/*****************************************************************************/ 724 725/* 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. 729*/ 730static long GetDirModDate(short vRefNum, 731 long dirID) 732{ 733 CInfoPBRec pb; 734 Str31 tempName; 735 long modDate; 736 737 /* Protection against File Sharing problem */ 738 tempName[0] = 0; 739 pb.dirInfo.ioNamePtr = tempName; 740 pb.dirInfo.ioVRefNum = vRefNum; 741 pb.dirInfo.ioDrDirID = dirID; 742 pb.dirInfo.ioFDirIndex = -1; /* use ioDrDirID */ 743 744 if ( PBGetCatInfoSync(&pb) == noErr ) 745 { 746 modDate = pb.dirInfo.ioDrMdDat; 747 } 748 else 749 { 750 modDate = -1; 751 } 752 753 return ( modDate ); 754} 755 756/*****************************************************************************/ 757 758pascal OSErr IndexedSearch(CSParamPtr pb, 759 long dirID) 760{ 761 static LevelRecHandle searchStack = NULL; /* static handle to LevelRec stack */ 762 static Size searchStackSize = 0; /* size of static handle */ 763 SearchPositionRecPtr catPosition; 764 long modDate; 765 short index = -1 ; 766 ExtendedTMTask timerTask; 767 OSErr result; 768 short realVRefNum; 769 Str63 itemName; 770 CInfoPBRec cPB; 771 long tempLong; 772 Boolean includeFiles; 773 Boolean includeDirs; 774 Boolean includeNames; 775 Str63 upperName; 776 777 timerTask.stopSearch = false; /* don't stop yet! */ 778 779 /* If request has a timeout, install a Time Manager task. */ 780 if ( pb->ioSearchTime != 0 ) 781 { 782 /* Start timer */ 783 timerTask.theTask.tmAddr = NewTimerUPP(TimeOutTask); 784 InsTime((QElemPtr)&(timerTask.theTask)); 785 PrimeTime((QElemPtr)&(timerTask.theTask), pb->ioSearchTime); 786 } 787 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 ) 795 { 796 pb->ioActMatchCount = 0; /* no matches yet */ 797 798 if ( includeNames ) 799 { 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, 804 upperName, 805 pb->ioSearchInfo1->hFileInfo.ioNamePtr[0] + 1); 806 /* Use the same non-international call the File Manager uses */ 807 UpperString(upperName, true); 808 } 809 810 /* Prevent casting to my type throughout code */ 811 catPosition = (SearchPositionRecPtr)&pb->ioCatPosition; 812 813 /* Create searchStack first time called */ 814 if ( searchStack == NULL ) 815 { 816 searchStack = (LevelRecHandle)NewHandle(kAdditionalLevelRecs * sizeof(LevelRec)); 817 } 818 819 /* Make sure searchStack really exists */ 820 if ( searchStack != NULL ) 821 { 822 searchStackSize = GetHandleSize((Handle)searchStack); 823 824 /* See if the search is a new search or a resumed search. */ 825 if ( catPosition->initialize == 0 ) 826 { 827 /* New search. */ 828 829 /* Get the real vRefNum and fill in catPosition->initialize. */ 830 result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &catPosition->initialize); 831 if ( result == noErr ) 832 { 833 /* clear searchStack */ 834 catPosition->stackDepth = 0; 835 836 /* use dirID parameter passed and... */ 837 index = -1; /* start with the passed directory itself! */ 838 } 839 } 840 else 841 { 842 /* We're resuming a search. */ 843 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 ) 847 { 848 /* Make sure the resumed search is to the same volume! */ 849 if ( catPosition->initialize == tempLong ) 850 { 851 /* For resume, catPosition->stackDepth > 0 */ 852 if ( catPosition->stackDepth > 0 ) 853 { 854 /* Position catPosition->stackDepth to access last saved level */ 855 --(catPosition->stackDepth); 856 857 /* Get the dirID and index for the next item */ 858 dirID = (*searchStack)[catPosition->stackDepth].dirID; 859 index = (*searchStack)[catPosition->stackDepth].index; 860 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 ) 864 { 865 result = catChangedErr; 866 } 867 } 868 else 869 { 870 /* Invalid catPosition record was passed */ 871 result = paramErr; 872 } 873 } 874 else 875 { 876 /* The volume is not the same */ 877 result = catChangedErr; 878 } 879 } 880 } 881 882 if ( result == noErr ) 883 { 884 /* ioNamePtr and ioVRefNum only need to be set up once. */ 885 cPB.hFileInfo.ioNamePtr = itemName; 886 cPB.hFileInfo.ioVRefNum = realVRefNum; 887 888 /* 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. 894 ** 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 900 ** has fired. 901 ** (c) the number of matches requested by the caller has been found. 902 ** (d) the last item on the volume was found. 903 */ 904 do 905 { 906 /* get the next item */ 907 cPB.hFileInfo.ioFDirIndex = index; 908 cPB.hFileInfo.ioDirID = dirID; 909 result = PBGetCatInfoSync(&cPB); 910 if ( index != -1 ) 911 { 912 if ( result == noErr ) 913 { 914 /* We found something */ 915 916 CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs); 917 918 ++index; 919 if ( (cPB.dirInfo.ioFlAttrib & kioFlAttribDirMask) != 0 ) 920 { 921 /* It's a directory */ 922 923 result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize); 924 if ( result == noErr ) 925 { 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); 931 932 /* position catPosition->stackDepth for next saved level */ 933 ++(catPosition->stackDepth); 934 935 /* The next item to get is the 1st item in the child directory */ 936 dirID = cPB.dirInfo.ioDrDirID; 937 index = 1; 938 } 939 } 940 /* else do nothing for files */ 941 } 942 else 943 { 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 ) 949 { 950 /* position catPosition->stackDepth to access last saved level */ 951 --(catPosition->stackDepth); 952 953 dirID = (*searchStack)[catPosition->stackDepth].dirID; 954 index = (*searchStack)[catPosition->stackDepth].index; 955 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 ) 959 { 960 result = catChangedErr; 961 } 962 else 963 { 964 /* Going back to ancestor directory. */ 965 /* Clear error so we can continue. */ 966 result = noErr; 967 } 968 } 969 else 970 { 971 /* We hit the bottom of the stack, so we'll let the */ 972 /* the eofErr drop us out of the loop. */ 973 result = eofErr; 974 } 975 } 976 } 977 else 978 { 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 */ 981 /* passed to us. */ 982 if ( result == noErr ) 983 { 984 /* We found something */ 985 986 CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs); 987 988 /* Now, set the index to 1 and then we're ready to look inside */ 989 /* the passed directory. */ 990 index = 1; 991 } 992 } 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 */ 996 997 /* Did we drop out of the loop because of timeout or */ 998 /* ioReqMatchCount was found? */ 999 if ( result == noErr ) 1000 { 1001 result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize); 1002 if ( result == noErr ) 1003 { 1004 /* Either there was a timeout or ioReqMatchCount was reached. */ 1005 /* Save the dirID and index for the next time we're called. */ 1006 1007 (*searchStack)[catPosition->stackDepth].dirID = dirID; 1008 (*searchStack)[catPosition->stackDepth].index = index; 1009 (*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID); 1010 1011 /* position catPosition->stackDepth for next saved level */ 1012 1013 ++(catPosition->stackDepth); 1014 } 1015 } 1016 } 1017 } 1018 else 1019 { 1020 /* searchStack Handle could not be allocated */ 1021 result = memFullErr; 1022 } 1023 } 1024 1025 if ( pb->ioSearchTime != 0 ) 1026 { 1027 /* Stop Time Manager task here if it was installed */ 1028 RmvTime((QElemPtr)&(timerTask.theTask)); 1029 DisposeTimerUPP(timerTask.theTask.tmAddr); 1030 } 1031 1032 return ( result ); 1033} 1034 1035/*****************************************************************************/ 1036 1037pascal OSErr PBCatSearchSyncCompat(CSParamPtr paramBlock) 1038{ 1039 OSErr result; 1040 Boolean supportsCatSearch; 1041 GetVolParmsInfoBuffer volParmsInfo; 1042 long infoSize; 1043#if !__MACOSSEVENORLATER 1044 static Boolean fullExtFSDispatchingtested = false; 1045 static Boolean hasFullExtFSDispatching = false; 1046 long response; 1047#endif 1048 1049 result = noErr; 1050 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 ) 1056 { 1057 fullExtFSDispatchingtested = true; 1058 if ( Gestalt(gestaltFSAttr, &response) == noErr ) 1059 { 1060 hasFullExtFSDispatching = ((response & (1L << gestaltFullExtFSDispatching)) != 0); 1061 } 1062 } 1063#endif 1064 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 ) 1070#endif 1071 { 1072 infoSize = sizeof(GetVolParmsInfoBuffer); 1073 result = HGetVolParms(paramBlock->ioNamePtr, paramBlock->ioVRefNum, 1074 &volParmsInfo, &infoSize); 1075 if ( result == noErr ) 1076 { 1077 supportsCatSearch = hasCatSearch(&volParmsInfo); 1078 } 1079 } 1080 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) ) 1084 { 1085 if ( supportsCatSearch ) 1086 { 1087 /* Volume supports CatSearch so use it. */ 1088 /* CatSearch is faster than an indexed search. */ 1089 result = PBCatSearchSync(paramBlock); 1090 } 1091 else 1092 { 1093 /* Volume doesn't support CatSearch so */ 1094 /* search using IndexedSearch from root directory. */ 1095 result = IndexedSearch(paramBlock, fsRtDirID); 1096 } 1097 } 1098 1099 return ( result ); 1100} 1101 1102/*****************************************************************************/ 1103 1104pascal OSErr NameFileSearch(ConstStr255Param volName, 1105 short vRefNum, 1106 ConstStr255Param fileName, 1107 FSSpecPtr matches, 1108 long reqMatchCount, 1109 long *actMatchCount, 1110 Boolean newSearch, 1111 Boolean partial) 1112{ 1113 CInfoPBRec searchInfo1, searchInfo2; 1114 HParamBlockRec pb; 1115 OSErr error; 1116 static CatPositionRec catPosition; 1117 static short lastVRefNum = 0; 1118 1119 /* get the real volume reference number */ 1120 error = DetermineVRefNum(volName, vRefNum, &vRefNum); 1121 if ( error != noErr ) 1122 return ( error ); 1123 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: */ 1129 { 1130 pb.csParam.ioSearchBits = fsSBPartialName + fsSBFlAttrib; /* partial name file matches or */ 1131 } 1132 else 1133 { 1134 pb.csParam.ioSearchBits = fsSBFullName + fsSBFlAttrib; /* full name file matches */ 1135 } 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, */ 1141 { 1142 catPosition.initialize = 0; /* then search from beginning of catalog */ 1143 } 1144 pb.csParam.ioCatPosition = catPosition; 1145 pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize); 1146 1147 /* search for fileName */ 1148 searchInfo1.hFileInfo.ioNamePtr = (StringPtr)fileName; 1149 searchInfo2.hFileInfo.ioNamePtr = NULL; 1150 1151 /* only match files (not directories) */ 1152 searchInfo1.hFileInfo.ioFlAttrib = 0x00; 1153 searchInfo2.hFileInfo.ioFlAttrib = kioFlAttribDirMask; 1154 1155 error = PBCatSearchSyncCompat((CSParamPtr)&pb); 1156 1157 if ( (error == noErr) || /* If no errors or the end of catalog was */ 1158 (error == eofErr) ) /* found, then the call was successful so */ 1159 { 1160 *actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */ 1161 } 1162 else 1163 { 1164 *actMatchCount = 0; /* else no matches found */ 1165 } 1166 1167 if ( (error == noErr) || /* If no errors */ 1168 (error == catChangedErr) ) /* or there was a change in the catalog */ 1169 { 1170 catPosition = pb.csParam.ioCatPosition; 1171 lastVRefNum = vRefNum; 1172 /* we can probably start the next search where we stopped this time */ 1173 } 1174 else 1175 { 1176 catPosition.initialize = 0; 1177 /* start the next search from beginning of catalog */ 1178 } 1179 1180 if ( pb.csParam.ioOptBuffer != NULL ) 1181 { 1182 DisposePtr(pb.csParam.ioOptBuffer); 1183 } 1184 1185 return ( error ); 1186} 1187 1188/*****************************************************************************/ 1189 1190pascal OSErr CreatorTypeFileSearch(ConstStr255Param volName, 1191 short vRefNum, 1192 OSType creator, 1193 OSType fileType, 1194 FSSpecPtr matches, 1195 long reqMatchCount, 1196 long *actMatchCount, 1197 Boolean newSearch) 1198{ 1199 CInfoPBRec searchInfo1, searchInfo2; 1200 HParamBlockRec pb; 1201 OSErr error; 1202 static CatPositionRec catPosition; 1203 static short lastVRefNum = 0; 1204 1205 /* get the real volume reference number */ 1206 error = DetermineVRefNum(volName, vRefNum, &vRefNum); 1207 if ( error != noErr ) 1208 return ( error ); 1209 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, */ 1220 { 1221 catPosition.initialize = 0; /* then search from beginning of catalog */ 1222 } 1223 pb.csParam.ioCatPosition = catPosition; 1224 pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize); 1225 1226 /* no fileName */ 1227 searchInfo1.hFileInfo.ioNamePtr = NULL; 1228 searchInfo2.hFileInfo.ioNamePtr = NULL; 1229 1230 /* only match files (not directories) */ 1231 searchInfo1.hFileInfo.ioFlAttrib = 0x00; 1232 searchInfo2.hFileInfo.ioFlAttrib = kioFlAttribDirMask; 1233 1234 /* search for creator; if creator = 0x00000000, ignore creator */ 1235 searchInfo1.hFileInfo.ioFlFndrInfo.fdCreator = creator; 1236 if ( creator == (OSType)0x00000000 ) 1237 { 1238 searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0x00000000; 1239 } 1240 else 1241 { 1242 searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0xffffffff; 1243 } 1244 1245 /* search for fileType; if fileType = 0x00000000, ignore fileType */ 1246 searchInfo1.hFileInfo.ioFlFndrInfo.fdType = fileType; 1247 if ( fileType == (OSType)0x00000000 ) 1248 { 1249 searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0x00000000; 1250 } 1251 else 1252 { 1253 searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0xffffffff; 1254 } 1255 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; 1261 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; 1266 1267 error = PBCatSearchSyncCompat((CSParamPtr)&pb); 1268 1269 if ( (error == noErr) || /* If no errors or the end of catalog was */ 1270 (error == eofErr) ) /* found, then the call was successful so */ 1271 { 1272 *actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */ 1273 } 1274 else 1275 { 1276 *actMatchCount = 0; /* else no matches found */ 1277 } 1278 1279 if ( (error == noErr) || /* If no errors */ 1280 (error == catChangedErr) ) /* or there was a change in the catalog */ 1281 { 1282 catPosition = pb.csParam.ioCatPosition; 1283 lastVRefNum = vRefNum; 1284 /* we can probably start the next search where we stopped this time */ 1285 } 1286 else 1287 { 1288 catPosition.initialize = 0; 1289 /* start the next search from beginning of catalog */ 1290 } 1291 1292 if ( pb.csParam.ioOptBuffer != NULL ) 1293 { 1294 DisposePtr(pb.csParam.ioOptBuffer); 1295 } 1296 1297 return ( error ); 1298} 1299 1300/*****************************************************************************/ 1301