]> git.saurik.com Git - wxWidgets.git/blob - src/mac/carbon/morefile/Search.c
Adding GetPartialTextExtents implementation.
[wxWidgets.git] / src / mac / carbon / morefile / Search.c
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
57 enum
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 */
74 struct LevelRec
75 {
76 long dirModDate; /* for detecting most (but not all) catalog changes */
77 long dirID;
78 short index;
79 };
80 typedef struct LevelRec LevelRec;
81 typedef 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
91 struct 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
101 typedef struct SearchPositionRec SearchPositionRec;
102 typedef 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
111 struct 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
120 typedef struct ExtendedTMTask ExtendedTMTask;
121 typedef ExtendedTMTask *ExtendedTMTaskPtr;
122
123 /*****************************************************************************/
124
125 static OSErr CheckVol(ConstStr255Param pathname,
126 short vRefNum,
127 short *realVRefNum,
128 long *volID);
129
130 static OSErr CheckStack(unsigned short stackDepth,
131 LevelRecHandle searchStack,
132 Size *searchStackSize);
133
134 static OSErr VerifyUserPB(CSParamPtr userPB,
135 Boolean *includeFiles,
136 Boolean *includeDirs,
137 Boolean *includeNames);
138
139 static Boolean IsSubString(ConstStr255Param aStringPtr,
140 ConstStr255Param subStringPtr);
141
142 static Boolean CompareMasked(const long *data1,
143 const long *data2,
144 const long *mask,
145 short longsToCompare);
146
147 static 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
159 static pascal void TimeOutTask(TMTaskPtr tmTaskPtr);
160
161 #else
162
163 static pascal TMTaskPtr GetTMTaskPtr(void);
164
165 static void TimeOutTask(void);
166
167 #endif
168
169 #if __WANTPASCALELIMINATION
170 #define pascal
171 #endif
172
173 static 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 */
183 static 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 */
216 static 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 */
246 static 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
358 ParamErrExit:
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 */
369 static 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 */
433 static 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 */
464 static 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
657 Hit:
658 foundMatch = true;
659
660 /* foundMatch is false if code jumps to Failed */
661 Failed:
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
702 static pascal void TimeOutTask(TMTaskPtr tmTaskPtr)
703 {
704 ((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true;
705 }
706
707 #else
708
709 static pascal TMTaskPtr GetTMTaskPtr(void)
710 ONEWORDINLINE(0x2e89); /* MOVE.L A1,(SP) */
711
712 static 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 */
730 static 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
758 pascal 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
1037 pascal 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
1104 pascal 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
1190 pascal 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 /*****************************************************************************/